form_helper.rb 56.8 KB
Newer Older
D
Initial  
David Heinemeier Hansson 已提交
1
require 'cgi'
2 3
require 'action_view/helpers/date_helper'
require 'action_view/helpers/tag_helper'
4
require 'action_view/helpers/form_tag_helper'
5
require 'action_view/helpers/active_model_helper'
R
Rafael Mendonça França 已提交
6
require 'action_view/helpers/tags'
7
require 'active_support/core_ext/class/attribute'
8
require 'active_support/core_ext/hash/slice'
9
require 'active_support/core_ext/object/blank'
10
require 'active_support/core_ext/string/output_safety'
11
require 'active_support/core_ext/array/extract_options'
12
require 'active_support/deprecation'
D
Initial  
David Heinemeier Hansson 已提交
13 14

module ActionView
15
  # = Action View Form Helpers
D
Initial  
David Heinemeier Hansson 已提交
16
  module Helpers
17 18 19
    # Form helpers are designed to make working with resources much easier
    # compared to using vanilla HTML.
    #
20 21 22 23
    # Typically, a form designed to create or update a resource reflects the
    # identity of the resource in several ways: (i) the url that the form is
    # sent to (the form element's +action+ attribute) should result in a request
    # being routed to the appropriate controller action (with the appropriate <tt>:id</tt>
24
    # parameter in the case of an existing resource), (ii) input fields should
25
    # be named in such a way that in the controller their values appear in the
26
    # appropriate places within the +params+ hash, and (iii) for an existing record,
27 28
    # when the form is initially displayed, input fields corresponding to attributes
    # of the resource should show the current values of those attributes.
29
    #
30 31 32 33 34 35 36 37
    # In Rails, this is usually achieved by creating the form using +form_for+ and
    # a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt>
    # tag and yields a form builder object that knows the model the form is about.
    # Input fields are created by calling methods defined on the form builder, which
    # means they are able to generate the appropriate names and default values
    # corresponding to the model attributes, as well as convenient IDs, etc.
    # Conventions in the generated field names allow controllers to receive form data
    # nicely structured in +params+ with no effort on your side.
38
    #
39 40
    # For example, to create a new person you typically set up a new instance of
    # +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and
41
    # in the view template pass that object to +form_for+:
42 43
    #
    #   <%= form_for @person do |f| %>
44 45 46 47 48 49
    #     <%= f.label :first_name %>:
    #     <%= f.text_field :first_name %><br />
    #
    #     <%= f.label :last_name %>:
    #     <%= f.text_field :last_name %><br />
    #
50 51 52 53 54 55 56 57 58
    #     <%= f.submit %>
    #   <% end %>
    #
    # The HTML generated for this would be (modulus formatting):
    #
    #   <form action="/people" class="new_person" id="new_person" method="post">
    #     <div style="margin:0;padding:0;display:inline">
    #       <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
    #     </div>
59
    #     <label for="person_first_name">First name</label>:
60
    #     <input id="person_first_name" name="person[first_name]" type="text" /><br />
61 62
    #
    #     <label for="person_last_name">Last name</label>:
63
    #     <input id="person_last_name" name="person[last_name]" type="text" /><br />
64
    #
65
    #     <input name="commit" type="submit" value="Create Person" />
66 67
    #   </form>
    #
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
    # As you see, the HTML reflects knowledge about the resource in several spots,
    # like the path the form should be submitted to, or the names of the input fields.
    #
    # In particular, thanks to the conventions followed in the generated field names, the
    # controller gets a nested hash <tt>params[:person]</tt> with the person attributes
    # set in the form. That hash is ready to be passed to <tt>Person.create</tt>:
    #
    #   if @person = Person.create(params[:person])
    #     # success
    #   else
    #     # error handling
    #   end
    #
    # Interestingly, the exact same view code in the previous example can be used to edit
    # a person. If <tt>@person</tt> is an existing record with name "John Smith" and ID 256,
    # the code above as is would yield instead:
    #
85
    #   <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
86 87 88 89 90
    #     <div style="margin:0;padding:0;display:inline">
    #       <input name="_method" type="hidden" value="put" />
    #       <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" />
    #     </div>
    #     <label for="person_first_name">First name</label>:
91
    #     <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br />
92 93
    #
    #     <label for="person_last_name">Last name</label>:
94
    #     <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br />
95
    #
96
    #     <input name="commit" type="submit" value="Update Person" />
97 98 99 100 101 102 103 104
    #   </form>
    #
    # Note that the endpoint, default values, and submit button label are tailored for <tt>@person</tt>.
    # That works that way because the involved helpers know whether the resource is a new record or not,
    # and generate HTML accordingly.
    #
    # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be
    # passed to <tt>Person#update_attributes</tt>:
105
    #
106
    #   if @person.update_attributes(params[:person])
107 108 109 110 111
    #     # success
    #   else
    #     # error handling
    #   end
    #
112
    # That's how you typically work with resources.
D
Initial  
David Heinemeier Hansson 已提交
113
    module FormHelper
W
wycats 已提交
114 115 116
      extend ActiveSupport::Concern

      include FormTagHelper
117
      include UrlHelper
W
wycats 已提交
118

J
José Valim 已提交
119 120 121 122 123
      # Converts the given object to an ActiveModel compliant one.
      def convert_to_model(object)
        object.respond_to?(:to_model) ? object.to_model : object
      end

124 125
      # Creates a form that allows the user to create or update the attributes
      # of a specific model object.
126
      #
127 128 129 130 131
      # The method can be used in several slightly different ways, depending on
      # how much you wish to rely on Rails to infer automatically from the model
      # how the form should be constructed. For a generic model object, a form
      # can be created by passing +form_for+ a string or symbol representing
      # the object we are concerned with:
132
      #
133
      #   <%= form_for :person do |f| %>
134 135 136 137
      #     First name: <%= f.text_field :first_name %><br />
      #     Last name : <%= f.text_field :last_name %><br />
      #     Biography : <%= f.text_area :biography %><br />
      #     Admin?    : <%= f.check_box :admin %><br />
138
      #     <%= f.submit %>
139
      #   <% end %>
140
      #
141 142 143 144
      # The variable +f+ yielded to the block is a FormBuilder object that
      # incorporates the knowledge about the model object represented by
      # <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder
      # are used to generate fields bound to this model. Thus, for example,
