~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb

  • Committer: Michael Forrest
  • Date: 2010-10-15 16:28:50 UTC
  • Revision ID: michael.forrest@canonical.com-20101015162850-tj2vchanv0kr0dun
refrozeĀ gems

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
require 'cgi'
 
2
require 'action_view/helpers/date_helper'
 
3
require 'action_view/helpers/tag_helper'
 
4
require 'action_view/helpers/form_tag_helper'
 
5
 
 
6
module ActionView
 
7
  module Helpers
 
8
    # Form helpers are designed to make working with models much easier
 
9
    # compared to using just standard HTML elements by providing a set of
 
10
    # methods for creating forms based on your models. This helper generates
 
11
    # the HTML for forms, providing a method for each sort of input
 
12
    # (e.g., text, password, select, and so on). When the form is submitted
 
13
    # (i.e., when the user hits the submit button or <tt>form.submit</tt> is
 
14
    # called via JavaScript), the form inputs will be bundled into the
 
15
    # <tt>params</tt> object and passed back to the controller.
 
16
    #
 
17
    # There are two types of form helpers: those that specifically work with
 
18
    # model attributes and those that don't. This helper deals with those that
 
19
    # work with model attributes; to see an example of form helpers that don't
 
20
    # work with model attributes, check the ActionView::Helpers::FormTagHelper
 
21
    # documentation.
 
22
    #
 
23
    # The core method of this helper, form_for, gives you the ability to create
 
24
    # a form for a model instance; for example, let's say that you have a model
 
25
    # <tt>Person</tt> and want to create a new instance of it:
 
26
    #
 
27
    #     # Note: a @person variable will have been created in the controller.
 
28
    #     # For example: @person = Person.new
 
29
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
 
30
    #       <%= f.text_field :first_name %>
 
31
    #       <%= f.text_field :last_name %>
 
32
    #       <%= submit_tag 'Create' %>
 
33
    #     <% end %>
 
34
    #
 
35
    # The HTML generated for this would be:
 
36
    #
 
37
    #     <form action="/persons/create" method="post">
 
38
    #       <input id="person_first_name" name="person[first_name]" size="30" type="text" />
 
39
    #       <input id="person_last_name" name="person[last_name]" size="30" type="text" />
 
40
    #       <input name="commit" type="submit" value="Create" />
 
41
    #     </form>
 
42
    #
 
43
    # If you are using a partial for your form fields, you can use this shortcut:
 
44
    #
 
45
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
 
46
    #       <%= render :partial => f %>
 
47
    #       <%= submit_tag 'Create' %>
 
48
    #     <% end %>
 
49
    #
 
50
    # This example will render the <tt>people/_form</tt> partial, setting a
 
51
    # local variable called <tt>form</tt> which references the yielded
 
52
    # FormBuilder. The <tt>params</tt> object created when this form is
 
53
    # submitted would look like:
 
54
    #
 
55
    #     {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
 
56
    #
 
57
    # The params hash has a nested <tt>person</tt> value, which can therefore
 
58
    # be accessed with <tt>params[:person]</tt> in the controller. If were
 
59
    # editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than
 
60
    # <tt>Person.new</tt> in the controller), the objects attribute values are
 
61
    # filled into the form (e.g., the <tt>person_first_name</tt> field would
 
62
    # have that person's first name in it).
 
63
    #
 
64
    # If the object name contains square brackets the id for the object will be
 
65
    # inserted. For example:
 
66
    #
 
67
    #   <%= text_field "person[]", "name" %>
 
68
    #
 
69
    # ...will generate the following ERb.
 
70
    #
 
71
    #   <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
 
72
    #
 
73
    # If the helper is being used to generate a repetitive sequence of similar
 
74
    # form elements, for example in a partial used by
 
75
    # <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may
 
76
    # come in handy. Example:
 
77
    #
 
78
    #   <%= text_field "person", "name", "index" => 1 %>
 
79
    #
 
80
    # ...becomes...
 
81
    #
 
82
    #   <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
 
83
    #
 
84
    # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and
 
85
    # <tt>fields_for</tt>.  This automatically applies the <tt>index</tt> to
 
86
    # all the nested fields.
 
87
    #
 
88
    # There are also methods for helping to build form tags in
 
89
    # link:classes/ActionView/Helpers/FormOptionsHelper.html,
 
90
    # link:classes/ActionView/Helpers/DateHelper.html, and
 
91
    # link:classes/ActionView/Helpers/ActiveRecordHelper.html
 
92
    module FormHelper
 
93
      # Creates a form and a scope around a specific model object that is used
 
94
      # as a base for questioning about values for the fields.
 
95
      #
 
96
      # Rails provides succinct resource-oriented form generation with +form_for+
 
97
      # like this:
 
98
      #
 
99
      #   <% form_for @offer do |f| %>
 
100
      #     <%= f.label :version, 'Version' %>:
 
101
      #     <%= f.text_field :version %><br />
 
102
      #     <%= f.label :author, 'Author' %>:
 
103
      #     <%= f.text_field :author %><br />
 
104
      #   <% end %>
 
105
      #
 
106
      # There, +form_for+ is able to generate the rest of RESTful form
 
107
      # parameters based on introspection on the record, but to understand what
 
