form_helper.rb 57.3 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 'active_support/core_ext/class/attribute'
6
require 'active_support/core_ext/hash/slice'
7
require 'active_support/core_ext/object/blank'
8
require 'active_support/core_ext/string/output_safety'
9
require 'active_support/core_ext/array/extract_options'
D
Initial  
David Heinemeier Hansson 已提交
10 11

module ActionView
12
  # = Action View Form Helpers
D
Initial  
David Heinemeier Hansson 已提交
13
  module Helpers
14 15 16
    # Form helpers are designed to make working with resources much easier
    # compared to using vanilla HTML.
    #
17
    # Forms for models are created with +form_for+. That method yields a form
18 19
    # builder that knows the model the form is about. The form builder is thus
    # able to generate default values for input fields that correspond to model
20
    # attributes, and also convenient names, IDs, endpoints, etc.
21 22 23 24
    #
    # Conventions in the generated field names allow controllers to receive form
    # data nicely structured in +params+ with no effort on your side.
    #
25 26 27
    # 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
    # pass it to +form_for+:
28 29
    #
    #   <%= form_for @person do |f| %>
30 31 32 33 34 35
    #     <%= f.label :first_name %>:
    #     <%= f.text_field :first_name %><br />
    #
    #     <%= f.label :last_name %>:
    #     <%= f.text_field :last_name %><br />
    #
36 37 38 39 40 41 42 43 44
    #     <%= 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>
45 46 47 48 49 50
    #     <label for="person_first_name">First name</label>:
    #     <input id="person_first_name" name="person[first_name]" size="30" type="text" /><br />
    #
    #     <label for="person_last_name">Last name</label>:
    #     <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br />
    #
51 52 53
    #     <input id="person_submit" name="commit" type="submit" value="Create Person" />
    #   </form>
    #
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
    # 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:
    #
71
    #   <form action="/people/256" class="edit_person" id="edit_person_256" method="post">
72 73 74 75 76 77 78 79 80 81 82 83 84 85 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>:
    #     <input id="person_first_name" name="person[first_name]" size="30" type="text" value="John" /><br />
    #
    #     <label for="person_last_name">Last name</label>:
    #     <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br />
    #
    #     <input id="person_submit" name="commit" type="submit" value="Update Person" />
    #   </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>:
91
    #
92
    #   if @person.update_attributes(params[:person])
93 94 95 96 97
    #     # success
    #   else
    #     # error handling
    #   end
    #
98
    # That's how you typically work with resources.
D
Initial  
David Heinemeier Hansson 已提交
99
    module FormHelper
W
wycats 已提交
100 101 102
      extend ActiveSupport::Concern

      include FormTagHelper
103
      include UrlHelper
W
wycats 已提交
104

J
José Valim 已提交
105 106 107 108 109
      # Converts the given object to an ActiveModel compliant one.
      def convert_to_model(object)
        object.respond_to?(:to_model) ? object.to_model : object
      end

110 111
      # Creates a form and a scope around a specific model object that is used
      # as a base for questioning about values for the fields.
112
      #
P
Pratik Naik 已提交
113
      # Rails provides succinct resource-oriented form generation with +form_for+
114 115
      # like this:
      #
116
      #   <%= form_for @offer do |f| %>
117 118 119 120
      #     <%= f.label :version, 'Version' %>:
      #     <%= f.text_field :version %><br />
      #     <%= f.label :author, 'Author' %>:
      #     <%= f.text_field :author %><br />
121
      #     <%= f.submit %>
122 123
      #   <% end %>
      #
124 125 126 127
      # There, +form_for+ is able to generate the rest of RESTful form
      # parameters based on introspection on the record, but to understand what
      # it does we need to dig first into the alternative generic usage it is
      # based upon.
128
      #
129 130
      # === Generic form_for
      #
131 132
      # The generic way to call +form_for+ yields a form builder around a
      # model:
133
      #
134
      #   <%= form_for :person do |f| %>
135 136 137 138
      #     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 />
139
      #     <%= f.submit %>
140
      #   <% end %>
141
      #
142 143
      # There, the argument is a symbol or string with the name of the
      # object the form is about.
P
Pratik Naik 已提交
144 145 146 147 148 149 150 151 152 153
      #
      # The form builder acts as a regular form helper that somehow carries the
      # model. Thus, the idea is that
      #
      #   <%= f.text_field :first_name %>
      #
      # gets expanded to
      #
      #   <%= text_field :person, :first_name %>
      #
154
      # The rightmost argument to +form_for+ is an
P
Pratik Naik 已提交
155 156
      # optional hash of options:
      #
157 158 159
      # * <tt>:url</tt> - The URL the form is submitted to. It takes the same
      #   fields you pass to +url_for+ or +link_to+. In particular you may pass
      #   here a named route directly as well. Defaults to the current action.
P
Pratik Naik 已提交
160 161
      # * <tt>:html</tt> - Optional HTML attributes for the form tag.
      #
162
      # Also note that +form_for+ doesn't create an exclusive scope. It's still
163 164
      # possible to use both the stand-alone FormHelper methods and methods
      # from FormTagHelper. For example:
165
      #
166
      #   <%= form_for @person do |f| %>
167 168 169 170
      #     First name: <%= f.text_field :first_name %>
      #     Last name : <%= f.text_field :last_name %>
      #     Biography : <%= text_area :person, :biography %>
      #     Admin?    : <%= check_box_tag "person[admin]", @person.company.admin? %>
171
      #     <%= f.submit %>
172 173
      #   <% end %>
      #
174 175 176
      # 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.
177
      #
P
Pratik Naik 已提交
178
      # === Resource-oriented style
179
      #
180 181 182 183
      # As we said above, in addition to manually configuring the +form_for+
      # call, you can rely on automated resource identification, which will use
      # the conventions and named routes of that approach. This is the
      # preferred way to use +form_for+ nowadays.
P
Pratik Naik 已提交
184 185
      #
      # For example, if <tt>@post</tt> is an existing record you want to edit
186
      #
187
      #   <%= form_for @post do |f| %>
188 189 190
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
191
      # is equivalent to something like:
192
      #
193
      #   <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
194 195 196
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
197
      # And for new records
198
      #
199
      #   <%= form_for(Post.new) do |f| %>
200 201 202
      #     ...
      #   <% end %>
      #
203
      # is equivalent to something like:
204
      #
205
      #   <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :class => "new_post", :id => "new_post" } do |f| %>
206 207 208 209 210
      #     ...
      #   <% end %>
      #
      # You can also overwrite the individual conventions, like this:
      #
211
      #   <%= form_for(@post, :url => super_post_path(@post)) do |f| %>
212 213 214
      #     ...
      #   <% end %>
      #
215 216 217 218 219 220
      # You can also set the answer format, like this:
      #
      #   <%= form_for(@post, :format => :json) do |f| %>
      #     ...
      #   <% end %>
      #
221 222 223
      # If you have an object that needs to be represented as a different
      # parameter, like a Client that acts as a Person:
      #
R
Typos  
rspeicher 已提交
224
      #   <%= form_for(@post, :as => :client) do |f| %>
225 226 227
      #     ...
      #   <% end %>
      #
228
      # For namespaced routes, like +admin_post_url+:
229
      #
230
      #   <%= form_for([:admin, @post]) do |f| %>
231 232 233
      #    ...
      #   <% end %>
      #
234
      # If your resource has associations defined, for example, you want to add comments
235 236 237 238 239 240
      # to the post given that the routes are set correctly:
      #
      #   <%= form_for([@document, @comment]) do |f| %>
      #    ...
      #   <% end %>
      #
R
Typos  
rspeicher 已提交
241 242
      # Where <tt>@document = Document.find(params[:id])</tt> and
      # <tt>@comment = Comment.new</tt>.
243
      #
244 245
      # === Setting the method
      #
246
      # You can force the form to use the full array of HTTP verbs by setting
247 248 249 250 251 252 253
      #
      #    :method => (:get|:post|:put|:delete)
      #
      # 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.
      #
S
Stefan Penner 已提交
254
      # === Unobtrusive JavaScript
255 256 257
      #
      # Specifying:
      #
S
Stefan Penner 已提交
258 259 260
      #    :remote => true
      #
      # in the options hash creates a form that will allow the unobtrusive JavaScript drivers to modify its
261
      # behaviour. The expected default behaviour is an XMLHttpRequest in the background instead of the regular
S
Stefan Penner 已提交
262
      # POST arrangement, but ultimately the behaviour is the choice of the JavaScript driver implementor.
263
      # Even though it's using JavaScript to serialize the form elements, the form submission will work just like
S
Stefan Penner 已提交
264 265 266 267
      # a regular submission as viewed by the receiving side (all elements available in <tt>params</tt>).
      #
      # Example:
      #
268
      #   <%= form_for(@post, :remote => true) do |f| %>
S
Stefan Penner 已提交
269 270 271 272 273
      #     ...
      #   <% end %>
      #
      # The HTML generated for this would be:
      #
274
      #   <form action='http://www.example.com' method='post' data-remote='true'>
S
Stefan Penner 已提交
275 276 277 278 279 280
      #     <div style='margin:0;padding:0;display:inline'>
      #       <input name='_method' type='hidden' value='put' />
      #     </div>
      #     ...
      #   </form>
      #
281 282 283
      # === Removing hidden model id's
      #
      # The form_for method automatically includes the model id as a hidden field in the form.
A
Akira Matsuda 已提交
284 285
      # 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
286 287 288 289 290 291 292 293 294 295 296 297 298
      # 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:
      #
      #   <%= form(@post) do |f| %>
      #     <% f.fields_for(:comments, :include_id => false) do |cf| %>
      #       ...
      #     <% end %>
      #   <% end %>
      #
299 300
      # === Customized form builders
      #
301 302 303 304
      # 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.
305
      #
306
      #   <%= form_for @person, :url => { :action => "create" }, :builder => LabellingFormBuilder do |f| %>
307 308
      #     <%= f.text_field :first_name %>
      #     <%= f.text_field :last_name %>
S
Santiago Pastorino 已提交
309 310
      #     <%= f.text_area :biography %>
      #     <%= f.check_box :admin %>
311
      #     <%= f.submit %>
312
      #   <% end %>
313
      #
314 315
      # In this case, if you use this:
      #
316
      #   <%= render f %>
317
      #
318 319 320 321 322
      # 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
323
      # of a nested fields_for call, unless it's explicitly set.
324
      #
325 326
      # In many cases you will want to wrap the above in another helper, so you
      # could do something like the following:
327
      #
328 329
      #   def labelled_form_for(record_or_name_or_array, *args, &proc)
      #     options = args.extract_options!
330
      #     form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
331 332
      #   end
      #
333 334
      # If you don't need to attach a form to a model instance, then check out
      # FormTagHelper#form_tag.
335 336 337 338 339 340
      #
      # === 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.
      #
341
      # To set an authenticity token you need to pass an <tt>:authenticity_token</tt> parameter
342
      #
343
      #   <%= form_for @invoice, :url => external_url, :authenticity_token => 'external_token' do |f|
344 345 346 347 348
      #     ...
      #   <% end %>
      #
      # If you don't want to an authenticity token field be rendered at all just pass <tt>false</tt>:
      #
349
      #   <%= form_for @invoice, :url => external_url, :authenticity_token => false do |f|
350 351
      #     ...
      #   <% end %>
352
      def form_for(record, options = {}, &proc)
353
        raise ArgumentError, "Missing block" unless block_given?
354

355
        options[:html] ||= {}
356

357 358 359 360 361 362 363 364 365
        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)
          apply_form_for_options!(record, options)
        end
366

367
        options[:html][:remote] = options.delete(:remote)
368
        options[:html][:method] = options.delete(:method) if options.has_key?(:method)
369
        options[:html][:authenticity_token] = options.delete(:authenticity_token)
370

371 372
        builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
        fields_for = fields_for(object_name, object, options, &proc)
373
        default_options = builder.multipart? ? { :multipart => true } : {}
374
        output = form_tag(options.delete(:url) || {}, default_options.merge!(options.delete(:html)))
375
        output << fields_for
W
wycats 已提交
376
        output.safe_concat('</form>')
377
      end
378

379 380
      def apply_form_for_options!(object_or_array, options) #:nodoc:
        object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
381 382
        object = convert_to_model(object)

383 384
        as = options[:as]
        action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post]
385 386 387 388 389
        options[:html].reverse_merge!(
          :class  => as ? "#{as}_#{action}" : dom_class(object, action),
          :id     => as ? "#{as}_#{action}" : dom_id(object, action),
          :method => method
        )
390

391
        options[:url] ||= polymorphic_path(object_or_array, :format => options.delete(:format))
392
      end
393
      private :apply_form_for_options!
394

395 396 397 398 399
      # 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
400
      #
401
      #   <%= form_for @person do |person_form| %>
402 403
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
404
      #
405
      #     <%= fields_for @person.permission do |permission_fields| %>
406 407
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
408 409
      #
      #     <%= f.submit %>
410 411
      #   <% end %>
      #
412 413
      # ...or if you have an object that needs to be represented as a different
      # parameter, like a Client that acts as a Person:
414
      #
415
      #   <%= fields_for :person, @client do |permission_fields| %>
416 417 418
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
419
      # ...or if you don't have an object, just a name of the parameter:
420
      #
421
      #   <%= fields_for :person do |permission_fields| %>
422 423 424
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
      # 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:
      #
465
      #   <%= form_for @person do |person_form| %>
466
      #     ...
467
      #     <%= person_form.fields_for :address do |address_fields| %>
468 469 470
      #       Street  : <%= address_fields.text_field :street %>
      #       Zip code: <%= address_fields.text_field :zip_code %>
      #     <% end %>
471
      #     ...
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
      #   <% 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
      #
491
      # Now, when you use a form element with the <tt>_destroy</tt> parameter,
492 493 494
      # with a value that evaluates to +true+, you will destroy the associated
      # model (eg. 1, '1', true, or 'true'):
      #
495
      #   <%= form_for @person do |person_form| %>
496
      #     ...
497
      #     <%= person_form.fields_for :address do |address_fields| %>
498
      #       ...
499
      #       Delete: <%= address_fields.check_box :_destroy %>
500
      #     <% end %>
501
      #     ...
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523
      #   <% 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
      #
      # 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:
      #
524
      #   <%= form_for @person do |person_form| %>
525
      #     ...
526
      #     <%= person_form.fields_for :projects do |project_fields| %>
527 528 529 530
      #       <% if project_fields.object.active? %>
      #         Name: <%= project_fields.text_field :name %>
      #       <% end %>
      #     <% end %>
531
      #     ...
532 533 534 535
      #   <% end %>
      #
      # It's also possible to specify the instance to be used:
      #
536
      #   <%= form_for @person do |person_form| %>
537 538 539
      #     ...
      #     <% @person.projects.each do |project| %>
      #       <% if project.active? %>
540
      #         <%= person_form.fields_for :projects, project do |project_fields| %>
541 542 543 544
      #           Name: <%= project_fields.text_field :name %>
      #         <% end %>
      #       <% end %>
      #     <% end %>
545
      #     ...
546 547
      #   <% end %>
      #
548 549
      # Or a collection to be used:
      #
550
      #   <%= form_for @person do |person_form| %>
551
      #     ...
552
      #     <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
