Created
June 23, 2009 16:24
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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