108
      # it does we need to dig first into the alternative generic usage it is
 
109
      # based upon.
 
110
      #
 
111
      # === Generic form_for
 
112
      #
 
113
      # The generic way to call +form_for+ yields a form builder around a
 
114
      # model:
 
115
      #
 
116
      #   <% form_for :person, :url => { :action => "update" } do |f| %>
 
117
      #     <%= f.error_messages %>
 
118
      #     First name: <%= f.text_field :first_name %><br />
 
119
      #     Last name : <%= f.text_field :last_name %><br />
 
120
      #     Biography : <%= f.text_area :biography %><br />
 
121
      #     Admin?    : <%= f.check_box :admin %><br />
 
122
      #   <% end %>
 
123
      #
 
124
      # There, the first argument is a symbol or string with the name of the
 
125
      # object the form is about, and also the name of the instance variable
 
126
      # the object is stored in.
 
127
      #
 
128
      # The form builder acts as a regular form helper that somehow carries the
 
129
      # model. Thus, the idea is that
 
130
      #
 
131
      #   <%= f.text_field :first_name %>
 
132
      #
 
133
      # gets expanded to
 
134
      #
 
135
      #   <%= text_field :person, :first_name %>
 
136
      #
 
137
      # If the instance variable is not <tt>@person</tt> you can pass the actual
 
138
      # record as the second argument:
 
139
      #
 
140
      #   <% form_for :person, person, :url => { :action => "update" } do |f| %>
 
141
      #     ...
 
142
      #   <% end %>
 
143
      #
 
144
      # In that case you can think
 
145
      #
 
146
      #   <%= f.text_field :first_name %>
 
147
      #
 
148
      # gets expanded to
 
149
      #
 
150
      #   <%= text_field :person, :first_name, :object => person %>
 
151
      #
 
152
      # You can even display error messages of the wrapped model this way:
 
153
      #
 
154
      #   <%= f.error_messages %>
 
155
      #
 
156
      # In any of its variants, the rightmost argument to +form_for+ is an
 
157
      # optional hash of options:
 
158
      #
 
159
      # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
 
160
      #   fields you pass to +url_for+ or +link_to+. In particular you may pass
 
161
      #   here a named route directly as well. Defaults to the current action.
 
162
      # * <tt>:html</tt> - Optional HTML attributes for the form tag.
 
163
      #
 
164
      # Worth noting is that the +form_for+ tag is called in a ERb evaluation
 
165
      # block, not an ERb output block. So that's <tt><% %></tt>, not
 
166
      # <tt><%= %></tt>.
 
167
      #
 
168
      # Also note that +form_for+ doesn't create an exclusive scope. It's still
 
169
      # possible to use both the stand-alone FormHelper methods and methods
 
170
      # from FormTagHelper. For example:
 
171
      #
 
172
      #   <% form_for :person, @person, :url => { :action => "update" } do |f| %>
 
173
      #     First name: <%= f.text_field :first_name %>
 
174
      #     Last name : <%= f.text_field :last_name %>
 
175
      #     Biography : <%= text_area :person, :biography %>
 
176
      #     Admin?    : <%= check_box_tag "person[admin]", @person.company.admin? %>
 
177
      #   <% end %>
 
178
      #
 
179
      # This also works for the methods in FormOptionHelper and DateHelper that
 
180
      # are designed to work with an object as base, like
 
181
      # FormOptionHelper#collection_select and DateHelper#datetime_select.
 
182
      #
 
183
      # === Resource-oriented style
 
184
      #
 
185
      # As we said above, in addition to manually configuring the +form_for+
 
186
      # call, you can rely on automated resource identification, which will use
 
187
      # the conventions and named routes of that approach. This is the
 
188
      # preferred way to use +form_for+ nowadays.
 
189
      #
 
190
      # For example, if <tt>@post</tt> is an existing record you want to edit
 
191
      #
 
192
      #   <% form_for @post do |f| %>
 
193
      #     ...
 
194
      #   <% end %>
 
195
      #
 
196
      # is equivalent to something like:
 
197
      #
 
198
      #   <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
 
199
      #     ...
 
200
      #   <% end %>
 
201
      #
 
202
      # And for new records
 
203
      #
 
204
      #   <% form_for(Post.new) do |f| %>
 
205
      #     ...
 
206
      #   <% end %>
 
207
      #
 
208
      # expands to
 
209
      #
 
210
      #   <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
 
211
      #     ...
 
212
      #   <% end %>
 
213
      #
 
214
      # You can also overwrite the individual conventions, like this:
 
215
      #
 
216
      #   <% form_for(@post, :url => super_post_path(@post)) do |f| %>
 
217
      #     ...
 
218
      #   <% end %>
 
219
      #
 
220
      # And for namespaced routes, like +admin_post_url+:
 
221
      #
 
222
      #   <% form_for([:admin, @post]) do |f| %>
 
223
      #    ...
 
224
      #   <% end %>
 
225
      #
 
226
      # === Customized form builders
 
227
      #
 
228
      # You can also build forms using a customized FormBuilder class. Subclass
 
229
      # FormBuilder and override or define some more helpers, then use your
 
230
      # custom builder. For example, let's say you made a helper to
 
231
      # automatically add labels to form inputs.
 
232
      #
 
233
      #   <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
 