P
Pratik Naik 已提交
145 146 147
      #
      #   <%= f.text_field :first_name %>
      #
148
      # will get expanded to
P
Pratik Naik 已提交
149 150
      #
      #   <%= text_field :person, :first_name %>
151 152 153 154
      # which results in an html <tt><input></tt> tag whose +name+ attribute is
      # <tt>person[first_name]</tt>. This means that when the form is submitted,
      # the value entered by the user will be available in the controller as
      # <tt>params[:person][:first_name]</tt>.
P
Pratik Naik 已提交
155
      #
156 157
      # For fields generated in this way using the FormBuilder,
      # if <tt>:person</tt> also happens to be the name of an instance variable
158 159
      # <tt>@person</tt>, the default value of the field shown when the form is
      # initially displayed (e.g. in the situation where you are editing an
160
      # existing record) will be the value of the corresponding attribute of
161
      # <tt>@person</tt>.
P
Pratik Naik 已提交
162
      #
163
      # The rightmost argument to +form_for+ is an
164 165 166 167 168 169 170 171 172 173
      # optional hash of options -
      #
      # * <tt>:url</tt> - The URL the form is to be submitted to. This may be
      #   represented in the same way as values passed to +url_for+ or +link_to+.
      #   So for example you may use a named route directly. When the model is
      #   represented by a string or symbol, as in the example above, if the
      #   <tt>:url</tt> option is not specified, by default the form will be
      #   sent back to the current url (We will describe below an alternative
      #   resource-oriented usage of +form_for+ in which the URL does not need
      #   to be specified explicitly).
174 175
      # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of
      #   id attributes on form elements. The namespace attribute will be prefixed
V
Vijay Dev 已提交
176
      #   with underscore on the generated HTML id.
P
Pratik Naik 已提交
177 178
      # * <tt>:html</tt> - Optional HTML attributes for the form tag.
      #
179
      # Also note that +form_for+ doesn't create an exclusive scope. It's still
180 181
      # possible to use both the stand-alone FormHelper methods and methods
      # from FormTagHelper. For example:
182
      #
183
      #   <%= form_for :person do |f| %>
184 185 186
      #     First name: <%= f.text_field :first_name %>
      #     Last name : <%= f.text_field :last_name %>
      #     Biography : <%= text_area :person, :biography %>
187
      #     Admin?    : <%= check_box_tag "person[admin]", "1", @person.company.admin? %>
188
      #     <%= f.submit %>
189 190
      #   <% end %>
      #
191 192 193
      # 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.
194
      #
195
      # === #form_for with a model object
196
      #
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
      # In the examples above, the object to be created or edited was
      # represented by a symbol passed to +form_for+, and we noted that
      # a string can also be used equivalently. It is also possible, however,
      # to pass a model object itself to +form_for+. For example, if <tt>@post</tt>
      # is an existing record you wish to edit, you can create the form using
      #
      #   <%= form_for @post do |f| %>
      #     ...
      #   <% end %>
      #
      # This behaves in almost the same way as outlined previously, with a
      # couple of small exceptions. First, the prefix used to name the input
      # elements within the form (hence the key that denotes them in the +params+
      # hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt>
      # if the object's class is +Post+. However, this can be overwritten using
      # the <tt>:as</tt> option, e.g. -
213
      #
214 215 216
      #   <%= form_for(@person, :as => :client) do |f| %>
      #     ...
      #   <% end %>
P
Pratik Naik 已提交
217
      #
218 219 220
      # would result in <tt>params[:client]</tt>.
      #
      # Secondly, the field values shown when the form is initially displayed
221 222
      # are taken from the attributes of the object passed to +form_for+,
      # regardless of whether the object is an instance
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
      # variable. So, for example, if we had a _local_ variable +post+
      # representing an existing record,
      #
      #   <%= form_for post do |f| %>
      #     ...
      #   <% end %>
      #
      # would produce a form with fields whose initial state reflect the current
      # values of the attributes of +post+.
      #
      # === Resource-oriented style
      #
      # In the examples just shown, although not indicated explicitly, we still
      # need to use the <tt>:url</tt> option in order to specify where the
      # form is going to be sent. However, further simplification is possible
      # if the record passed to +form_for+ is a _resource_, i.e. it corresponds
      # to a set of RESTful routes, e.g. defined using the +resources+ method
      # in <tt>config/routes.rb</tt>. In this case Rails will simply infer the
      # appropriate URL from the record itself. For example,
242
      #
243
      #   <%= form_for @post do |f| %>
244 245 246
      #     ...
      #   <% end %>
      #
247
      # is then equivalent to something like:
248
      #
249
      #   <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
250 251 252
      #     ...
      #   <% end %>
      #
253
      # And for a new record
254
      #
255
      #   <%= form_for(Post.new) do |f| %>
256 257 258
      #     ...
      #   <% end %>
      #
259
      # is equivalent to something like:
260
      #
261
      #   <%= form_for @post, :as => :post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
262 263 264
      #     ...
      #   <% end %>
      #
265
      # However you can still overwrite individual conventions, such as:
266
      #
267
      #   <%= form_for(@post, :url => super_posts_path) do |f| %>
268 269 270
      #     ...
      #   <% end %>
      #
271 272 273 274 275 276
      # You can also set the answer format, like this:
      #
      #   <%= form_for(@post, :format => :json) do |f| %>
      #     ...
      #   <% end %>
      #
277
      # For namespaced routes, like +admin_post_url+:
278
      #
279
      #   <%= form_for([:admin, @post]) do |f| %>
280 281 282
      #    ...
      #   <% end %>
      #
283
      # If your resource has associations defined, for example, you want to add comments
R
Ray Baxter 已提交
284
      # to the document given that the routes are set correctly:
285 286 287 288 289
      #
      #   <%= form_for([@document, @comment]) do |f| %>
      #    ...
      #   <% end %>
      #
R
Typos  
rspeicher 已提交
290 291
      # Where <tt>@document = Document.find(params[:id])</tt> and
      # <tt>@comment = Comment.new</tt>.
292
      #
293 294
      # === Setting the method
      #
295
      # You can force the form to use the full array of HTTP verbs by setting
296
      #
297
      #    :method => (:get|:post|:patch|:put|:delete)
298
      #
299 300 301
      # in the options hash. If the verb is not GET or POST, which are natively
      # supported by HTML forms, the form will be set to POST and a hidden input
      # called _method will carry the intended verb for the server to interpret.
302
      #
S
Stefan Penner 已提交
303
      # === Unobtrusive JavaScript
304 305 306
      #
      # Specifying:
      #
S
Stefan Penner 已提交
307 308 309
      #    :remote => true
      #
      # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
310
      # behavior. The expected default behavior is an XMLHttpRequest in the background instead of the regular
311
      # POST arrangement, but ultimately the behavior is the choice of the JavaScript driver implementor.
312
      # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
S
Stefan Penner 已提交
313 314 315 316
      # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
      #
      # Example:
      #
317
      #   <%= form_for(@post, :remote => true) do |f| %>
S
Stefan Penner 已提交
318 319 320 321 322
      #     ...
      #   <% end %>
      #
      # The HTML generated for this would be:
      #
323
      #   <form action='http://www.example.com' method='post' data-remote='true'>
S
Stefan Penner 已提交
324 325 326 327 328 329
      #     <div style='margin:0;padding:0;display:inline'>
      #       <input name='_method' type='hidden' value='put' />
      #     </div>
      #     ...
      #   </form>
      #
330 331 332
      # === Removing hidden model id's
      #
      # The form_for method automatically includes the model id as a hidden field in the form.
A
Akira Matsuda 已提交
333 334
      # This is used to maintain the correlation between the form data and its associated model.
      # Some ORM systems do not use IDs on nested models so in this case you want to be able
335 336 337 338 339 340 341
      # to disable the hidden id.
      #
      # In the following example the Post model has many Comments stored within it in a NoSQL database,
      # thus there is no primary key for comments.
      #
      # Example:
      #
V
Vijay Dev 已提交
342
      #   <%= form_for(@post) do |f| %>
343 344 345 346 347
      #     <% f.fields_for(:comments, :include_id => false) do |cf| %>
      #       ...
      #     <% end %>
      #   <% end %>
      #
348 349
      # === Customized form builders
      #
350 351 352 353
      # You can also build forms using a customized FormBuilder class. Subclass
      # FormBuilder and override or define some more helpers, then use your
      # custom builder. For example, let's say you made a helper to
      # automatically add labels to form inputs.
354
      #
355
      #   <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %>
356 357
      #     <%= f.text_field :first_name %>
      #     <%= f.text_field :last_name %>
S
Santiago Pastorino 已提交
358 359
      #     <%= f.text_area :biography %>
      #     <%= f.check_box :admin %>
360
      #     <%= f.submit %>
361
      #   <% end %>
362
      #
363 364
      # In this case, if you use this:
      #
365
      #   <%= render f %>
366
      #
367 368 369 370 371
      # The rendered template is <tt>people/_labelling_form</tt> and the local
      # variable referencing the form builder is called
      # <tt>labelling_form</tt>.
      #
      # The custom FormBuilder class is automatically merged with the options
372
      # of a nested fields_for call, unless it's explicitly set.
373
      #
374 375
      # In many cases you will want to wrap the above in another helper, so you
      # could do something like the following:
376
      #
377 378
      #   def labelled_form_for(record_or_name_or_array, *args, &proc)
      #     options = args.extract_options!
379
      #     form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
380 381
      #   end
      #
382 383
      # If you don't need to attach a form to a model instance, then check out
      # FormTagHelper#form_tag.
384 385 386 387 388 389
      #
      # === Form to external resources
      #
      # When you build forms to external resources sometimes you need to set an authenticity token or just render a form
      # without it, for example when you submit data to a payment gateway number and types of fields could be limited.
      #
390
      # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
391
      #
392
      #   <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
393 394 395 396 397
      #     ...
      #   <% end %>
      #
      # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
      #
398
      #   <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
399 400
      #     ...
      #   <% end %>
401
      def form_for(record, options = {}, &proc)
402
        raise ArgumentError, "Missing block" unless block_given?
403

404
        options[:html] ||= {}
405

406 407 408 409 410 411 412
        case record
        when String, Symbol
          object_name = record
          object      = nil
        else
          object      = record.is_a?(Array) ? record.last : record
          object_name = options[:as] || ActiveModel::Naming.param_key(object)
413
          apply_form_for_options!(record, object, options)
414
        end
415

416
        options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote)
417
        options[:html][:method] = options.delete(:method) if options.has_key?(:method)
418
        options[:html][:authenticity_token] = options.delete(:authenticity_token)
419

420
        builder = options[:parent_builder] = instantiate_builder(object_name, object, options)
421
        fields_for = fields_for(object_name, object, options, &proc)
422
        default_options = builder.multipart? ? { :multipart => true } : {}
423 424 425
        default_options.merge!(options.delete(:html))

        form_tag(options.delete(:url) || {}, default_options) { fields_for }
426
      end
427

428
      def apply_form_for_options!(record, object, options) #:nodoc:
429 430
        object = convert_to_model(object)

431
        as = options[:as]
432
        action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post]
433
        options[:html].reverse_merge!(
434 435
          :class  => as ? "#{action}_#{as}" : dom_class(object, action),
          :id     => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence,
436 437
          :method => method
        )
438

439
        options[:url] ||= polymorphic_path(record, :format => options.delete(:format))
440
      end
441
      private :apply_form_for_options!
442

443 444 445 446 447
      # 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.
      #
      # === Generic Examples
448
      #
449 450 451 452 453 454 455 456 457 458 459 460
      # Although the usage and purpose of +field_for+ is similar to +form_for+'s,
      # its method signature is slightly different. Like +form_for+, it yields
      # a FormBuilder object associated with a particular model object to a block,
      # and within the block allows methods to be called on the builder to
      # generate fields associated with the model object. Fields may reflect
      # a model object in two ways - how they are named (hence how submitted
      # values appear within the +params+ hash in the controller) and what
      # default values are shown when the form the fields appear in is first
      # displayed. In order for both of these features to be specified independently,
      # both an object name (represented by either a symbol or string) and the
      # object itself can be passed to the method separately -
      #
461
      #   <%= form_for @person do |person_form| %>
462 463
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
464
      #
465
      #     <%= fields_for :permission, @person.permission do |permission_fields| %>
466 467
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
468 469
      #
      #     <%= f.submit %>
470 471
      #   <% end %>
      #