553 554
      #       Name: <%= project_fields.text_field :name %>
      #     <% end %>
555
      #     ...
556 557
      #   <% end %>
      #
558 559 560 561 562 563 564 565 566 567 568 569 570
      # In addition, you may want to have access to the current iteration index.
      # In that case, you can use a similar method called fields_for_with_index
      # which receives a block with an extra parameter:
      #
      #   <%= form_for @person do |person_form| %>
      #     ...
      #     <%= person_form.fields_for_with_index :projects do |project_fields, index| %>
      #       Position: <%= index %>
      #       Name: <%= project_fields.text_field :name %>
      #     <% end %>
      #     ...
      #   <% end %>
      #
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
      # 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
      #
      # 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
589
      # attributes hash by adding a form element for the <tt>_destroy</tt>
590 591 592
      # parameter with a value that evaluates to +true+
      # (eg. 1, '1', true, or 'true'):
      #
593
      #   <%= form_for @person do |person_form| %>
594
      #     ...
595
      #     <%= person_form.fields_for :projects do |project_fields| %>
596
      #       Delete: <%= project_fields.check_box :_destroy %>
597
      #     <% end %>
598
      #     ...
599
      #   <% end %>
600 601
      def fields_for(record_name, record_object = nil, options = {}, &block)
        builder = instantiate_builder(record_name, record_object, options, &block)
602 603 604
        output = capture(builder, &block)
        output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id?
        output
605 606
      end

607
      # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
608
      # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation
609
      # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly.
610
      # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
611 612
      # 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).
613 614 615
      #
      # ==== Examples
      #   label(:post, :title)
P
Pratik Naik 已提交
616
      #   # => <label for="post_title">Title</label>
617
      #
618 619 620
      #   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)
      #
621 622
      #   helpers:
      #     label:
623 624 625 626 627 628 629 630
      #       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>
      #
631 632
      # Localization can also be based purely on the translation of the attribute-name
      # (if you are using ActiveRecord):
633
      #
634
      #   activerecord:
J
José Valim 已提交
635
      #     attributes:
636 637 638 639 640 641
      #       post:
      #         cost: "Total cost"
      #
      #   label(:post, :cost)
      #   # => <label for="post_cost">Total cost</label>
      #
642
      #   label(:post, :title, "A short title")
P
Pratik Naik 已提交
643
      #   # => <label for="post_title">A short title</label>
644 645
      #
      #   label(:post, :title, "A short title", :class => "title_label")
P
Pratik Naik 已提交
646
      #   # => <label for="post_title" class="title_label">A short title</label>
647
      #
648 649 650
      #   label(:post, :privacy, "Public Post", :value => "public")
      #   # => <label for="post_privacy_public">Public Post</label>
      #
S
Stephen Celis 已提交
651 652 653
      #   label(:post, :terms) do
      #     'Accept <a href="/terms">Terms</a>.'
      #   end
654
      def label(object_name, method, content_or_options = nil, options = nil, &block)
655 656 657
        content_is_options = content_or_options.is_a?(Hash)
        if content_is_options || block_given?
          options = content_or_options if content_is_options
S
Stephen Celis 已提交
658 659
          text = nil
        else
660
          text = content_or_options
S
Stephen Celis 已提交
661 662 663 664
        end

        options ||= {}
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block)
665 666
      end

D
Initial  
David Heinemeier Hansson 已提交
667 668
      # 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
669
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
D
David Heinemeier Hansson 已提交
670
      # shown.
D
Initial  
David Heinemeier Hansson 已提交
671
      #
672
      # ==== Examples
D
David Heinemeier Hansson 已提交
673
      #   text_field(:post, :title, :size => 20)
674 675 676 677 678 679 680 681 682 683 684
      #   # => <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" />
      #
685
      def text_field(object_name, method, options = {})
686
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
D
Initial  
David Heinemeier Hansson 已提交
687 688
      end

689 690
      # 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
691
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
692 693 694 695
      # shown.
      #
      # ==== Examples
      #   password_field(:login, :pass, :size => 20)
696
      #   # => <input type="password" id="login_pass" name="login[pass]" size="20" />
697
      #
698
      #   password_field(:account, :secret, :class => "form_input", :value => @account.secret)
699
      #   # => <input type="password" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
700 701
      #
      #   password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
702
      #   # => <input type="password" id="user_password" name="user[password]" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
703 704
      #
      #   password_field(:account, :pin, :size => 20, :class => 'form_input')
705
      #   # => <input type="password" id="account_pin" name="account[pin]" size="20" class="form_input" />
706
      #
707
      def password_field(object_name, method, options = {})
708
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", { :value => nil }.merge!(options))
D
Initial  
David Heinemeier Hansson 已提交
709 710
      end

711 712
      # 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
713
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
714 715
      # shown.
      #
716
      # ==== Examples
717 718 719 720 721 722 723
      #   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)
724
      #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
725
      def hidden_field(object_name, method, options = {})
726
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
D
Initial  
David Heinemeier Hansson 已提交
727 728
      end

729
      # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object
730
      # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
731
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
732 733
      # shown.
      #
734 735
      # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>.
      #
736 737 738 739 740 741 742 743 744 745
      # ==== Examples
      #   file_field(:user, :avatar)
      #   # => <input type="file" id="user_avatar" name="user[avatar]" />
      #
      #   file_field(:post, :attached, :accept => 'text/html')
      #   # => <input type="file" id="post_attached" name="post[attached]" />
      #
      #   file_field(:attachment, :file, :class => 'file_input')
      #   # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
      #
746
      def file_field(object_name, method, options = {})
747
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options.update({:size => nil}))
748 749
      end

D
Initial  
David Heinemeier Hansson 已提交
750 751 752 753
      # 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+.
      #
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
      # ==== 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')
      #   # => <textarea cols="40" rows="15" id="application_notes" name="application[notes]" class="app_input">
      #   #      #{@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>
774
      def text_area(object_name, method, options = {})
775
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
D
Initial  
David Heinemeier Hansson 已提交
776
      end
777

D
Initial  
David Heinemeier Hansson 已提交
778
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
P
Pratik Naik 已提交
779
      # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
Y
Yehuda Katz 已提交
780 781
      # 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 已提交
782
      # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
P
Pratik Naik 已提交
783 784 785 786 787
      #
      # ==== 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 已提交
788
      # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
P
Pratik Naik 已提交
789 790 791 792 793 794 795
      # 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 已提交
796 797
      # To prevent this the helper generates an auxiliary hidden field before
      # the very check box. The hidden field has the same name and its
798
      # attributes mimic an unchecked check box.
P
Pratik Naik 已提交
799 800 801 802 803 804
      #
      # 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 已提交
805 806 807 808
      #
      # Unfortunately that workaround does not work when the check box goes
      # within an array-like parameter, as in
      #
809
      #   <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
P
Pratik Naik 已提交
810 811 812 813 814
      #     <%= form.check_box :paid %>
      #     ...
      #   <% end %>
      #
      # because parameter name repetition is precisely what Rails seeks to distinguish
P
Pratik Naik 已提交
815 816 817 818 819
      # 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 已提交
820
      #
821
      # ==== Examples
822
      #   # Let's say that @post.validated? is 1:
D
Initial  
David Heinemeier Hansson 已提交
823
      #   check_box("post", "validated")
P
Pratik Naik 已提交
824 825
      #   # => <input name="post[validated]" type="hidden" value="0" />
      #   #    <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
D
Initial  
David Heinemeier Hansson 已提交
826
      #
827
      #   # Let's say that @puppy.gooddog is "no":
D
Initial  
David Heinemeier Hansson 已提交
828
      #   check_box("puppy", "gooddog", {}, "yes", "no")
P
Pratik Naik 已提交
829 830
      #   # => <input name="puppy[gooddog]" type="hidden" value="no" />
      #   #    <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
831
      #
P
Pratik Naik 已提交
832
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
P
Pratik Naik 已提交
833 834
      #   # => <input name="eula[accepted]" type="hidden" value="no" />
      #   #    <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
835
      #
836
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
837
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
838
      end
839 840 841

      # 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 已提交
842 843 844 845
      # 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.
846 847 848
      #
      # ==== Examples
      #   # Let's say that @post.category returns "rails":
849 850
      #   radio_button("post", "category", "rails")
      #   radio_button("post", "category", "java")
P
Pratik Naik 已提交
851 852
      #   # => <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" />
853
      #
854 855
      #   radio_button("user", "receive_newsletter", "yes")
      #   radio_button("user", "receive_newsletter", "no")
P
Pratik Naik 已提交
856 857
      #   # => <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" />
858
      def radio_button(object_name, method, tag_value, options = {})
859
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
860
      end
861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876

      # Returns a text_field of type "search".
      def search_field(object_name, method, options = {})
        options = options.stringify_keys

        if options["autosave"]
          if options["autosave"] == true
            options["autosave"] = request.host.split(".").reverse.join(".")
          end
          options["results"] ||= 10
        end

        if options["onsearch"]
          options["incremental"] = true unless options.has_key?("incremental")
        end

877
        InstanceTag.new(object_name, method, self, options.delete("object")).to_input_field_tag("search", options)
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910
      end

      # Returns a text_field of type "tel".
      def telephone_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("tel", options)
      end
      alias phone_field telephone_field

      # Returns a text_field of type "url".
      def url_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("url", options)
      end

      # Returns a text_field of type "email".
      def email_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("email", options)
      end

      # Returns an input tag of type "number".
      #
      # ==== Options
      # * Accepts same options as number_field_tag
      def number_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("number", options)
      end

      # Returns an input tag of type "range".
      #
      # ==== Options
      # * Accepts same options as range_field_tag
      def range_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_number_field_tag("range", options)
      end
911 912 913

      private

914 915
        def instantiate_builder(record_name, record_object, options, &block)
          case record_name
916 917
          when String, Symbol
            object = record_object
918
            object_name = record_name
919
          else
920
            object = record_name
921 922 923 924 925 926
            object_name = ActiveModel::Naming.param_key(object)
          end

          builder = options[:builder] || ActionView::Base.default_form_builder
          builder.new(object_name, object, self, options, block)
        end
D
Initial  
David Heinemeier Hansson 已提交
927 928
    end

929
    class InstanceTag
S
Stephen Celis 已提交
930
      include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper
D
Initial  
David Heinemeier Hansson 已提交
931

932
      attr_reader :object, :method_name, :object_name
933

A
Aaron Patterson 已提交
934 935 936
      DEFAULT_FIELD_OPTIONS     = { "size" => 30 }
      DEFAULT_RADIO_OPTIONS     = { }
      DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }
D
Initial  
David Heinemeier Hansson 已提交
937

938
      def initialize(object_name, method_name, template_object, object = nil)
939
        @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
940
        @template_object = template_object
941 942 943
        @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
        @object = retrieve_object(object)
        @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) if Regexp.last_match
D
Initial  
David Heinemeier Hansson 已提交
944
      end
945

S
Stephen Celis 已提交
946
      def to_label_tag(text = nil, options = {}, &block)
947
        options = options.stringify_keys
948
        tag_value = options.delete("value")
949
        name_and_id = options.dup
950 951 952 953 954 955 956

        if name_and_id["for"]
          name_and_id["id"] = name_and_id["for"]
        else
          name_and_id.delete("id")
        end

957
        add_default_name_and_id_for_value(tag_value, name_and_id)
958
        options.delete("index")
959
        options["for"] ||= name_and_id["id"]
960

S
Stephen Celis 已提交
961 962
        if block_given?
          label_tag(name_and_id["id"], options, &block)
963
        else
S
Stephen Celis 已提交
964
          content = if text.blank?
965 966
            method_and_value = tag_value.present? ? "#{method_name}.#{tag_value}" : method_name
            I18n.t("helpers.label.#{object_name}.#{method_and_value}", :default => "").presence
S
Stephen Celis 已提交
967 968 969
          else
            text.to_s
          end
970

S
Stephen Celis 已提交
971 972 973
          content ||= if object && object.class.respond_to?(:human_attribute_name)
            object.class.human_attribute_name(method_name)
          end
974

S
Stephen Celis 已提交
975
          content ||= method_name.humanize
976

S
Stephen Celis 已提交
977 978
          label_tag(name_and_id["id"], content, options)
        end
979 980
      end

D
Initial  
David Heinemeier Hansson 已提交
981
      def to_input_field_tag(field_type, options = {})
982
        options = options.stringify_keys
983
        options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
984
        options = DEFAULT_FIELD_OPTIONS.merge(options)
985 986 987
        if field_type == "hidden"
          options.delete("size")
        end
988
        options["type"]  ||= field_type
989
        options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file"
990
        options["value"] &&= ERB::Util.html_escape(options["value"])
991 992 993 994
        add_default_name_and_id(options)
        tag("input", options)
      end

995 996 997 998 999 1000 1001 1002
      def to_number_field_tag(field_type, options = {})
        options = options.stringify_keys
        if range = options.delete("in") || options.delete("within")
          options.update("min" => range.min, "max" => range.max)
        end
        to_input_field_tag(field_type, options)
      end

1003
      def to_radio_button_tag(tag_value, options = {})
1004
        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
1005 1006
        options["type"]     = "radio"
        options["value"]    = tag_value
1007 1008 1009 1010 1011 1012
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
        else
          checked = self.class.radio_button_checked?(value(object), tag_value)
        end
1013
        options["checked"]  = "checked" if checked
1014
        add_default_name_and_id_for_value(tag_value, options)
1015 1016 1017
        tag("input", options)
      end

D
Initial  
David Heinemeier Hansson 已提交
1018
      def to_text_area_tag(options = {})
1019
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
D
Initial  
David Heinemeier Hansson 已提交
1020
        add_default_name_and_id(options)
1021 1022

        if size = options.delete("size")
1023
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
1024 1025
        end

1026
        content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options)
D
Initial  
David Heinemeier Hansson 已提交
1027 1028 1029
      end

      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
1030 1031 1032
        options = options.stringify_keys
        options["type"]     = "checkbox"
        options["value"]    = checked_value
1033 1034 1035
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
1036
        else
1037
          checked = self.class.check_box_checked?(value(object), checked_value)
1038
        end
1039
        options["checked"] = "checked" if checked
1040 1041 1042 1043 1044 1045
        if options["multiple"]
          add_default_name_and_id_for_value(checked_value, options)
          options.delete("multiple")
        else
          add_default_name_and_id(options)
        end
1046 1047
        hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
        checkbox = tag("input", options)
1048
        (hidden + checkbox).html_safe
D
Initial  
David Heinemeier Hansson 已提交
1049 1050 1051
      end

      def to_boolean_select_tag(options = {})
1052
        options = options.stringify_keys
D
Initial  
David Heinemeier Hansson 已提交
1053
        add_default_name_and_id(options)
1054
        value = value(object)
D
Initial  
David Heinemeier Hansson 已提交
1055 1056 1057 1058 1059 1060 1061 1062
        tag_text = "<select"
        tag_text << tag_options(options)
        tag_text << "><option value=\"false\""
        tag_text << " selected" if value == false
        tag_text << ">False</option><option value=\"true\""
        tag_text << " selected" if value
        tag_text << ">True</option></select>"
      end
1063

1064
      def to_content_tag(tag_name, options = {})
1065
        content_tag(tag_name, value(object), options)
1066
      end
1067

