fields_for
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:
Examples
<% form_for @person, :url => { :action => "update" } 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.
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.
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' } } }
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 } %>
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
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 %>
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