472 473 474 475 476 477 478 479 480
      # In this case, the checkbox field will be represented by an HTML +input+
      # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted
      # value will appear in the controller as <tt>params[:permission][:admin]</tt>.
      # If <tt>@person.permission</tt> is an existing record with an attribute
      # +admin+, the initial state of the checkbox when first displayed will
      # reflect the value of <tt>@person.permission.admin</tt>.
      #
      # Often this can be simplified by passing just the name of the model
      # object to +fields_for+ -
481
      #
482
      #   <%= fields_for :permission do |permission_fields| %>
483 484 485
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
486 487 488
      # ...in which case, if <tt>:permission</tt> also happens to be the name of an
      # instance variable <tt>@permission</tt>, the initial state of the input
      # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>.
489
      #
490 491 492 493 494
      # Alternatively, you can pass just the model object itself (if the first
      # argument isn't a string or symbol +fields_for+ will realize that the
      # name has been omitted) -
      #
      #   <%= fields_for @person.permission do |permission_fields| %>
495 496 497
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
498 499 500 501
      # and +fields_for+ will derive the required name of the field from the
      # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is
      # of class +Permission+, the field will still be named <tt>permission[admin]</tt>.
      #
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
      # 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.
      #
      # === Nested Attributes Examples
      #
      # When the object belonging to the current scope has a nested attribute
      # writer for a certain attribute, fields_for will yield a new scope
      # for that attribute. This allows you to create forms that set or change
      # the attributes of a parent object and its associations in one go.
      #
      # Nested attribute writers are normal setter methods named after an
      # association. The most common way of defining these writers is either
      # with +accepts_nested_attributes_for+ in a model definition or by
      # defining a method with the proper name. For example: the attribute
      # writer for the association <tt>:address</tt> is called
      # <tt>address_attributes=</tt>.
      #
      # Whether a one-to-one or one-to-many style form builder will be yielded
      # depends on whether the normal reader method returns a _single_ object
      # or an _array_ of objects.
      #
      # ==== One-to-one
      #
      # Consider a Person class which returns a _single_ Address from the
      # <tt>address</tt> reader method and responds to the
      # <tt>address_attributes=</tt> writer method:
      #
      #   class Person
      #     def address
      #       @address
      #     end
      #
      #     def address_attributes=(attributes)
      #       # Process the attributes hash
      #     end
      #   end
      #
      # This model can now be used with a nested fields_for, like so:
      #
542
      #   <%= form_for @person do |person_form| %>
543
      #     ...
544
      #     <%= person_form.fields_for :address do |address_fields| %>
545 546 547
      #       Street  : <%= address_fields.text_field :street %>
      #       Zip code: <%= address_fields.text_field :zip_code %>
      #     <% end %>
548
      #     ...
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
      #   <% end %>
      #
      # When address is already an association on a Person you can use
      # +accepts_nested_attributes_for+ to define the writer method for you:
      #
      #   class Person < ActiveRecord::Base
      #     has_one :address
      #     accepts_nested_attributes_for :address
      #   end
      #
      # If you want to destroy the associated model through the form, you have
      # to enable it first using the <tt>:allow_destroy</tt> option for
      # +accepts_nested_attributes_for+:
      #
      #   class Person < ActiveRecord::Base
      #     has_one :address
      #     accepts_nested_attributes_for :address, :allow_destroy => true
      #   end
      #
568
      # Now, when you use a form element with the <tt>_destroy</tt> parameter,
569 570 571
      # with a value that evaluates to +true+, you will destroy the associated
      # model (eg. 1, '1', true, or 'true'):
      #
572
      #   <%= form_for @person do |person_form| %>
573
      #     ...
574
      #     <%= person_form.fields_for :address do |address_fields| %>
575
      #       ...
576
      #       Delete: <%= address_fields.check_box :_destroy %>
577
      #     <% end %>
578
      #     ...
579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
      #   <% end %>
      #
      # ==== One-to-many
      #
      # Consider a Person class which returns an _array_ of Project instances
      # from the <tt>projects</tt> reader method and responds to the
      # <tt>projects_attributes=</tt> writer method:
      #
      #   class Person
      #     def projects
      #       [@project1, @project2]
      #     end
      #
      #     def projects_attributes=(attributes)
      #       # Process the attributes hash
      #     end
      #   end
      #
597 598 599 600 601 602 603 604 605 606 607 608
      # Note that the <tt>projects_attributes=</tt> writer method is in fact
      # required for fields_for to correctly identify <tt>:projects</tt> as a
      # collection, and the correct indices to be set in the form markup.
      #
      # When projects is already an association on Person you can use
      # +accepts_nested_attributes_for+ to define the writer method for you:
      #
      #   class Person < ActiveRecord::Base
      #     has_many :projects
      #     accepts_nested_attributes_for :projects
      #   end
      #
609 610 611 612
      # This model can now be used with a nested fields_for. The block given to
      # the nested fields_for call will be repeated for each instance in the
      # collection:
      #
613
      #   <%= form_for @person do |person_form| %>
614
      #     ...
615
      #     <%= person_form.fields_for :projects do |project_fields| %>
616 617 618 619
      #       <% if project_fields.object.active? %>
      #         Name: <%= project_fields.text_field :name %>
      #       <% end %>
      #     <% end %>
620
      #     ...
621 622 623 624
      #   <% end %>
      #
      # It's also possible to specify the instance to be used:
      #
625
      #   <%= form_for @person do |person_form| %>
626 627 628
      #     ...
      #     <% @person.projects.each do |project| %>
      #       <% if project.active? %>
629
      #         <%= person_form.fields_for :projects, project do |project_fields| %>
630 631 632 633
      #           Name: <%= project_fields.text_field :name %>
      #         <% end %>
      #       <% end %>
      #     <% end %>
634
      #     ...
635 636
      #   <% end %>
      #
637 638
      # Or a collection to be used:
      #
639
      #   <%= form_for @person do |person_form| %>
640
      #     ...
641
      #     <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
642 643
      #       Name: <%= project_fields.text_field :name %>
      #     <% end %>
644
      #     ...
645 646
      #   <% end %>
      #
647 648
      # When projects is already an association on Person you can use
      # +accepts_nested_attributes_for+ to define the writer method for you:
649
      #
650 651 652 653
      #   class Person < ActiveRecord::Base
      #     has_many :projects
      #     accepts_nested_attributes_for :projects
      #   end
654
      #
655 656 657 658 659 660 661 662 663 664
      # If you want to destroy any of the associated models through the
      # form, you have to enable it first using the <tt>:allow_destroy</tt>
      # option for +accepts_nested_attributes_for+:
      #
      #   class Person < ActiveRecord::Base
      #     has_many :projects
      #     accepts_nested_attributes_for :projects, :allow_destroy => true
      #   end
      #
      # This will allow you to specify which models to destroy in the
665
      # attributes hash by adding a form element for the <tt>_destroy</tt>
666 667 668
      # parameter with a value that evaluates to +true+
      # (eg. 1, '1', true, or 'true'):
      #
669
      #   <%= form_for @person do |person_form| %>
670
      #     ...
671
      #     <%= person_form.fields_for :projects do |project_fields| %>
672
      #       Delete: <%= project_fields.check_box :_destroy %>
673
      #     <% end %>
674
      #     ...
675
      #   <% end %>
676 677 678 679 680 681 682 683 684 685 686 687 688
      #
      # When a collection is used you might want to know the index of each
      # object into the array. For this purpose, the <tt>index</tt> method
      # is available in the FormBuilder object.
      #
      #   <%= form_for @person do |person_form| %>
      #     ...
      #     <%= person_form.fields_for :projects do |project_fields| %>
      #       Project #<%= project_fields.index %>
      #       ...
      #     <% end %>
      #     ...
      #   <% end %>
689
      def fields_for(record_name, record_object = nil, options = {}, &block)
690
        builder = instantiate_builder(record_name, record_object, options)
691 692 693
        output = capture(builder, &block)
        output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
        output
694 695
      end

696
      # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
697
      # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
698
      # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
699
      # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
700 701
      # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
      # target labels for radio_button tags (where the value is used in the ID of the input tag).
702 703 704
      #
      # ==== Examples
      #   label(:post, :title)
P
Pratik Naik 已提交
705
      #   # => <label for="post_title">Title</label>
706
      #
707 708 709
      #   You can localize your labels based on model and attribute names.
      #   For example you can define the following in your locale (e.g. en.yml)
      #
710 711
      #   helpers:
      #     label:
712 713 714 715 716 717 718 719
      #       post:
      #         body: "Write your entire text here"
      #
      #   Which then will result in
      #
      #   label(:post, :body)
      #   # => <label for="post_body">Write your entire text here</label>
      #
720 721
      # Localization can also be based purely on the translation of the attribute-name
      # (if you are using ActiveRecord):
722
      #
723
      #   activerecord:
J
José Valim 已提交
724
      #     attributes:
725 726 727 728 729 730
      #       post:
      #         cost: "Total cost"
      #
      #   label(:post, :cost)
      #   # => <label for="post_cost">Total cost</label>
      #
731
      #   label(:post, :title, "A short title")
P
Pratik Naik 已提交
732
      #   # => <label for="post_title">A short title</label>
733 734
      #
      #   label(:post, :title, "A short title", :class => "title_label")
P
Pratik Naik 已提交
735
      #   # => <label for="post_title" class="title_label">A short title</label>
736
      #
737 738 739
      #   label(:post, :privacy, "Public Post", :value => "public")
      #   # => <label for="post_privacy_public">Public Post</label>
      #
S
Stephen Celis 已提交
740
      #   label(:post, :terms) do
741
      #     'Accept <a href="/terms">Terms</a>.'.html_safe
S
Stephen Celis 已提交
742
      #   end
743
      def label(object_name, method, content_or_options = nil, options = nil, &block)
744
        Tags::Label.new(object_name, method, self, content_or_options, options).render(&block)
745 746
      end

D
Initial  
David Heinemeier Hansson 已提交
747 748
      # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
749
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
D
David Heinemeier Hansson 已提交
750
      # shown.
D
Initial  
David Heinemeier Hansson 已提交
751
      #
752
      # ==== Examples
D
David Heinemeier Hansson 已提交
753
      #   text_field(:post, :title, :size => 20)
754 755 756 757 758 759 760 761 762 763 764
      #   # => <input type="text" id="post_title" name="post[title]" size="20" value="#{@post.title}" />
      #
      #   text_field(:post, :title, :class => "create_input")
      #   # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
      #
      #   text_field(:session, :user, :onchange => "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }")
      #   # => <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!'); }"/>
      #
      #   text_field(:snippet, :code, :size => 20, :class => 'code_input')
      #   # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" />
      #
765
      def text_field(object_name, method, options = {})
766
        Tags::TextField.new(object_name, method, self, options).render
D
Initial  
David Heinemeier Hansson 已提交
767 768
      end

769 770
      # Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
771
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
772 773 774 775
      # shown.
      #
      # ==== Examples
      #   password_field(:login, :pass, :size => 20)
776
      #   # => <input type="password" id="login_pass" name="login[pass]" size="20" />
777
      #
778
      #   password_field(:account, :secret, :class => "form_input", :value => @account.secret)
779
      #   # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
780 781
      #
      #   password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
782
      #   # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
783 784
      #
      #   password_field(:account, :pin, :size => 20, :class => 'form_input')
785
      #   # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
786
      #
787
      def password_field(object_name, method, options = {})
788
        Tags::PasswordField.new(object_name, method, self, options).render
D
Initial  
David Heinemeier Hansson 已提交
789 790
      end

791 792
      # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
793
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
794 795
      # shown.
      #
796
      # ==== Examples
797 798 799 800 801 802 803
      #   hidden_field(:signup, :pass_confirm)
      #   # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" />
      #
      #   hidden_field(:post, :tag_list)
      #   # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" />
      #
      #   hidden_field(:user, :token)
804
      #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
805
      def hidden_field(object_name, method, options = {})
806
        Tags::HiddenField.new(object_name, method, self, options).render
D
Initial  
David Heinemeier Hansson 已提交
807 808
      end

809
      # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
810
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
811
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
812 813
      # shown.
      #
814 815
      # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
      #
816 817 818 819 820
      # ==== Examples
      #   file_field(:user, :avatar)
      #   # => <input type="file" id="user_avatar" name="user[avatar]" />
      #
      #   file_field(:post, :attached, :accept => 'text/html')
821
      #   # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" />
822 823 824 825
      #
      #   file_field(:attachment, :file, :class => 'file_input')
      #   # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
      #
826
      def file_field(object_name, method, options = {})
827
        Tags::FileField.new(object_name, method, self, options).render
828 829
      end

D
Initial  
David Heinemeier Hansson 已提交
830 831 832 833
      # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
      # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
      # hash with +options+.
      #
834 835 836 837 838 839 840 841 842 843 844 845
      # ==== Examples
      #   text_area(:post, :body, :cols => 20, :rows => 40)
      #   # => <textarea cols="20" rows="40" id="post_body" name="post[body]">
      #   #      #{@post.body}
      #   #    </textarea>
      #
      #   text_area(:comment, :text, :size => "20x30")
      #   # => <textarea cols="20" rows="30" id="comment_text" name="comment[text]">
      #   #      #{@comment.text}
      #   #    </textarea>
      #
      #   text_area(:application, :notes, :cols => 40, :rows => 15, :class => 'app_input')
846
      #   # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
847 848 849 850 851 852 853
      #   #      #{@application.notes}
      #   #    </textarea>
      #
      #   text_area(:entry, :body, :size => "20x20", :disabled => 'disabled')
      #   # => <textarea cols="20" rows="20" id="entry_body" name="entry[body]" disabled="disabled">
      #   #      #{@entry.body}
      #   #    </textarea>
854
      def text_area(object_name, method, options = {})
855
        Tags::TextArea.new(object_name, method, self, options).render
D
Initial  
David Heinemeier Hansson 已提交
856
      end
857

D
Initial  
David Heinemeier Hansson 已提交
858
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
P
Pratik Naik 已提交
859
      # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
Y
Yehuda Katz 已提交
860 861
      # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked.
      # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1
P
Pratik Naik 已提交
862
      # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
P
Pratik Naik 已提交
863 864 865 866 867
      #
      # ==== Gotcha
      #
      # The HTML specification says unchecked check boxes are not successful, and
      # thus web browsers do not send them. Unfortunately this introduces a gotcha:
P
Pratik Naik 已提交
868
      # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
P
Pratik Naik 已提交
869 870 871 872 873 874 875
      # invoice the user unchecks its check box, no +paid+ parameter is sent. So,
      # any mass-assignment idiom like
      #
      #   @invoice.update_attributes(params[:invoice])
      #
      # wouldn't update the flag.
      #
P
Pratik Naik 已提交
876 877
      # To prevent this the helper generates an auxiliary hidden field before
      # the very check box. The hidden field has the same name and its
878
      # attributes mimic an unchecked check box.
P
Pratik Naik 已提交
879 880 881 882 883 884
      #
      # This way, the client either sends only the hidden field (representing
      # the check box is unchecked), or both fields. Since the HTML specification
      # says key/value pairs have to be sent in the same order they appear in the
      # form, and parameters extraction gets the last occurrence of any repeated
      # key in the query string, that works for ordinary forms.
P
Pratik Naik 已提交
885 886 887 888
      #
      # Unfortunately that workaround does not work when the check box goes
      # within an array-like parameter, as in
      #
889
      #   <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
P
Pratik Naik 已提交
890 891 892 893 894
      #     <%= form.check_box :paid %>
      #     ...
      #   <% end %>
      #
      # because parameter name repetition is precisely what Rails seeks to distinguish
P
Pratik Naik 已提交
895 896 897 898 899
      # the elements of the array. For each item with a checked check box you
      # get an extra ghost item with only that attribute, assigned to "0".
      #
      # In that case it is preferable to either use +check_box_tag+ or to use
      # hashes instead of arrays.
D
Initial  
David Heinemeier Hansson 已提交
900
      #
901
      # ==== Examples
902
      #   # Let's say that @post.validated? is 1:
D
Initial  
David Heinemeier Hansson 已提交
903
      #   check_box("post", "validated")
P
Pratik Naik 已提交
904 905
      #   # => <input name="post[validated]" type="hidden" value="0" />
      #   #    <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
D
Initial  
David Heinemeier Hansson 已提交
906
      #
907
      #   # Let's say that @puppy.gooddog is "no":
D
Initial  
David Heinemeier Hansson 已提交
908
      #   check_box("puppy", "gooddog", {}, "yes", "no")
P
Pratik Naik 已提交
909 910
      #   # => <input name="puppy[gooddog]" type="hidden" value="no" />
      #   #    <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
911
      #
P
Pratik Naik 已提交
912
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
P
Pratik Naik 已提交
913 914
      #   # => <input name="eula[accepted]" type="hidden" value="no" />
      #   #    <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
915
      #
916
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
917
        Tags::CheckBox.new(object_name, method, self, checked_value, unchecked_value, options).render
D
Initial  
David Heinemeier Hansson 已提交
918
      end
919 920 921

      # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
P
Pratik Naik 已提交
922 923 924 925
      # radio button will be checked.
      #
      # To force the radio button to be checked pass <tt>:checked => true</tt> in the
      # +options+ hash. You may pass HTML options there as well.
926 927 928
      #
      # ==== Examples
      #   # Let's say that @post.category returns "rails":
929 930
      #   radio_button("post", "category", "rails")
      #   radio_button("post", "category", "java")
P
Pratik Naik 已提交
931 932
      #   # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
      #   #    <input type="radio" id="post_category_java" name="post[category]" value="java" />
933
      #
934 935
      #   radio_button("user", "receive_newsletter", "yes")
      #   radio_button("user", "receive_newsletter", "no")
P
Pratik Naik 已提交
936 937
      #   # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
      #   #    <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" />
938
      def radio_button(object_name, method, tag_value, options = {})
939
        Tags::RadioButton.new(object_name, method, self, tag_value, options).render
940
      end
941

R
Ray Baxter 已提交
942
      # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
943 944
      # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
      # some browsers.
R
Ray Baxter 已提交
945 946
      #
      # ==== Examples
947
      #
R
Ray Baxter 已提交
948
      #   search_field(:user, :name)
949
      #   # => <input id="user_name" name="user[name]" type="search" />
R
Ray Baxter 已提交
950
      #   search_field(:user, :name, :autosave => false)
951
      #   # => <input autosave="false" id="user_name" name="user[name]" type="search" />
R
Ray Baxter 已提交
952
      #   search_field(:user, :name, :results => 3)
953
      #   # => <input id="user_name" name="user[name]" results="3" type="search" />
