Flowdock
fields_for(record_or_name_or_array, *args, &block) public

Creates a scope around a specific model object like form_for, but doesn’t create the form tags themselves. This makes fields_for suitable for specifying additional model objects in the same form.

Generic Examples

<%= form_for @person do |person_form| %>
  First name: <%= person_form.text_field :first_name %>
  Last name : <%= person_form.text_field :last_name %>

  <%= fields_for @person.permission do |permission_fields| %>
    Admin?  : <%= permission_fields.check_box :admin %>
  <% end %>
<% end %>

…or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:

<%= fields_for :person, @client do |permission_fields| %>
  Admin?: <%= permission_fields.check_box :admin %>
<% end %>

…or if you don’t have an object, just a name of the parameter:

<%= fields_for :person do |permission_fields| %>
  Admin?: <%= permission_fields.check_box :admin %>
<% end %>

Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base, like FormOptionHelper#collection_select and DateHelper#datetime_select.

Nested Attributes Examples

When the object belonging to the current scope has a nested attribute writer for a certain attribute, fields_for will yield a new scope for that attribute. This allows you to create forms that set or change the attributes of a parent object and its associations in one go.

Nested attribute writers are normal setter methods named after an association. The most common way of defining these writers is either with accepts_nested_attributes_for in a model definition or by defining a method with the proper name. For example: the attribute writer for the association :address is called address_attributes=.

Whether a one-to-one or one-to-many style form builder will be yielded depends on whether the normal reader method returns a single object or an array of objects.

One-to-one

Consider a Person class which returns a single Address from the address reader method and responds to the address_attributes= writer method:

class Person
  def address
    @address
  end

  def address_attributes=(attributes)
    # Process the attributes hash
  end
end

This model can now be used with a nested fields_for, like so:

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :address do |address_fields| %>
    Street  : <%= address_fields.text_field :street %>
    Zip code: <%= address_fields.text_field :zip_code %>
  <% end %>
<% end %>

When address is already an association on a Person you can use accepts_nested_attributes_for to define the writer method for you:

class Person < ActiveRecord::Base
  has_one :address
  accepts_nested_attributes_for :address
end

If you want to destroy the associated model through the form, you have to enable it first using the :allow_destroy option for accepts_nested_attributes_for:

class Person < ActiveRecord::Base
  has_one :address
  accepts_nested_attributes_for :address, :allow_destroy => true
end

Now, when you use a form element with the _destroy parameter, with a value that evaluates to true, you will destroy the associated model (eg. 1, ‘1’, true, or ‘true’):

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :address do |address_fields| %>
    ...
    Delete: <%= address_fields.check_box :_destroy %>
  <% end %>
<% end %>

One-to-many

Consider a Person class which returns an array of Project instances from the projects reader method and responds to the projects_attributes= writer method:

class Person
  def projects
    [@project1, @project2]
  end

  def projects_attributes=(attributes)
    # Process the attributes hash
  end
end

This model can now be used with a nested fields_for. The block given to the nested fields_for call will be repeated for each instance in the collection:

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :projects do |project_fields| %>
    <% if project_fields.object.active? %>
      Name: <%= project_fields.text_field :name %>
    <% end %>
  <% end %>
<% end %>

It’s also possible to specify the instance to be used:

<%= form_for @person do |person_form| %>
  ...
  <% @person.projects.each do |project| %>
    <% if project.active? %>
      <%= person_form.fields_for :projects, project do |project_fields| %>
        Name: <%= project_fields.text_field :name %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Or a collection to be used:

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
    Name: <%= project_fields.text_field :name %>
  <% end %>
<% end %>

When projects is already an association on Person you can use accepts_nested_attributes_for to define the writer method for you:

class Person < ActiveRecord::Base
  has_many :projects
  accepts_nested_attributes_for :projects
end

If you want to destroy any of the associated models through the form, you have to enable it first using the :allow_destroy option for accepts_nested_attributes_for:

class Person < ActiveRecord::Base
  has_many :projects
  accepts_nested_attributes_for :projects, :allow_destroy => true
end

This will allow you to specify which models to destroy in the attributes hash by adding a form element for the _destroy parameter with a value that evaluates to true (eg. 1, ‘1’, true, or ‘true’):

<%= form_for @person do |person_form| %>
  ...
  <%= person_form.fields_for :projects do |project_fields| %>
    Delete: <%= project_fields.check_box :_destroy %>
  <% end %>