1068 1069 1070
      def retrieve_object(object)
        if object
          object
1071 1072 1073
        elsif @template_object.instance_variable_defined?("@#{@object_name}")
          @template_object.instance_variable_get("@#{@object_name}")
        end
1074
      rescue NameError
1075
        # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
1076
        nil
D
Initial  
David Heinemeier Hansson 已提交
1077 1078
      end

1079 1080 1081 1082 1083 1084 1085 1086 1087
      def retrieve_autoindex(pre_match)
        object = self.object || @template_object.instance_variable_get("@#{pre_match}")
        if object && object.respond_to?(:to_param)
          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

1088 1089
      def value(object)
        self.class.value(object, @method_name)
D
Initial  
David Heinemeier Hansson 已提交
1090 1091
      end

1092 1093 1094
      def value_before_type_cast(object)
        self.class.value_before_type_cast(object, @method_name)
      end
1095

1096
      class << self
1097
        def value(object, method_name)
N
Neeraj Singh 已提交
1098
          object.send method_name if object
1099
        end
1100

1101 1102
        def value_before_type_cast(object, method_name)
          unless object.nil?
1103 1104 1105
            object.respond_to?(method_name + "_before_type_cast") ?
            object.send(method_name + "_before_type_cast") :
            object.send(method_name)
1106 1107
          end
        end
1108

1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
        def check_box_checked?(value, checked_value)
          case value
          when TrueClass, FalseClass
            value
          when NilClass
            false
          when Integer
            value != 0
          when String
            value == checked_value
1119 1120
          when Array
            value.include?(checked_value)
1121 1122 1123 1124
          else
            value.to_i != 0
          end
        end
1125

1126 1127
        def radio_button_checked?(value, checked_value)
          value.to_s == checked_value.to_s
1128
        end
1129 1130
      end

D
Initial  
David Heinemeier Hansson 已提交
1131
      private
1132
        def add_default_name_and_id_for_value(tag_value, options)
1133
          unless tag_value.nil?
1134
            pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
1135 1136
            specified_id = options["id"]
            add_default_name_and_id(options)
1137
            options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present?
1138 1139 1140 1141 1142
          else
            add_default_name_and_id(options)
          end
        end

D
Initial  
David Heinemeier Hansson 已提交
1143
        def add_default_name_and_id(options)
1144 1145
          if options.has_key?("index")
            options["name"] ||= tag_name_with_index(options["index"])
1146
            options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) }
1147
            options.delete("index")
1148
          elsif defined?(@auto_index)
1149
            options["name"] ||= tag_name_with_index(@auto_index)
1150
            options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) }
1151
          else
1152
            options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
1153
            options["id"] = options.fetch("id"){ tag_id }
1154
          end
D
Initial  
David Heinemeier Hansson 已提交
1155
        end
1156

D
Initial  
David Heinemeier Hansson 已提交
1157
        def tag_name
1158
          "#{@object_name}[#{sanitized_method_name}]"
D
Initial  
David Heinemeier Hansson 已提交
1159
        end
1160

1161
        def tag_name_with_index(index)
1162
          "#{@object_name}[#{index}][#{sanitized_method_name}]"
1163
        end
D
Initial  
David Heinemeier Hansson 已提交
1164 1165

        def tag_id
1166
          "#{sanitized_object_name}_#{sanitized_method_name}"
D
Initial  
David Heinemeier Hansson 已提交
1167
        end
1168 1169

        def tag_id_with_index(index)
1170
          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
1171 1172 1173
        end

        def sanitized_object_name
1174
          @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
1175 1176 1177 1178
        end

        def sanitized_method_name
          @sanitized_method_name ||= @method_name.sub(/\?$/,"")
1179
        end
D
Initial  
David Heinemeier Hansson 已提交
1180
    end
1181

1182
    class FormBuilder
1183
      # The methods which wrap a form helper call.
1184
      class_attribute :field_helpers
J
José Valim 已提交
1185
      self.field_helpers = FormHelper.instance_method_names - %w(form_for convert_to_model)
1186

1187
      attr_accessor :object_name, :object, :options
1188

1189
      attr_reader :multipart, :parent_builder
1190 1191
      alias :multipart? :multipart

1192 1193 1194 1195 1196
      def multipart=(multipart)
        @multipart = multipart
        parent_builder.multipart = multipart if parent_builder
      end

Y
Yehuda Katz 已提交
1197 1198 1199 1200
      def self.model_name
        @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
      end

1201 1202 1203 1204
      def to_model
        self
      end

1205
      def initialize(object_name, object, template, options, proc)
1206
        @nested_child_index = {}
1207
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
1208
        @parent_builder = options[:parent_builder]
1209
        @default_options = @options ? @options.slice(:index) : {}
1210 1211 1212 1213 1214 1215 1216
        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
1217
        @multipart = nil
1218
      end
1219

1220
      (field_helpers - %w(label check_box radio_button fields_for hidden_field file_field)).each do |selector|
1221
        class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
1222 1223 1224 1225 1226 1227 1228
          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
1229
        RUBY_EVAL
1230
      end
1231

1232
      # Check +fields_for+ for docs and examples.
1233 1234 1235 1236 1237 1238
      def fields_for_with_index(record_name, record_object = nil, fields_options = {}, &block)
        index = fields_options[:index] || options[:child_index] || nested_child_index(@object_name)
        block_with_index = Proc.new{ |obj| block.call(obj, index) }
        fields_for(record_name, record_object, fields_options, &block_with_index)
      end

1239 1240 1241 1242
      def fields_for(record_name, record_object = nil, fields_options = {}, &block)
        fields_options, record_object = record_object, nil if record_object.is_a?(Hash)
        fields_options[:builder] ||= options[:builder]
        fields_options[:parent_builder] = self
1243

1244
        case record_name
1245
        when String, Symbol
1246 1247
          if nested_attributes_association?(record_name)
            return fields_for_with_nested_attributes(record_name, record_object, fields_options, block)
1248
          end
1249
        else
1250 1251 1252 1253 1254 1255 1256 1257 1258
          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)
          "[#{options[:index]}]"
        elsif defined?(@auto_index)
          self.object_name = @object_name.to_s.sub(/\[\]$/,"")
          "[#{@auto_index}]"
1259
        end
1260
        record_name = "#{object_name}#{index}[#{record_name}]"
1261

1262
        @template.fields_for(record_name, record_object, fields_options, &block)
1263
      end
1264

S
Stephen Celis 已提交
1265 1266
      def label(method, text = nil, options = {}, &block)
        @template.label(@object_name, method, text, objectify_options(options), &block)
1267
      end
1268

1269
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
1270
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
1271
      end
1272

1273
      def radio_button(method, tag_value, options = {})
1274
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1275
      end
1276

1277 1278 1279 1280
      def hidden_field(method, options = {})
        @emitted_hidden_id = true if method == :id
        @template.hidden_field(@object_name, method, objectify_options(options))
      end
1281

1282
      def file_field(method, options = {})
1283
        self.multipart = true
1284 1285
        @template.file_field(@object_name, method, objectify_options(options))
      end
1286

1287 1288 1289
      # 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:
      #
1290
      #   <%= form_for @post do |f| %>
1291 1292
      #     <%= f.submit %>
      #   <% end %>
1293
      #
1294 1295 1296 1297
      # 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
1298
      # the %{model} as translation interpolation:
1299 1300 1301 1302
      #
      #   en:
      #     helpers:
      #       submit:
1303 1304
      #         create: "Create a %{model}"
      #         update: "Confirm changes to %{model}"
1305
      #
1306 1307 1308 1309 1310 1311
      # It also searches for a key specific for the given object:
      #
      #   en:
      #     helpers:
      #       submit:
      #         post:
1312
      #           create: "Add %{model}"
1313
      #
1314 1315
      def submit(value=nil, options={})
        value, options = nil, value if value.is_a?(Hash)
1316
        value ||= submit_default_value
1317
        @template.submit_tag(value, options)
1318
      end
1319

1320
      def emitted_hidden_id?
1321
        @emitted_hidden_id ||= nil
1322 1323
      end

1324 1325 1326 1327
      private
        def objectify_options(options)
          @default_options.merge(options.merge(:object => @object))
        end
1328

1329
        def submit_default_value
1330
          object = convert_to_model(@object)
1331
          key    = object ? (object.persisted? ? :update : :create) : :submit
1332 1333 1334 1335 1336 1337 1338

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

1339 1340 1341 1342 1343 1344
          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)
1345 1346
        end

1347 1348 1349 1350
        def nested_attributes_association?(association_name)
          @object.respond_to?("#{association_name}_attributes=")
        end

1351
        def fields_for_with_nested_attributes(association_name, association, options, block)
1352
          name = "#{object_name}[#{association_name}_attributes]"
1353
          association = convert_to_model(association)
1354

1355
          if association.respond_to?(:persisted?)
1356
            association = [association] if @object.send(association_name).is_a?(Array)
1357
          elsif !association.respond_to?(:to_ary)
1358 1359
            association = @object.send(association_name)
          end
1360

1361
          if association.respond_to?(:to_ary)
1362
            explicit_child_index = options[:child_index]
W
wycats 已提交
1363 1364 1365 1366 1367
            output = ActiveSupport::SafeBuffer.new
            association.each do |child|
              output << fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, options, block)
            end
            output
1368
          elsif association
1369
            fields_for_nested_model(name, association, options, block)
1370 1371 1372
          end
        end

1373
        def fields_for_nested_model(name, object, options, block)
1374
          object = convert_to_model(object)
1375

1376 1377 1378
          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
1379
          @template.fields_for(name, object, options, &block)
1380 1381
        end

1382 1383 1384
        def nested_child_index(name)
          @nested_child_index[name] ||= -1
          @nested_child_index[name] += 1
1385
        end
1386 1387 1388 1389

        def convert_to_model(object)
          object.respond_to?(:to_model) ? object.to_model : object
        end
1390
    end
D
Initial  
David Heinemeier Hansson 已提交
1391
  end
1392

1393
  ActiveSupport.on_load(:action_view) do
1394 1395 1396 1397
    class ActionView::Base
      cattr_accessor :default_form_builder
      @@default_form_builder = ::ActionView::Helpers::FormBuilder
    end
1398
  end
J
Jeremy Kemper 已提交
1399
end