R
Ray Baxter 已提交
954 955
      #   #  Assume request.host returns "www.example.com"
      #   search_field(:user, :name, :autosave => true)
956
      #   # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" />
R
Ray Baxter 已提交
957
      #   search_field(:user, :name, :onsearch => true)
958
      #   # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
R
Ray Baxter 已提交
959
      #   search_field(:user, :name, :autosave => false, :onsearch => true)
960
      #   # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" />
R
Ray Baxter 已提交
961
      #   search_field(:user, :name, :autosave => true, :onsearch => true)
962
      #   # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" />
963
      #
964
      def search_field(object_name, method, options = {})
965
        Tags::SearchField.new(object_name, method, self, options).render
966 967 968
      end

      # Returns a text_field of type "tel".
969
      #
970
      #   telephone_field("user", "phone")
971
      #   # => <input id="user_phone" name="user[phone]" type="tel" />
972
      #
973
      def telephone_field(object_name, method, options = {})
974
        Tags::TelField.new(object_name, method, self, options).render
975 976 977
      end
      alias phone_field telephone_field

978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
      # Returns a text_field of type "date".
      #
      #   date_field("user", "born_on")
      #   # => <input id="user_born_on" name="user[born_on]" type="date" />
      #
      # The default value is generated by trying to call "to_date"
      # on the object's value, which makes it behave as expected for instances
      # of DateTime and ActiveSupport::TimeWithZone. You can still override that
      # by passing the "value" option explicitly, e.g.
      #
      #   @user.born_on = Date.new(1984, 1, 27)
      #   date_field("user", "born_on", value: "1984-05-12")
      #   # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" />
      #
      def date_field(object_name, method, options = {})
        Tags::DateField.new(object_name, method, self, options).render
      end

996
      # Returns a text_field of type "url".
997 998
      #
      #   url_field("user", "homepage")
999
      #   # => <input id="user_homepage" name="user[homepage]" type="url" />
1000
      #
1001
      def url_field(object_name, method, options = {})
1002
        Tags::UrlField.new(object_name, method, self, options).render
1003 1004 1005
      end

      # Returns a text_field of type "email".
1006 1007
      #
      #   email_field("user", "address")
1008
      #   # => <input id="user_address" name="user[address]" type="email" />
1009
      #
1010
      def email_field(object_name, method, options = {})
1011
        Tags::EmailField.new(object_name, method, self, options).render
1012 1013 1014 1015 1016 1017 1018
      end

      # Returns an input tag of type "number".
      #
      # ==== Options
      # * Accepts same options as number_field_tag
      def number_field(object_name, method, options = {})
1019
        Tags::NumberField.new(object_name, method, self, options).render
1020 1021 1022 1023 1024 1025 1026
      end

      # Returns an input tag of type "range".
      #
      # ==== Options
      # * Accepts same options as range_field_tag
      def range_field(object_name, method, options = {})
1027
        Tags::RangeField.new(object_name, method, self, options).render
1028
      end
1029 1030 1031

      private

1032
        def instantiate_builder(record_name, record_object, options)
1033
          case record_name
1034 1035
          when String, Symbol
            object = record_object
1036
            object_name = record_name
1037
          else
1038
            object = record_name
1039 1040 1041 1042
            object_name = ActiveModel::Naming.param_key(object)
          end

          builder = options[:builder] || ActionView::Base.default_form_builder
1043
          builder.new(object_name, object, self, options)
1044
        end
D
Initial  
David Heinemeier Hansson 已提交
1045 1046
    end

1047
    class FormBuilder
1048
      # The methods which wrap a form helper call.
1049
      class_attribute :field_helpers
1050
      self.field_helpers = FormHelper.instance_methods - [:form_for, :convert_to_model]
1051

1052
      attr_accessor :object_name, :object, :options
1053

1054
      attr_reader :multipart, :parent_builder, :index
1055 1056
      alias :multipart? :multipart

1057 1058 1059 1060 1061
      def multipart=(multipart)
        @multipart = multipart
        parent_builder.multipart = multipart if parent_builder
      end

1062 1063
      def self._to_partial_path
        @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
1064 1065
      end

1066 1067
      def to_partial_path
        self.class._to_partial_path
Y
Yehuda Katz 已提交
1068 1069
      end

1070 1071 1072 1073
      def to_model
        self
      end

1074 1075 1076 1077 1078 1079
      def initialize(object_name, object, template, options, block=nil)
        if block
          ActiveSupport::Deprecation.warn(
            "Giving a block to FormBuilder is deprecated and has no effect anymore.")
        end

1080
        @nested_child_index = {}
1081
        @object_name, @object, @template, @options = object_name, object, template, options
1082
        @parent_builder = options[:parent_builder]
1083
        @default_options = @options ? @options.slice(:index, :namespace) : {}
1084 1085 1086 1087 1088 1089 1090
        if @object_name.to_s.match(/\[\]$/)
          if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
            @auto_index = object.to_param
          else
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
          end
        end
1091
        @multipart = nil
1092
        @index = options[:index] || options[:child_index]
1093
      end
1094

