Skip to content

Instantly share code, notes, and snippets.

@schorsch
Created June 23, 2009 16:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save schorsch/134652 to your computer and use it in GitHub Desktop.
Save schorsch/134652 to your computer and use it in GitHub Desktop.
Rails Form helper to ease the rendering of nested_forms via accepts_nested_attributes_for
module KingForm
# Method for building forms that contain fields for associated models.
module NestedFormHelper
# Renders the form for nested objects defined via activerecord accepts_nested_attributes_for
#
# The associated argument can be either an object, or a collection of objects to be rendered.
#
# An options hash can be specified to override the default behaviors.
#
# Options are:
# * <tt>:new</tt> - specify a certain number of new elements to be added to the form. Useful for displaying a
# few blank elements at the bottom.
# * <tt>:name</tt> - override the name of the association, both for the field names, and the name of the partial
# * <tt>:partial</tt> - specify the name of the partial in which the form is located.
# * <tt>:fields_for</tt> - specify additional options for the fields_for_associated call
# * <tt>:locals</tt> - specify additional variables to be passed along to the partial
# * <tt>:render</tt> - specify additional options to be passed along to the render :partial call
# * <tt>:skip</tt> - array of elements which will be skipped, usefull if you already rendered a partial in the same form with parts of the data.
# eg. obj.addresses, render the firt address on top of form, render all the other addresses at the bottom
#
def render_nested_form(associated, opts = {})
associated = associated.is_a?(Array) ? associated : [associated] # preserve association proxy if this is one
opts.symbolize_keys!
(opts[:new] - associated.select(&:new_record?).length).times { associated.build } if opts[:new]
unless associated.empty?
name = extract_option_or_class_name(opts, :name, associated.first)
partial = opts[:partial] || name
if opts[:skip] # objects to be skipped are present
skip_el = opts[:skip].is_a?(Array) ? opts[:skip] : [opts[:skip]]
assoc_el = []
associated.each { |el| assoc_el << el unless skip_el.include?(el) }
else # normal procedure
assoc_el = associated
end
output = assoc_el.map do |element|
fields_for(association_name(name), element, (opts[:fields_for] || {}).merge(:name => name)) do |f|
@template.render( {:partial => "#{partial}",
#The current objects classname is always present in partial so:
#when Object is LineItem locals has :line_item => object
:locals => {name.to_sym => f.object, :f => f}.merge( opts[:locals] || {} )
}.merge( opts[:render] || {} ) )
end
end
output.join
end
end
private
def association_name(class_name)
@object.respond_to?("#{class_name}_attributes=") ? class_name : class_name.pluralize
end
def extract_option_or_class_name(hash, option, object)
(hash.delete(option) || object.class.name.split('::').last.underscore).to_s
end
end
end
# include the nested_form helper method on the very top of FormBuilder
ActionView::Helpers::FormBuilder.class_eval { include KingForm::NestedFormHelper }
=begin
== Example Nested Forms
Put the above module and helper include somewhere in your rails instance (plugin/lib / ..)
The first thing you need to do is enable attributes on the association in the model.
class Project < ActiveRecord::Base
has_many :tasks
accepts_nested_attributes_for :tasks
end
The partial for the nested object(one Task) should have the name of the associated element's type( or the partial to use)
## projects/_task.html.erb
<div class="task">
<label>Title</label>
<%= f.text_field :title %>
</div>
In the parent element's form (Project), call the render_nested_form method, with the collection of
elements you'd like to render as the first argument.
## projects/_form.html.erb
<%= f.render_nested_form(@project.tasks) %>
That call will render the partial named _task.html.erb with each element in the supplied collection of tasks, wrapping
the partial in a form builder (fields_for) with all the necessary arguments to produce a hash that will satisfy the
tasks_attributes method(provided by Rails new accepts_nested_attributes_for).
=== Options
See comments in nested_form_helper module for full reference of options.
You may want to add a few blank tasks to the bottom of your form; no need to do that in the controller anymore.
<%= f.render_nested_form(@project.tasks, :new => 3, :partial=>'some_partial', :locals=>..) %>
=end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment