ActionView::Helpers::FormHelperを読む

Railsコードリーディング

ActionView::Helpers::FormHelperを読む

2013/2/17

@blp1526

Module FormHelper

actionpack/lib/action_view/helpers/form_helper.rb

extend ActiveSupport::Concern

include FormTagHelper
include UrlHelper
include ModelNaming

form_for

def form_for(record, options = {}, &block)
# 省略
end
  • 引数について
    • record、ハッシュのoptions、ブロックの&blockの3つ

ブロックの有無の判定

block_given?がfalseの場合に例外

def form_for(record, option = {}, &block)
  raise ArgumentError, "Missing block" unless block_given?
  html_options = options[:html] ||= {}

recordの判定(String、Symbolの場合)

  case record
  when String, Symbol
    object_name = record
    object      = nil
  else
  • String、Symbolの場合はローカル変数objectはnil
  • 疑問
    • object_name とは?
    • object とは?

recordの判定(String、Symbol以外の場合)

    object      = record.is_a?(Array) ? record.last : record
  • String、Symbol以外の場合、Arrayならrecordのlastがobjectに
  • Arrayでなければrecordがobjectに
  • recordが配列の場合についてはこちらを参照

ローカル変数objectの判定

    raise ArgumentError, "First argument in form cannot contain nil or be empty" unless object
    object_name = options[:as] || model_name_from_record_or_class(object).param_key
    apply_form_for_options!(record, object, options)
  end
  • objectがnilもしくはfalseであれば例外
  • options[:as]が指定されていればそれがobject_nameになる
  • 指定されていない場合は?

model_name_from_record_or_class

  • 以下、actionpack/lib/action_view/model_naming.rbに定義
module ActionView
  module ModelNaming
    def model_name_from_record_or_class(record_or_class)
      (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
    end
  end
end
  • ClassはClass < Object、詳細はこちらにて
  • 以上、actionpack/lib/action_view/model_naming.rbより

convert_to_model

以下、actionpack/lib/action_view/helpers/form_helper.rbに定義

def convert_to_model(object)
  object.respond_to?(:to_model) ? object.to_model : object
end

to_model

activemodel/lib/active_model/conversion.rb

def to_model
  self
end

model_name

activemodel/lib/active_model/naming.rb

def model_name
  @_model_name ||= begin
    namespace = self.parents.detect do |n|
      n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
    end
    ActiveModel::Name.new(self, namespace)
  end
end

ActiveModel::Name.new

ActiveModel::Name < String
activemodel/lib/active_model/naming.rb

def initialize(klass, namespace = nil, name = nil)
  name ||= klass.name

  raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?

  super(name)

  # 以下略
end

html_optionsの判定

以下、再びform_forに

  html_options[:data]   = options.delete(:data)   if options.has_key?(:data)
  html_options[:remote] = options.delete(:remote) if options.has_key?(:remote)
  html_options[:method] = options.delete(:method) if options.has_key?(:method)
  html_options[:authenticity_token] = options.delete(:authenticity_token)

タグの作成

  builder = instantiate_builder(object_name, object, options)
  output  = capture(builder, &block)
  html_options[:multipart] = builder.multipart?

  form_tag(options[:url] || {}, html_options) { output }
end

以上でform_forの定義は終わり

instantiate_builder

      private
        def instantiate_builder(record_name, record_object, options)
          case record_name
          when String, Symbol
            object = record_object
            object_name = record_name
          else
            object = record_name
            object_name = model_name_from_record_or_class(object).param_key
          end
          builder = options[:builder] || default_form_builder
          builder.new(object_name, object, self, options)
        end
  • record_nameで分岐
    • String、Symbolの場合
    • それ以外の場合

default_form_builder

      private
        def default_form_builder
          builder = ActionView::Base.default_form_builder
          builder.respond_to?(:constantize) ? builder.constantize : builder
        end

capture

Kernel、でよいのか?
actionpack/lib/action_view/helpers/capture_helper.rb

def capture(stream)
  begin
    stream = stream.to_s
    eval "$#{stream} = StringIO.new"
    yield
    result = eval("$#{stream}").string
  ensure
    eval("$#{stream} = #{stream.upcase}")
  end

  result
end

builder.multipart?

actionpack/lib/action_view/helpers/form_helper.rb

    class FormBuilder
      include ModelNaming
      # 省略
      attr_accessor :object_name, :object, :options
      attr_reader :multipart, :index
      alias :multipart? :multipart
      def multipart=(multipart)
        @multipart = multipart
        if parent_builder = @options[:parent_builder]
          parent_builder.multipart = multipart
        end
      end

form_tag

以上、form_forの処理