1095
      (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector|
1096
        class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1097 1098 1099 1100 1101 1102 1103
          def #{selector}(method, options = {})  # def text_field(method, options = {})
            @template.send(                      #   @template.send(
              #{selector.inspect},               #     "text_field",
              @object_name,                      #     @object_name,
              method,                            #     method,
              objectify_options(options))        #     objectify_options(options))
          end                                    # end
1104
        RUBY_EVAL
1105
      end
1106

1107
      def fields_for(record_name, record_object = nil, fields_options = {}, &block)
1108
        fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options?
1109 1110
        fields_options[:builder] ||= options[:builder]
        fields_options[:parent_builder] = self
1111
        fields_options[:namespace] = options[:namespace]
1112

1113
        case record_name
1114
        when String, Symbol
1115 1116
          if nested_attributes_association?(record_name)
            return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
1117
          end
1118
        else
1119 1120 1121 1122 1123
          record_object = record_name.is_a?(Array) ? record_name.last : record_name
          record_name   = ActiveModel::Naming.param_key(record_object)
        end

        index = if options.has_key?(:index)
1124
          options[:index]
1125 1126
        elsif defined?(@auto_index)
          self.object_name = @object_name.to_s.sub(/\[\]$/,"")
1127
          @auto_index
1128
        end
1129 1130 1131

        record_name = index ? "#{object_name}[#{index}][#{record_name}]" : "#{object_name}[#{record_name}]"
        fields_options[:child_index] = index
1132

1133
        @template.fields_for(record_name, record_object, fields_options, &block)
1134
      end
1135

S
Stephen Celis 已提交
1136 1137
      def label(method, text = nil, options = {}, &block)
        @template.label(@object_name, method, text, objectify_options(options), &block)
1138
      end
1139

1140
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
1141
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
1142
      end
1143

1144
      def radio_button(method, tag_value, options = {})
1145
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1146
      end
1147

1148 1149 1150 1151
      def hidden_field(method, options = {})
        @emitted_hidden_id = true if method == :id
        @template.hidden_field(@object_name, method, objectify_options(options))
      end
1152

1153
      def file_field(method, options = {})
1154
        self.multipart = true
1155 1156
        @template.file_field(@object_name, method, objectify_options(options))
      end
1157

1158 1159 1160
      # Add the submit button for the given form. When no value is given, it checks
      # if the object is a new resource or not to create the proper label:
      #
1161
      #   <%= form_for @post do |f| %>
1162 1163
      #     <%= f.submit %>
      #   <% end %>
1164
      #
1165 1166 1167 1168
      # In the example above, if @post is a new record, it will use "Create Post" as
      # submit button label, otherwise, it uses "Update Post".
      #
      # Those labels can be customized using I18n, under the helpers.submit key and accept
1169
      # the %{model} as translation interpolation:
1170 1171 1172 1173
      #
      #   en:
      #     helpers:
      #       submit:
1174 1175
      #         create: "Create a %{model}"
      #         update: "Confirm changes to %{model}"
1176
      #
1177 1178 1179 1180 1181 1182
      # It also searches for a key specific for the given object:
      #
      #   en:
      #     helpers:
      #       submit:
      #         post:
1183
      #           create: "Add %{model}"
1184
      #
1185 1186
      def submit(value=nil, options={})
        value, options = nil, value if value.is_a?(Hash)
1187
        value ||= submit_default_value
1188
        @template.submit_tag(value, options)
1189
      end
1190

1191 1192 1193 1194 1195 1196 1197 1198
      # Add the submit button for the given form. When no value is given, it checks
      # if the object is a new resource or not to create the proper label:
      #
      #   <%= form_for @post do |f| %>
      #     <%= f.button %>
      #   <% end %>
      #
      # In the example above, if @post is a new record, it will use "Create Post" as
1199
      # button label, otherwise, it uses "Update Post".
1200
      #
1201 1202
      # Those labels can be customized using I18n, under the helpers.submit key
      # (the same as submit helper) and accept the %{model} as translation interpolation:
1203 1204 1205
      #
      #   en:
      #     helpers:
1206
      #       submit:
1207 1208 1209 1210 1211 1212 1213
      #         create: "Create a %{model}"
      #         update: "Confirm changes to %{model}"
      #
      # It also searches for a key specific for the given object:
      #
      #   en:
      #     helpers:
1214
      #       submit:
1215 1216 1217 1218 1219 1220 1221 1222 1223
      #         post:
      #           create: "Add %{model}"
      #
      def button(value=nil, options={})
        value, options = nil, value if value.is_a?(Hash)
        value ||= submit_default_value
        @template.button_tag(value, options)
      end

1224
      def emitted_hidden_id?
1225
        @emitted_hidden_id ||= nil
1226 1227
      end

1228 1229 1230 1231
      private
        def objectify_options(options)
          @default_options.merge(options.merge(:object => @object))
        end
1232

1233
        def submit_default_value
1234
          object = convert_to_model(@object)
1235
          key    = object ? (object.persisted? ? :update : :create) : :submit
1236 1237 1238 1239 1240 1241 1242

          model = if object.class.respond_to?(:model_name)
            object.class.model_name.human
          else
            @object_name.to_s.humanize
          end

1243 1244 1245 1246 1247 1248
          defaults = []
          defaults << :"helpers.submit.#{object_name}.#{key}"
          defaults << :"helpers.submit.#{key}"
          defaults << "#{key.to_s.humanize} #{model}"

          I18n.t(defaults.shift, :model => model, :default => defaults)
1249 1250
        end

1251 1252 1253 1254
        def nested_attributes_association?(association_name)
          @object.respond_to?("#{association_name}_attributes=")
        end

1255
        def fields_for_with_nested_attributes(association_name, association, options, block)
1256
          name = "#{object_name}[#{association_name}_attributes]"
1257
          association = convert_to_model(association)
1258

1259
          if association.respond_to?(:persisted?)
1260
            association = [association] if @object.send(association_name).is_a?(Array)
1261
          elsif !association.respond_to?(:to_ary)
1262 1263
            association = @object.send(association_name)
          end
1264

1265
          if association.respond_to?(:to_ary)
1266
            explicit_child_index = options[:child_index]
W
wycats 已提交
1267 1268
            output = ActiveSupport::SafeBuffer.new
            association.each do |child|
1269 1270
              options[:child_index] = nested_child_index(name) unless explicit_child_index
              output << fields_for_nested_model("#{name}[#{options[:child_index]}]", child, options, block)
W
wycats 已提交
1271 1272
            end
            output
1273
          elsif association
1274
            fields_for_nested_model(name, association, options, block)
1275 1276 1277
          end
        end

1278
        def fields_for_nested_model(name, object, options, block)
1279
          object = convert_to_model(object)
1280

1281 1282 1283
          parent_include_id = self.options.fetch(:include_id, true)
          include_id = options.fetch(:include_id, parent_include_id)
          options[:hidden_field_id] = object.persisted? && include_id
1284
          @template.fields_for(name, object, options, &block)
1285 1286
        end

1287 1288 1289
        def nested_child_index(name)
          @nested_child_index[name] ||= -1
          @nested_child_index[name] += 1
1290
        end
1291 1292 1293 1294

        def convert_to_model(object)
          object.respond_to?(:to_model) ? object.to_model : object
        end
1295
    end
D
Initial  
David Heinemeier Hansson 已提交
1296
  end
1297

1298
  ActiveSupport.on_load(:action_view) do
1299
    cattr_accessor(:default_form_builder) { ::ActionView::Helpers::FormBuilder }
1300
  end
J
Jeremy Kemper 已提交
1301
end