Lingceng's Blog

Rails Helper Magic

Rails view helpers default value magic

Following will set default value for :first_name field if an instance variable @person present.

<%= form_for :person do |f| %>
  First name: <%= f.text_field :first_name %><br />
<% end %>

Actually the following will set the default value too.

First name: <%= text_field :person, :first_name %><br />

The magic is hidden in retrieve_object method in lib/action_view/helpers/tags/base.rb. The @template_object is the view context here.

def retrieve_object(object)
  if object
    object
  elsif @template_object.instance_variable_defined?("@#{@object_name}")
    @template_object.instance_variable_get("@#{@object_name}")
  end
  ...
end

Most form helpers such as text_field, check_box and select, is based on Helpers:Tags::Base. So the magic works for them too.

Rails helpers has two similar definitions

There are always two similar definitions for most view helpers. Such as check_box and select.

One is in ActionView::Helpers::[Some]Helper.

check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")

The other is in ActionView::Helpers::FormBuilder.

check_box(method, options = {}, checked_value = "1", unchecked_value = "0")

The difference is the first definition needs to specify the object_name.

The truth is most helpers provide a wrapper for FormBuilder class.

See the check_box in FormBuilder below. The @template is the view context here. The objectify_options will pass in the current object of the form.

def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
  @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
end

By the way, helpers ends with ‘tag’, such as check_box_tag and select_tag, have another implement. They have much different usage.

Difference between form_for and fields_for

Both are helpers and very similar. form_for handles more options for URL and methods.

def form_for(record, options = {}, &block)
  ...
  builder = instantiate_builder(object_name, object, options)
  output  = capture(builder, &block)
  form_tag(options[:url] || {}, html_options) { output }
end

def fields_for(record_name, record_object = nil, options = {}, &block)
  builder = instantiate_builder(record_name, record_object, options)
  capture(builder, &block)
end

But fields_for also defined in FormBuilder class while form_for did not. So we can use fields_for as following:

f.fields_for :orders do |order_form|
end

The FormBuilder#fields_for handle some details about accepts_nested_attributes_for.

Comments