schorsch (owner)

Forks

Revisions

  • 593d82 schorsch Wed Jun 24 06:05:53 -0700 2009
  • 472bc9 Tue Jun 23 09:24:57 -0700 2009
gist: 134652 Download_button fork
public
Description:
Rails Form helper to ease the rendering of nested_forms via accepts_nested_attributes_for
Public Clone URL: git://gist.github.com/134652.git
Embed All Files: show embed
Ruby #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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