Rails’s FormBuilder fields_for Custom Fields Trick


Have you ever had the need to create custom form fields (say, a text input field) inside a form_for call?

Let’s suppose we have the following scenario:

class Order < ActiveRecord::Base
    has_many :line_items, :dependent => :destroy
end

class LineItem < ActiveRecord::Base
  belongs_to :order
  serialize :options
  attr_accessible :options

  def needs_options?
    true # pretend this does something more meaningful
  end
end

Notice how the LineItem model has one field options, which is serializable. That means we can assign it (almost) any object and Rails will serialize it when storing it in the database and then automatically unserialize it upon retrieval. Let's assume we want to assign that field a hash for some line items (for which LineItem#needs_options? returns true).

The purpose here is to construct a form, which allows us to edit an order and its attached line items and their eventual options all at once.

Taking advantage of FormBuilder#object and FormBuilder#object_name (see here for docs), we can construct a form this way:

<% form_for :order do |form| %>
    <%= form.label :notes %>
    <%= form.text_area :notes %>

    <% form.fields_for :line_items do |line_item_fields| %>
        <% line_item = line_item_fields.object %>

        <% if line_item.needs_options? %>
            <% tag_name = "#{line_item_fields.object_name}[options][some_option_name]" %>
            <%= label_tag tag_name, "Some option name:" %>
            <%= text_field_tag tag_name, :value => "some_option_value" %>
        <% end %>
    <% end %>

    <%= form.submit 'Save Changes' %>
<% end %>

The problem here is that we don't know how Rails prefixes the names of the fields of nested objects (in our case, the line items). That's why we need a way to get that info from Rails and just append to it our custom field name (in our case, [options][some_option_name]). This is exactly what we can get by calling line_item_fields.object_name (which is in fact FormBuilder#object_name).

The form above can result in a params hash, which looks something like this:

"order" => {
    "notes" => "This order is just an example.",
    "line_items_attributes" => {
        "0" => {
            "id" => "1234",
            "options" => {
                "some_option_name" => "My custom value for line item 1234",
            },
        },
        "1" => {
            "id" => "1235",
            "options" => {
                "some_option_name" => "Another value for line item 1235",
            },
        },
    }
}

If the code above does not make sense to you, then you probably don't need to and should not use FormBuilder#object_name. Otherwise, you owe me a beer :)

Comments are closed.