234
      #     <%= f.text_field :first_name %>
 
235
      #     <%= f.text_field :last_name %>
 
236
      #     <%= text_area :person, :biography %>
 
237
      #     <%= check_box_tag "person[admin]", @person.company.admin? %>
 
238
      #   <% end %>
 
239
      #
 
240
      # In this case, if you use this:
 
241
      #
 
242
      #   <%= render :partial => f %>
 
243
      #
 
244
      # The rendered template is <tt>people/_labelling_form</tt> and the local
 
245
      # variable referencing the form builder is called
 
246
      # <tt>labelling_form</tt>.
 
247
      #
 
248
      # The custom FormBuilder class is automatically merged with the options
 
249
      # of a nested fields_for call, unless it's explicitely set.
 
250
      #
 
251
      # In many cases you will want to wrap the above in another helper, so you
 
252
      # could do something like the following:
 
253
      #
 
254
      #   def labelled_form_for(record_or_name_or_array, *args, &proc)
 
255
      #     options = args.extract_options!
 
256
      #     form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
 
257
      #   end
 
258
      #
 
259
      # If you don't need to attach a form to a model instance, then check out
 
260
      # FormTagHelper#form_tag.
 
261
      def form_for(record_or_name_or_array, *args, &proc)
 
262
        raise ArgumentError, "Missing block" unless block_given?
 
263
 
 
264
        options = args.extract_options!
 
265
 
 
266
        case record_or_name_or_array
 
267
        when String, Symbol
 
268
          object_name = record_or_name_or_array
 
269
        when Array
 
270
          object = record_or_name_or_array.last
 
271
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
 
272
          apply_form_for_options!(record_or_name_or_array, options)
 
273
          args.unshift object
 
274
        else
 
275
          object = record_or_name_or_array
 
276
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
 
277
          apply_form_for_options!([object], options)
 
278
          args.unshift object
 
279
        end
 
280
 
 
281
        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
 
282
        fields_for(object_name, *(args << options), &proc)
 
283
        concat('</form>'.html_safe!)
 
284
      end
 
285
 
 
286
      def apply_form_for_options!(object_or_array, options) #:nodoc:
 
287
        object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
 
288
 
 
289
        html_options =
 
290
          if object.respond_to?(:new_record?) && object.new_record?
 
291
            { :class  => dom_class(object, :new),  :id => dom_id(object), :method => :post }
 
292
          else
 