<% end %>
Show source
Register or log in to add new notes.
April 8, 2009 - (>= v2.3.2)
13 thanks

Setting child_index while using nested attributes mass assignment

When using nested attributes mass assignment sometimes you will want to add new records with javascript. You can do it with pure javascript, but if HTML is long your javascript will be long and messy and it will not be DRY as probably you already have a partial for it.

So to add a partial dynamically you can do something like that (notice string “index_to_replace_with_js”):

link_to_function

def add_object_link(name, form, object, partial, where)
  options = {:parent => true}.merge(options)
  html = render(:partial => partial, :locals => { :form => form}, :object => object)
  link_to_function name, %{
    var new_object_id = new Date().getTime() ;
    var html = jQuery(#{js html}.replace(/index_to_replace_with_js/g, new_object_id)).hide();
    html.appendTo(jQuery("#{where}")).slideDown('slow');
  }
end

js method in one of helpers (from minus mor plugin)

def js(data)
  if data.respond_to? :to_json
    data.to_json
  else
    data.inspect.to_json
  end
end

This method will generate link adding generated partial to html.

The thing that is not mentioned in docs is how to set child_index. You must add it as an argument in hash.

Example of partial

<% form.fields_for :tasks, task, :child_index => 
       (task.new_record? ? "index_to_replace_with_js" : nil) do |tasks_form| %>
  <% tasks_form.text_field :name %>
<% end %>

Using add_object_link

<% form_for :project do |form| %>
  <div id="tasks">
    <%# displaying existing tasks %>
  </div>

  <%= add_object_link("New task, form, Task.new, "task", "#tasks") %>

<% end %>

Thanks to child_index after insertion it will change indexes to current time in miliseconds so added tasks will have different names and ids.

January 22, 2009
6 thanks

Using fields_for with has_many associations

If you want to edit each element of an array of objects (such as with a has_many type association), you will need to include “[]” in your field parameter name, like so:

<% fields_for "object[]" do |subfield| -%>
  [...]
 <% end -%>

Because you named the field parameter “object[]”, fields_for will assume you have an instance variable @object to use for the fields’ values. To fake this, you can do something like:

<% objects.each do |@object| -%>
  <% fields_for "object[]" do |subfield| -%>
    [...]
  <% end -%>
<% end -%>

If that looks like sacrilegious Rails code to you, then you could consider:

<% objects.each do |object| -%>
  <% fields_for "object[]", object do |subfield| -%>
    [...]
  <% end -%>
<% end -%>

In either case, params[:object] will be a hash where the ID of each object (determined via ActiveRecord::Base#to_param ) is associated with a hash of its new values:

params = { 'object' => { '123' => { 'field' => 'newval' }, '159' => { 'field' => 'newval' } } }
August 21, 2009
2 thanks

Passing parameters to custom formbuilders

If you implement your own formbuilder, the options passed are available as @options inside your formbuilder. If you want those configuration options passed to all builders in the fields_for sections, use the following code in your form builder:

def fields_for_with_options(record_or_name_or_array, *args, &block) options = args.extract_options! fields_for_without_options(record_or_name_or_array, *(args << options.merge(@options)), &block) end alias_method_chain :fields_for, :options

Usage:

form_for @my_object, :builder => MyCustomFormbuilder, :some_setting => :cool
October 23, 2009
2 thanks

Setting child_index while using nested attributes mass assignment with prototype

First of all, drogus idea really helped me. I’m not using jQuery, therefore I implemented my own version:

link_to_function

def add_object_link(name, where, render_options)
  html = render(render_options)

  link_to_function name, %{
    Element.insert('#{where}', #{html.to_json}.replace(/index_to_replace_with_js/g, new Date().getTime()));
  }
end

Using add_object_link

<%= add_object_link 'Add asset', 'assets', :partial => 'assets/asset', :object => Asset.new, :locals => { :f => f } %>
October 7, 2010
1 thank

Preserve order of elements within fields_for

I had the @colleagues collection prepared that I wanted to be rendered within fields_for block. However it was searchlogic object with filtering/sorting applied, and the sort order was not preserved in resultant view.

<%= f.fields_for :evaluators, @colleagues do |builder| %>
<%= render "colleague", :f => builder %>
<% end %>  

Solution to this problem was typecasting this collection to array

<%= f.fields_for :evaluators, @colleagues.to_a do |builder| %>
<%= render "colleague", :f => builder %>
<% end %>  
March 29, 2012 - (v2.3.2 - v3.2.1)
1 thank

JQuery script for dynamically adding and removing fields_for

I like drogus idea. But I wanted a cleaner one, so I created an unobtrusive JQuery script to have the same functionality.

Example Usage:

<%= form_for @post do |form| %>
  Title: <%= form.text_field :title %>
  Body: <%= form.text_field :body %>

  Tags:
  <div id="tag-list"></div>

  <div class="numerous">
    <div class="numerous-form">
      <%= form.fields_for :tag, Tag.new, :child_index => "replace_this" do |f| %>
        <%= f.text_field :name %>
        <%= f.hidden_field :_destroy, :value => 0, :class => "numerous-remove-field" %>
        <%= link_to "delete", "#", :class => "numerous-remove" %>
      <% end %>
    </div>

    <%= link_to "add tag", "#", :class => "numerous-add", :id => "for-tag-list" %>
  </div>
<% end %>

See script at: http://github.com/kbparagua/numerous.js

July 11, 2013
0 thanks

Using fields_for with collection (no association)

accepts_nested_attributes_for has some detractors: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models

But fields_for is defined both on FormBuilder and FormHelper and is still useful even when accepts_nested_attributes_for is not being used. Consider a “to do list” application where we wish to edit and update a list of tasks (Task model). I like to use a TaskCollectionController to manage the actions on the collection, it feels more RESTful than overloading a TaskController:

class TaskCollectionController < ApplicationController
# task_collection_edit GET /task_collection/edit 
  def edit
    @tasks = Task.all
  end

# task_collection_update PUT /task_collection/update
  def update
    # it would be better to use a TaskCollection form object here
    # to make this controller skinny,
    # for the sake of brevity I skipped the form object
    params[:tasks].values.each do |attrs|
      if attrs[:_destroy] == '1'
        Task.find(attrs[:id]).destroy
      elsif attrs[:id].blank?
        Task.create(attrs.slice!(:id,:_destroy))
      else
        Task.find(attrs[:id]).update_attributes(attrs.slice!(:id,:_destroy))
      end
    end
    redirect_to task_collection_edit_path
  end
end

In the edit view, I use fields_for to index added tasks, and it also will insert the id as a hidden field for existing tasks:

%h1 Edit Task Collection
= form_tag task_collection_update_path, :method => :put do
  %table#tasks
    %tr
      %th Name
      %th Priority
    - @tasks.each do |task|
      = fields_for 'tasks[]', task, :hidden_field_id => true do |task_form|
        = render :partial => 'task_form', :locals => {:task_form => task_form}

  = button_tag 'Save'

= button_tag "Add task", :type => :button, :id => :add_task

%script{:type => 'html/template', :id => 'task_form_template'}
  = fields_for 'tasks[]', Task.new, :index => 'NEW_RECORD', :hidden_field_id => true do |task_form| render(:partial => 'task_form', :locals => {:task_form => task_form}); end

:javascript
  $(function(){
    task_form = function(){ return $('#task_form_template').text().replace(/NEW_RECORD/g, new Date().getTime())}
    var add_task = function(){ $('#tasks').append(task_form()) }
    $('#add_task').on('click', add_task)
  })

:hidden_field_id => true triggers the insertion of the id field, and a placeholder index “NEW_RECORD” is replaced by javascript when a task is added, as others have described here. When there’s no association, the index key is :index, vs. :child_index in the case of an association.

Here’s the partial used in the edit view:

%tr
  %td= task_form.text_field :name
  %td= task_form.select :priority, [1,2,3,4,5]
  %td= remove_item(task_form)

The remove_item helper triggers the deletion of persisted tasks:

module TaskCollectionHelper
  def remove_item(form_builder)
    if form_builder.object.new_record?
      # If the task is a new record, remove the div from the dom
      link_to_function( 'Remove', "$(this).closest('tr').remove()")
    else
      # However if it's a "real" record it has to be deleted from the database,
      # hide the form and mark for destruction
      form_builder.hidden_field(:_destroy) +
      link_to_function( 'Remove', "$(this).closest('tr').hide(); $(this).siblings().attr('value',1)")
    end
  end
end