293
            { :class  => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
 
294
          end
 
295
 
 
296
        options[:html] ||= {}
 
297
        options[:html].reverse_merge!(html_options)
 
298
        options[:url] ||= polymorphic_path(object_or_array)
 
299
      end
 
300
 
 
301
      # Creates a scope around a specific model object like form_for, but
 
302
      # doesn't create the form tags themselves. This makes fields_for suitable
 
303
      # for specifying additional model objects in the same form.
 
304
      #
 
305
      # === Generic Examples
 
306
      #
 
307
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
308
      #     First name: <%= person_form.text_field :first_name %>
 
309
      #     Last name : <%= person_form.text_field :last_name %>
 
310
      #
 
311
      #     <% fields_for @person.permission do |permission_fields| %>
 
312
      #       Admin?  : <%= permission_fields.check_box :admin %>
 
313
      #     <% end %>
 
314
      #   <% end %>
 
315
      #
 
316
      # ...or if you have an object that needs to be represented as a different
 
317
      # parameter, like a Client that acts as a Person:
 
318
      #
 
319
      #   <% fields_for :person, @client do |permission_fields| %>
 
320
      #     Admin?: <%= permission_fields.check_box :admin %>
 
321
      #   <% end %>
 
322
      #
 
323
      # ...or if you don't have an object, just a name of the parameter:
 
324
      #
 
325
      #   <% fields_for :person do |permission_fields| %>
 
326
      #     Admin?: <%= permission_fields.check_box :admin %>
 
327
      #   <% end %>
 
328
      #
 
329
      # Note: This also works for the methods in FormOptionHelper and
 
330
      # DateHelper that are designed to work with an object as base, like
 
331
      # FormOptionHelper#collection_select and DateHelper#datetime_select.
 
332
      #
 
333
      # === Nested Attributes Examples
 
334
      #
 
335
      # When the object belonging to the current scope has a nested attribute
 
336
      # writer for a certain attribute, fields_for will yield a new scope
 
337
      # for that attribute. This allows you to create forms that set or change
 
338
      # the attributes of a parent object and its associations in one go.
 
339
      #
 
340
      # Nested attribute writers are normal setter methods named after an
 
341
      # association. The most common way of defining these writers is either
 
342
      # with +accepts_nested_attributes_for+ in a model definition or by
 
343
      # defining a method with the proper name. For example: the attribute
 
344
      # writer for the association <tt>:address</tt> is called
 
345
      # <tt>address_attributes=</tt>.
 
346
      #
 
347
      # Whether a one-to-one or one-to-many style form builder will be yielded
 
348
      # depends on whether the normal reader method returns a _single_ object
 
349
      # or an _array_ of objects.
 
350
      #
 
351
      # ==== One-to-one
 
352
      #
 
353
      # Consider a Person class which returns a _single_ Address from the
 
354
      # <tt>address</tt> reader method and responds to the
 
355
      # <tt>address_attributes=</tt> writer method:
 
356
      #
 
357
      #   class Person
 
358
      #     def address
 
359
      #       @address
 
360
      #     end
 
361
      #
 
362
      #     def address_attributes=(attributes)
 
363
      #       # Process the attributes hash
 
364
      #     end
 
365
      #   end
 
366
      #
 
367
      # This model can now be used with a nested fields_for, like so:
 
368
      #
 
369
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
370
      #     ...
 
371
      #     <% person_form.fields_for :address do |address_fields| %>
 
372
      #       Street  : <%= address_fields.text_field :street %>
 
373
      #       Zip code: <%= address_fields.text_field :zip_code %>
 
374
      #     <% end %>
 
375
      #   <% end %>
 
376
      #
 
377
      # When address is already an association on a Person you can use
 
378
      # +accepts_nested_attributes_for+ to define the writer method for you:
 
379
      #
 
380
      #   class Person < ActiveRecord::Base
 
381
      #     has_one :address
 
382
      #     accepts_nested_attributes_for :address
 
383
      #   end
 
384
      #
 
385
      # If you want to destroy the associated model through the form, you have
 
386
      # to enable it first using the <tt>:allow_destroy</tt> option for
 
387
      # +accepts_nested_attributes_for+:
 
388
      #
 
389
      #   class Person < ActiveRecord::Base
 
390
      #     has_one :address
 
391
      #     accepts_nested_attributes_for :address, :allow_destroy => true
 
392
      #   end
 
393
      #
 
394
      # Now, when you use a form element with the <tt>_delete</tt> parameter,
 
395
      # with a value that evaluates to +true+, you will destroy the associated
 
396
      # model (eg. 1, '1', true, or 'true'):
 
397
      #
 
398
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
399
      #     ...
 
400
      #     <% person_form.fields_for :address do |address_fields| %>
 
401
      #       ...
 
402
      #       Delete: <%= address_fields.check_box :_delete %>
 
403
      #     <% end %>
 
404
      #   <% end %>
 
405
      #
 
406
      # ==== One-to-many
 
407
      #
 
408
      # Consider a Person class which returns an _array_ of Project instances
 
409
      # from the <tt>projects</tt> reader method and responds to the
 
410
      # <tt>projects_attributes=</tt> writer method:
 
411
      #
 
412
      #   class Person
 
413
      #     def projects
 
414
      #       [@project1, @project2]
 
415
      #     end
 
416
      #
 
417
      #     def projects_attributes=(attributes)
 
418
      #       # Process the attributes hash
 
419
      #     end
 
420
      #   end
 
421
      #
 
422
      # This model can now be used with a nested fields_for. The block given to
 
423
      # the nested fields_for call will be repeated for each instance in the
 
424
      # collection:
 
425
      #
 
426
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
427
      #     ...
 
428
      #     <% person_form.fields_for :projects do |project_fields| %>
 
429
      #       <% if project_fields.object.active? %>
 
430
      #         Name: <%= project_fields.text_field :name %>
 
431
      #       <% end %>
 
432
      #     <% end %>
 
433
      #   <% end %>
 
434
      #
 
435
      # It's also possible to specify the instance to be used:
 
436
      #
 
437
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
438
      #     ...
 
439
      #     <% @person.projects.each do |project| %>
 
440
      #       <% if project.active? %>
 
441
      #         <% person_form.fields_for :projects, project do |project_fields| %>
 
442
      #           Name: <%= project_fields.text_field :name %>
 
443
      #         <% end %>
 
444
      #       <% end %>
 
445
      #     <% end %>
 
446
      #   <% end %>
 
447
      #
 
448
      # Or a collection to be used:
 
449
      #
 
450
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
451
      #     ...
 
452
      #     <% person_form.fields_for :projects, @active_projects do |project_fields| %>
 
453
      #       Name: <%= project_fields.text_field :name %>
 
454
      #     <% end %>
 
455
      #   <% end %>
 
456
      #
 
457
      # When projects is already an association on Person you can use
 
458
      # +accepts_nested_attributes_for+ to define the writer method for you:
 
459
      #
 
460
      #   class Person < ActiveRecord::Base
 
461
      #     has_many :projects
 
462
      #     accepts_nested_attributes_for :projects
 
463
      #   end
 
464
      #
 
465
      # If you want to destroy any of the associated models through the
 
466
      # form, you have to enable it first using the <tt>:allow_destroy</tt>
 
467
      # option for +accepts_nested_attributes_for+:
 
468
      #
 
469
      #   class Person < ActiveRecord::Base
 
470
      #     has_many :projects
 
471
      #     accepts_nested_attributes_for :projects, :allow_destroy => true
 
472
      #   end
 
473
      #
 
474
      # This will allow you to specify which models to destroy in the
 
475
      # attributes hash by adding a form element for the <tt>_delete</tt>
 
476
      # parameter with a value that evaluates to +true+
 
477
      # (eg. 1, '1', true, or 'true'):
 
478
      #
 
479
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
 
480
      #     ...
 
481
      #     <% person_form.fields_for :projects do |project_fields| %>
 
482
      #       Delete: <%= project_fields.check_box :_delete %>
 
483
      #     <% end %>
 
484
      #   <% end %>
 
485
      def fields_for(record_or_name_or_array, *args, &block)
 
486
        raise ArgumentError, "Missing block" unless block_given?
 
487
        options = args.extract_options!
 
488
 
 
489
        case record_or_name_or_array
 
490
        when String, Symbol
 
491
          object_name = record_or_name_or_array
 
492
          object = args.first
 
493
        else
 
494
          object = record_or_name_or_array
 
495
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
 
496
        end
 
497
 
 
498
        builder = options[:builder] || ActionView::Base.default_form_builder
 
499
        yield builder.new(object_name, object, self, options, block)
 
500
      end
 
501
 
 
502
      # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
 
503
      # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
 
504
      # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
 
505
      # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
 
506
      # target labels for radio_button tags (where the value is used in the ID of the input tag).
 
507
      #
 
508
      # ==== Examples
 
509
      #   label(:post, :title)
 
510
      #   # => <label for="post_title">Title</label>
 
511
      #
 
512
      #   label(:post, :title, "A short title")
 
513
      #   # => <label for="post_title">A short title</label>
 
514
      #
 
515
      #   label(:post, :title, "A short title", :class => "title_label")
 
516
      #   # => <label for="post_title" class="title_label">A short title</label>
 
517
      #
 
518
      #   label(:post, :privacy, "Public Post", :value => "public")
 
519
      #   # => <label for="post_privacy_public">Public Post</label>
 
520
      #
 
521
      def label(object_name, method, text = nil, options = {})
 
522
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
 
523
      end
 
524
 
 
525
      # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
 
526
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
 
527
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
 
528
      # shown.
 
529
      #
 
530
      # ==== Examples
 
531
      #   text_field(:post, :title, :size => 20)
 
532
      #   # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
 
533
      #
 
534
      #   text_field(:post, :title, :class => "create_input")
 
535
      #   # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
 
536
      #
 
537
      #   text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
 
538
      #   # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/>
 
539
      #
 
540
      #   text_field(:snippet, :code, :size => 20, :class => 'code_input')
 
541
      #   # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
 
542
      #
 
543
      def text_field(object_name, method, options = {})
 
544
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
 
545
      end
 
546
 
 
547
      # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
 
548
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
 
549
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
 
550
      # shown.
 
551
      #
 
552
      # ==== Examples
 
553
      #   password_field(:login, :pass, :size => 20)
 
554
      #   # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
 
555
      #
 
556
      #   password_field(:account, :secret, :class => "form_input")
 
557
      #   # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
 
558
      #
 
559
      #   password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
 
560
      #   # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
 
561
      #
 
562
      #   password_field(:account, :pin, :size => 20, :class => 'form_input')
 
563
      #   # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
 
564
      #
 
565
      def password_field(object_name, method, options = {})
 
566
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
 
567
      end
 
568
 
 
569
      # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
 
570
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
 
571
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
 
572
      # shown.
 
573
      #
 
574
      # ==== Examples
 
575
      #   hidden_field(:signup, :pass_confirm)
 
576
      #   # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
 
577
      #
 
578
      #   hidden_field(:post, :tag_list)
 
579
      #   # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
 
580
      #
 
581
      #   hidden_field(:user, :token)
 
582
      #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
 
583
      def hidden_field(object_name, method, options = {})
 
584
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
 
585
      end
 
586
 
 
587
      # Returns an file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
 
588
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
 
589
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
 
590
      # shown.
 
591
      #
 
592
      # ==== Examples
 
593
      #   file_field(:user, :avatar)
 
594
      #   # => <input type="file" id="user_avatar" name="user[avatar]" />
 
595
      #
 
596
      #   file_field(:post, :attached, :accept => 'text/html')
 
597
      #   # => <input type="file" id="post_attached" name="post[attached]" />
 
598
      #
 
599
      #   file_field(:attachment, :file, :class => 'file_input')
 
600
      #   # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
 
601
      #
 
602
      def file_field(object_name, method, options = {})
 
603
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
 
604
      end
 
605
 
 
606
      # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
 
607
      # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
 
608
      # hash with +options+.
 
609
      #
 
610
      # ==== Examples
 
611
      #   text_area(:post, :body, :cols => 20, :rows => 40)
 
612
      #   # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
 
613
      #   #      #{@post.body}
 
614
      #   #    </textarea>
 
615
      #
 
616
      #   text_area(:comment, :text, :size => "20x30")
 
617
      #   # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
 
618
      #   #      #{@comment.text}
 
619
      #   #    </textarea>
 
620
      #
 
621
      #   text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
 
622
      #   # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
 
623
      #   #      #{@application.notes}
 
624
      #   #    </textarea>
 
625
      #
 
626
      #   text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
 
627
      #   # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
 
628
      #   #      #{@entry.body}
 
629
      #   #    </textarea>
 
630
      def text_area(object_name, method, options = {})
 
631
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
 
632
      end
 
633
 
 
634
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
 
635
      # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
 
636
      # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. 
 
637
      # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 
 
638
      # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
 
639
      #
 
640
      # ==== Gotcha
 
641
      #
 
642
      # The HTML specification says unchecked check boxes are not successful, and
 
643
      # thus web browsers do not send them. Unfortunately this introduces a gotcha:
 
644
      # if an Invoice model has a +paid+ flag, and in the form that edits a paid
 
645
      # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
 
646
      # any mass-assignment idiom like
 
647
      #
 
648
      #   @invoice.update_attributes(params[:invoice])
 
649
      #
 
650
      # wouldn't update the flag.
 
651
      #
 
652
      # To prevent this the helper generates a hidden field with the same name as
 
653
      # the checkbox after the very check box. So, the client either sends only the
 
654
      # hidden field (representing the check box is unchecked), or both fields.
 
655
      # Since the HTML specification says key/value pairs have to be sent in the
 
656
      # same order they appear in the form and Rails parameters extraction always
 
657
      # gets the first occurrence of any given key, that works in ordinary forms.
 
658
      #
 
659
      # Unfortunately that workaround does not work when the check box goes
 
660
      # within an array-like parameter, as in
 
661
      #
 
662
      #   <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
 
663
      #     <%= form.check_box :paid %>
 
664
      #     ...
 
665
      #   <% end %>
 
666
      #
 
667
      # because parameter name repetition is precisely what Rails seeks to distinguish
 
668
      # the elements of the array.
 
669
      #
 
670
      # ==== Examples
 
671
      #   # Let's say that @post.validated? is 1:
 
672
      #   check_box("post", "validated")
 
673
      #   # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
 
674
      #   #    <input name="post[validated]" type="hidden" value="0" />
 
675
      #
 
676
      #   # Let's say that @puppy.gooddog is "no":
 
677
      #   check_box("puppy", "gooddog", {}, "yes", "no")
 
678
      #   # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
 
679
      #   #    <input name="puppy[gooddog]" type="hidden" value="no" />
 
680
      #
 
681
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
 
682
      #   # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
 
683
      #   #    <input name="eula[accepted]" type="hidden" value="no" />
 
684
      #
 
685
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
 
686
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
 
687
      end
 
688
 
 
689
      # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
 
690
      # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
 
691
      # radio button will be checked.
 
692
      #
 
693
      # To force the radio button to be checked pass <tt>:checked => true</tt> in the
 
694
      # +options+ hash. You may pass HTML options there as well.
 
695
      #
 
696
      # ==== Examples
 
697
      #   # Let's say that @post.category returns "rails":
 
698
      #   radio_button("post", "category", "rails")
 
699
      #   radio_button("post", "category", "java")
 
700
      #   # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
 
701
      #   #    <input type="radio" id="post_category_java" name="post[category]" value="java" />
 
702
      #
 
703
      #   radio_button("user", "receive_newsletter", "yes")
 
704
      #   radio_button("user", "receive_newsletter", "no")
 
705
      #   # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
 
706
      #   #    <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
 
707
      def radio_button(object_name, method, tag_value, options = {})
 
708
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
 
709
      end
 
710
    end
 
711
 
 
712
    class InstanceTag #:nodoc:
 
713
      include Helpers::TagHelper, Helpers::FormTagHelper
 
714
 
 
715
      attr_reader :method_name, :object_name
 
716
 
 
717
      DEFAULT_FIELD_OPTIONS     = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
 
718
      DEFAULT_RADIO_OPTIONS     = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
 
719
      DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
 
720
 
 
721
      def initialize(object_name, method_name, template_object, object = nil)
 
722
        @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
 
723
        @template_object = template_object
 
724
        @object = object
 
725
        if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
 
726
          if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
 
727
            @auto_index = object.to_param
 
728
          else
 
729
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
 
730
          end
 
731
        end
 
732
      end
 
733
 
 
734
      def to_label_tag(text = nil, options = {})
 
735
        options = options.stringify_keys
 
736
        tag_value = options.delete("value")
 
737
        name_and_id = options.dup
 
738
        name_and_id["id"] = name_and_id["for"]
 
739
        add_default_name_and_id_for_value(tag_value, name_and_id)
 
740
        options.delete("index")
 
741
        options["for"] ||= name_and_id["id"]
 
742
        content = (text.blank? ? nil : text.to_s) || method_name.humanize
 
743
        label_tag(name_and_id["id"], content, options)
 
744
      end
 
745
 
 
746
      def to_input_field_tag(field_type, options = {})
 
747
        options = options.stringify_keys
 
748
        options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
 
749
        options = DEFAULT_FIELD_OPTIONS.merge(options)
 
750
        if field_type == "hidden"
 
751
          options.delete("size")
 
752
        end
 
753
        options["type"] = field_type
 
754
        options["value"] ||= value_before_type_cast(object) unless field_type == "file"
 
755
        options["value"] &&= html_escape(options["value"])
 
756
        add_default_name_and_id(options)
 
757
        tag("input", options)
 
758
      end
 
759
 
 
760
      def to_radio_button_tag(tag_value, options = {})
 
761
        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
 
762
        options["type"]     = "radio"
 
763
        options["value"]    = tag_value
 
764
        if options.has_key?("checked")
 
765
          cv = options.delete "checked"
 
766
          checked = cv == true || cv == "checked"
 
767
        else
 
768
          checked = self.class.radio_button_checked?(value(object), tag_value)
 
769
        end
 
770
        options["checked"]  = "checked" if checked
 
771
        add_default_name_and_id_for_value(tag_value, options)
 
772
        tag("input", options)
 
773
      end
 
774
 
 
775
      def to_text_area_tag(options = {})
 
776
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
 
777
        add_default_name_and_id(options)
 
778
 
 
779
        if size = options.delete("size")
 
780
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
 
781
        end
 
782
 
 
783
        content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
 
784
      end
 
785
 
 
786
      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
 
787
        options = options.stringify_keys
 
788
        options["type"]     = "checkbox"
 
789
        options["value"]    = checked_value
 
790
        if options.has_key?("checked")
 
791
          cv = options.delete "checked"
 
792
          checked = cv == true || cv == "checked"
 
793
        else
 
794
          checked = self.class.check_box_checked?(value(object), checked_value)
 
795
        end
 
796
        options["checked"] = "checked" if checked
 
797
        add_default_name_and_id(options)
 
798
        hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
 
799
        checkbox = tag("input", options)
 
800
        (hidden + checkbox).html_safe!
 
801
      end
 
802
 
 
803
      def to_boolean_select_tag(options = {})
 
804
        options = options.stringify_keys
 
805
        add_default_name_and_id(options)
 
806
        value = value(object)
 
807
        tag_text = "<select"
 
808
        tag_text << tag_options(options)
 
809
        tag_text << "><option value=\"false\""
 
810
        tag_text << " selected" if value == false
 
811
        tag_text << ">False</option><option value=\"true\""
 
812
        tag_text << " selected" if value
 
813
        tag_text << ">True</option></select>"
 
814
      end
 
815
 
 
816
      def to_content_tag(tag_name, options = {})
 
817
        content_tag(tag_name, value(object), options)
 
818
      end
 
819
 
 
820
      def object
 
821
        @object || @template_object.instance_variable_get("@#{@object_name}")
 
822
      rescue NameError
 
823
        # As @object_name may contain the nested syntax (item[subobject]) we
 
824
        # need to fallback to nil.
 
825
        nil
 
826
      end
 
827
 
 
828
      def value(object)
 
829
        self.class.value(object, @method_name)
 
830
      end
 
831
 
 
832
      def value_before_type_cast(object)
 
833
        self.class.value_before_type_cast(object, @method_name)
 
834
      end
 
835
 
 
836
      class << self
 
837
        def value(object, method_name)
 
838
          object.send method_name unless object.nil?
 
839
        end
 
840
 
 
841
        def value_before_type_cast(object, method_name)
 
842
          unless object.nil?
 
843
            object.respond_to?(method_name + "_before_type_cast") ?
 
844
            object.send(method_name + "_before_type_cast") :
 
845
            object.send(method_name)
 
846
          end
 
847
        end
 
848
 
 
849
        def check_box_checked?(value, checked_value)
 
850
          case value
 
851
          when TrueClass, FalseClass
 
852
            value
 
853
          when NilClass
 
854
            false
 
855
          when Integer
 
856
            value != 0
 
857
          when String
 
858
            value == checked_value
 
859
          when Array
 
860
            value.include?(checked_value)
 
861
          else
 
862
            value.to_i != 0
 
863
          end
 
864
        end
 
865
 
 
866
        def radio_button_checked?(value, checked_value)
 
867
          value.to_s == checked_value.to_s
 
868
        end
 
869
      end
 
870
 
 
871
      private
 
872
        def add_default_name_and_id_for_value(tag_value, options)
 
873
          unless tag_value.nil?
 
874
            pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
 
875
            specified_id = options["id"]
 
876
            add_default_name_and_id(options)
 
877
            options["id"] += "_#{pretty_tag_value}" unless specified_id
 
878
          else
 
879
            add_default_name_and_id(options)
 
880
          end
 
881
        end
 
882
 
 
883
        def add_default_name_and_id(options)
 
884
          if options.has_key?("index")
 
885
            options["name"] ||= tag_name_with_index(options["index"])
 
886
            options["id"]   ||= tag_id_with_index(options["index"])
 
887
            options.delete("index")
 
888
          elsif defined?(@auto_index)
 
889
            options["name"] ||= tag_name_with_index(@auto_index)
 
890
            options["id"]   ||= tag_id_with_index(@auto_index)
 
891
          else
 
892
            options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
 
893
            options["id"]   ||= tag_id
 
894
          end
 
895
        end
 
896
 
 
897
        def tag_name
 
898
          "#{@object_name}[#{sanitized_method_name}]"
 
899
        end
 
900
 
 
901
        def tag_name_with_index(index)
 
902
          "#{@object_name}[#{index}][#{sanitized_method_name}]"
 
903
        end
 
904
 
 
905
        def tag_id
 
906
          "#{sanitized_object_name}_#{sanitized_method_name}"
 
907
        end
 
908
 
 
909
        def tag_id_with_index(index)
 
910
          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
 
911
        end
 
912
 
 
913
        def sanitized_object_name
 
914
          @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
 
915
        end
 
916
 
 
917
        def sanitized_method_name
 
918
          @sanitized_method_name ||= @method_name.sub(/\?$/,"")
 
919
        end
 
920
    end
 
921
 
 
922
    class FormBuilder #:nodoc:
 
923
      # The methods which wrap a form helper call.
 
924
      class_inheritable_accessor :field_helpers
 
925
      self.field_helpers = (FormHelper.instance_methods - ['form_for'])
 
926
 
 
927
      attr_accessor :object_name, :object, :options
 
928
 
 
929
      def initialize(object_name, object, template, options, proc)
 
930
        @nested_child_index = {}
 
931
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
 
932
        @default_options = @options ? @options.slice(:index) : {}
 
933
        if @object_name.to_s.match(/\[\]$/)
 
934
          if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
 
935
            @auto_index = object.to_param
 
936
          else
 
937
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
 
938
          end
 
939
        end
 
940
      end
 
941
 
 
942
      (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
 
943
        src = <<-end_src
 
944
          def #{selector}(method, options = {})  # def text_field(method, options = {})
 
945
            @template.send(                      #   @template.send(
 
946
              #{selector.inspect},               #     "text_field",
 
947
              @object_name,                      #     @object_name,
 
948
              method,                            #     method,
 
949
              objectify_options(options))        #     objectify_options(options))
 
950
          end                                    # end
 
951
        end_src
 
952
        class_eval src, __FILE__, __LINE__
 
953
      end
 
954
 
 
955
      def fields_for(record_or_name_or_array, *args, &block)
 
956
        if options.has_key?(:index)
 
957
          index = "[#{options[:index]}]"
 
958
        elsif defined?(@auto_index)
 
959
          self.object_name = @object_name.to_s.sub(/\[\]$/,"")
 
960
          index = "[#{@auto_index}]"
 
961
        else
 
962
          index = ""
 
963
        end
 
964
 
 
965
        if options[:builder]
 
966
          args << {} unless args.last.is_a?(Hash)
 
967
          args.last[:builder] ||= options[:builder]
 
968
        end
 
969
 
 
970
        case record_or_name_or_array
 
971
        when String, Symbol
 
972
          if nested_attributes_association?(record_or_name_or_array)
 
973
            return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
 
974
          else
 
975
            name = "#{object_name}#{index}[#{record_or_name_or_array}]"
 
976
          end
 
977
        when Array
 
978
          object = record_or_name_or_array.last
 
979
          name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
 
980
          args.unshift(object)
 
981
        else
 
982
          object = record_or_name_or_array
 
983
          name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
 
984
          args.unshift(object)
 
985
        end
 
986
 
 
987
        @template.fields_for(name, *args, &block)
 
988
      end
 
989
 
 
990
      def label(method, text = nil, options = {})
 
991
        @template.label(@object_name, method, text, objectify_options(options))
 
992
      end
 
993
 
 
994
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
 
995
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
 
996
      end
 
997
 
 
998
      def radio_button(method, tag_value, options = {})
 
999
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
 
1000
      end
 
1001
      
 
1002
      def hidden_field(method, options = {})
 
1003
        @emitted_hidden_id = true if method == :id
 
1004
        @template.hidden_field(@object_name, method, objectify_options(options))
 
1005
      end
 
1006
 
 
1007
      def error_message_on(method, *args)
 
1008
        @template.error_message_on(@object, method, *args)
 
1009
      end
 
1010
 
 
1011
      def error_messages(options = {})
 
1012
        @template.error_messages_for(@object_name, objectify_options(options))
 
1013
      end
 
1014
 
 
1015
      def submit(value = "Save changes", options = {})
 
1016
        @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
 
1017
      end
 
1018
 
 
1019
      def emitted_hidden_id?
 
1020
        @emitted_hidden_id
 
1021
      end
 
1022
 
 
1023
      private
 
1024
        def objectify_options(options)
 
1025
          @default_options.merge(options.merge(:object => @object))
 
1026
        end
 
1027
 
 
1028
        def nested_attributes_association?(association_name)
 
1029
          @object.respond_to?("#{association_name}_attributes=")
 
1030
        end
 
1031
 
 
1032
        def fields_for_with_nested_attributes(association_name, args, block)
 
1033
          name = "#{object_name}[#{association_name}_attributes]"
 
1034
          association = args.first
 
1035
 
 
1036
          if association.respond_to?(:new_record?)
 
1037
            association = [association] if @object.send(association_name).is_a?(Array)
 
1038
          elsif !association.is_a?(Array)
 
1039
            association = @object.send(association_name)
 
1040
          end
 
1041
 
 
1042
          if association.is_a?(Array)
 
1043
            explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
 
1044
            association.map do |child|
 
1045
              fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
 
1046
            end.join
 
1047
          elsif association
 
1048
            fields_for_nested_model(name, association, args, block)
 
1049
          end
 
1050
        end
 
1051
 
 
1052
        def fields_for_nested_model(name, object, args, block)
 
1053
          if object.new_record?
 
1054
            @template.fields_for(name, object, *args, &block)
 
1055
          else
 
1056
            @template.fields_for(name, object, *args) do |builder|
 
1057
              block.call(builder)
 
1058
              @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id?
 
1059
            end
 
1060
          end
 
1061
        end
 
1062
 
 
1063
        def nested_child_index(name)
 
1064
          @nested_child_index[name] ||= -1
 
1065
          @nested_child_index[name] += 1
 
1066
        end
 
1067
    end
 
1068
  end
 
1069
 
 
1070
  class Base
 
1071
    cattr_accessor :default_form_builder
 
1072
    self.default_form_builder = ::ActionView::Helpers::FormBuilder
 
1073
  end
 
1074
end