form_helper.rb 45.4 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'
J
Jeremy Kemper 已提交
5
require 'active_support/core_ext/class/inheritable_attributes'
6
require 'active_support/core_ext/hash/slice'
D
Initial  
David Heinemeier Hansson 已提交
7 8 9

module ActionView
  module Helpers
10 11 12 13 14 15 16 17
    # Form helpers are designed to make working with models much easier
    # compared to using just standard HTML elements by providing a set of
    # methods for creating forms based on your models. This helper generates
    # the HTML for forms, providing a method for each sort of input
    # (e.g., text, password, select, and so on). When the form is submitted
    # (i.e., when the user hits the submit button or <tt>form.submit</tt> is
    # called via JavaScript), the form inputs will be bundled into the
    # <tt>params</tt> object and passed back to the controller.
D
David Heinemeier Hansson 已提交
18
    #
19 20 21 22 23
    # There are two types of form helpers: those that specifically work with
    # model attributes and those that don't. This helper deals with those that
    # work with model attributes; to see an example of form helpers that don't
    # work with model attributes, check the ActionView::Helpers::FormTagHelper
    # documentation.
D
David Heinemeier Hansson 已提交
24
    #
25 26 27
    # The core method of this helper, form_for, gives you the ability to create
    # a form for a model instance; for example, let's say that you have a model
    # <tt>Person</tt> and want to create a new instance of it:
D
Initial  
David Heinemeier Hansson 已提交
28
    #
29
    #     # Note: a @person variable will have been created in the controller.
30 31
    #     # For example: @person = Person.new
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
D
David Heinemeier Hansson 已提交
32 33
    #       <%= f.text_field :first_name %>
    #       <%= f.text_field :last_name %>
34
    #       <%= submit_tag 'Create' %>
D
David Heinemeier Hansson 已提交
35
    #     <% end %>
D
Initial  
David Heinemeier Hansson 已提交
36
    #
37
    # The HTML generated for this would be:
D
Initial  
David Heinemeier Hansson 已提交
38
    #
39
    #     <form action="/persons/create" method="post">
D
David Heinemeier Hansson 已提交
40 41
    #       <input id="person_first_name" name="person[first_name]" size="30" type="text" />
    #       <input id="person_last_name" name="person[last_name]" size="30" type="text" />
42
    #       <input name="commit" type="submit" value="Create" />
D
David Heinemeier Hansson 已提交
43
    #     </form>
D
Initial  
David Heinemeier Hansson 已提交
44
    #
45 46 47 48 49 50 51
    # If you are using a partial for your form fields, you can use this shortcut:
    #
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
    #       <%= render :partial => f %>
    #       <%= submit_tag 'Create' %>
    #     <% end %>
    #
52 53 54 55
    # This example will render the <tt>people/_form</tt> partial, setting a
    # local variable called <tt>form</tt> which references the yielded
    # FormBuilder. The <tt>params</tt> object created when this form is
    # submitted would look like:
D
Initial  
David Heinemeier Hansson 已提交
56
    #
57
    #     {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
58
    #
59 60 61 62 63 64
    # The params hash has a nested <tt>person</tt> value, which can therefore
    # be accessed with <tt>params[:person]</tt> in the controller. If were
    # editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than
    # <tt>Person.new</tt> in the controller), the objects attribute values are
    # filled into the form (e.g., the <tt>person_first_name</tt> field would
    # have that person's first name in it).
65
    #
66 67
    # If the object name contains square brackets the id for the object will be
    # inserted. For example:
68
    #
69
    #   <%= text_field "person[]", "name" %>
70
    #
71
    # ...will generate the following ERb.
72 73 74
    #
    #   <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
    #
75 76 77 78
    # If the helper is being used to generate a repetitive sequence of similar
    # form elements, for example in a partial used by
    # <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may
    # come in handy. Example:
79 80 81
    #
    #   <%= text_field "person", "name", "index" => 1 %>
    #
82
    # ...becomes...
83
    #
84 85
    #   <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
    #
86 87 88
    # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and
    # <tt>fields_for</tt>.  This automatically applies the <tt>index</tt> to
    # all the nested fields.
89
    #
90 91 92 93
    # There are also methods for helping to build form tags in
    # link:classes/ActionView/Helpers/FormOptionsHelper.html,
    # link:classes/ActionView/Helpers/DateHelper.html, and
    # link:classes/ActionView/Helpers/ActiveRecordHelper.html
D
Initial  
David Heinemeier Hansson 已提交
94
    module FormHelper
95 96
      # Creates a form and a scope around a specific model object that is used
      # as a base for questioning about values for the fields.
97
      #
P
Pratik Naik 已提交
98
      # Rails provides succinct resource-oriented form generation with +form_for+
99 100 101 102 103 104 105
      # like this:
      #
      #   <% form_for @offer do |f| %>
      #     <%= f.label :version, 'Version' %>:
      #     <%= f.text_field :version %><br />
      #     <%= f.label :author, 'Author' %>:
      #     <%= f.text_field :author %><br />
106 107
      #   <% end %>
      #
108 109 110 111
      # 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.
112
      #
113 114
      # === Generic form_for
      #
115 116
      # The generic way to call +form_for+ yields a form builder around a
      # model:
117
      #
P
Pratik Naik 已提交
118
      #   <% form_for :person, :url => { :action => "update" } do |f| %>
119 120 121 122 123 124
      #     <%= f.error_messages %>
      #     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 />
      #   <% end %>
125
      #
P
Pratik Naik 已提交
126
      # There, the first argument is a symbol or string with the name of the
127 128
      # object the form is about, and also the name of the instance variable
      # the object is stored in.
P
Pratik Naik 已提交
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
      #
      # 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 %>
      #
      # If the instance variable is not <tt>@person</tt> you can pass the actual
      # record as the second argument:
      #
      #   <% form_for :person, person, :url => { :action => "update" } do |f| %>
      #     ...
      #   <% end %>
      #
      # In that case you can think
      #
      #   <%= f.text_field :first_name %>
      #
      # gets expanded to
      #
      #   <%= text_field :person, :first_name, :object => person %>
      #
      # You can even display error messages of the wrapped model this way:
      #
      #   <%= f.error_messages %>
      #
      # In any of its variants, the rightmost argument to +form_for+ is an
      # optional hash of options:
      #
161 162 163
      # * <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 已提交
164 165
      # * <tt>:html</tt> - Optional HTML attributes for the form tag.
      #
166 167 168
      # Worth noting is that the +form_for+ tag is called in a ERb evaluation
      # block, not an ERb output block. So that's <tt><% %></tt>, not
      # <tt><%= %></tt>.
169 170
      #
      # Also note that +form_for+ doesn't create an exclusive scope. It's still
171 172
      # possible to use both the stand-alone FormHelper methods and methods
      # from FormTagHelper. For example:
173
      #
174
      #   <% form_for :person, @person, :url => { :action => "update" } do |f| %>
175 176 177 178 179 180
      #     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? %>
      #   <% end %>
      #
181 182 183
      # 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.
184
      #
P
Pratik Naik 已提交
185
      # === Resource-oriented style
186
      #
187 188 189 190
      # 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 已提交
191 192
      #
      # For example, if <tt>@post</tt> is an existing record you want to edit
193
      #
P
Pratik Naik 已提交
194
      #   <% form_for @post do |f| %>
195 196 197
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
198
      # is equivalent to something like:
199 200 201 202 203
      #
      #   <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
204
      # And for new records
205 206 207 208 209
      #
      #   <% form_for(Post.new) do |f| %>
      #     ...
      #   <% end %>
      #
P
Pratik Naik 已提交
210
      # expands to
211
      #
212
      #   <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
213 214 215 216 217 218 219 220 221
      #     ...
      #   <% end %>
      #
      # You can also overwrite the individual conventions, like this:
      #
      #   <% form_for(@post, :url => super_post_path(@post)) do |f| %>
      #     ...
      #   <% end %>
      #
222
      # And for namespaced routes, like +admin_post_url+:
223 224 225 226 227
      #
      #   <% form_for([:admin, @post]) do |f| %>
      #    ...
      #   <% end %>
      #
228 229
      # === Customized form builders
      #
230 231 232 233
      # 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.
234
      #
235 236 237 238 239 240
      #   <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
      #     <%= f.text_field :first_name %>
      #     <%= f.text_field :last_name %>
      #     <%= text_area :person, :biography %>
      #     <%= check_box_tag "person[admin]", @person.company.admin? %>
      #   <% end %>
241
      #
242 243 244 245
      # In this case, if you use this:
      #
      #   <%= render :partial => f %>
      #
246 247 248 249 250 251
      # 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
      # of a nested fields_for call, unless it's explicitely set.
252
      #
253 254
      # In many cases you will want to wrap the above in another helper, so you
      # could do something like the following:
255
      #
256 257
      #   def labelled_form_for(record_or_name_or_array, *args, &proc)
      #     options = args.extract_options!
258
      #     form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
259 260
      #   end
      #
261 262
      # If you don't need to attach a form to a model instance, then check out
      # FormTagHelper#form_tag.
263
      def form_for(record_or_name_or_array, *args, &proc)
264
        raise ArgumentError, "Missing block" unless block_given?
265

266
        options = args.extract_options!
267

268
        case record_or_name_or_array
269
        when String, Symbol
270
          object_name = record_or_name_or_array
271
        when Array
272
          object = record_or_name_or_array.last
273
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
274
          apply_form_for_options!(record_or_name_or_array, options)
275
          args.unshift object
276
        else
277
          object = record_or_name_or_array
278
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
279
          apply_form_for_options!([object], options)
280
          args.unshift object
281 282
        end

283
        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}))
284
        fields_for(object_name, *(args << options), &proc)
285
        concat('</form>')
286
      end
287

288 289
      def apply_form_for_options!(object_or_array, options) #:nodoc:
        object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
290

291 292 293 294 295 296 297
        html_options =
          if object.respond_to?(:new_record?) && object.new_record?
            { :class  => dom_class(object, :new),  :id => dom_id(object), :method => :post }
          else
            { :class  => dom_class(object, :edit), :id => dom_id(object, :edit), :method => :put }
          end

298 299
        options[:html] ||= {}
        options[:html].reverse_merge!(html_options)
300
        options[:url] ||= polymorphic_path(object_or_array)
301
      end
302

303 304 305 306 307
      # 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
308
      #
309
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
310 311
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
312
      #
313
      #     <% fields_for @person.permission do |permission_fields| %>
314 315 316 317
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
      #   <% end %>
      #
318 319
      # ...or if you have an object that needs to be represented as a different
      # parameter, like a Client that acts as a Person:
320 321 322 323 324
      #
      #   <% fields_for :person, @client do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
325
      # ...or if you don't have an object, just a name of the parameter:
326 327 328 329 330
      #
      #   <% fields_for :person do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 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 465 466 467 468 469 470 471 472 473 474 475 476 477
      # 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:
      #
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
      #     ...
      #     <% person_form.fields_for :address do |address_fields| %>
      #       Street  : <%= address_fields.text_field :street %>
      #       Zip code: <%= address_fields.text_field :zip_code %>
      #     <% end %>
      #   <% 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
      #
      # Now, when you use a form element with the <tt>_delete</tt> parameter,
      # with a value that evaluates to +true+, you will destroy the associated
      # model (eg. 1, '1', true, or 'true'):
      #
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
      #     ...
      #     <% person_form.fields_for :address do |address_fields| %>
      #       ...
      #       Delete: <%= address_fields.check_box :_delete %>
      #     <% end %>
      #   <% 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:
      #
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
      #     ...
      #     <% person_form.fields_for :projects do |project_fields| %>
      #       <% if project_fields.object.active? %>
      #         Name: <%= project_fields.text_field :name %>
      #       <% end %>
      #     <% end %>
      #   <% end %>
      #
      # It's also possible to specify the instance to be used:
      #
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
      #     ...
      #     <% @person.projects.each do |project| %>
      #       <% if project.active? %>
      #         <% person_form.fields_for :projects, project do |project_fields| %>
      #           Name: <%= project_fields.text_field :name %>
      #         <% end %>
      #       <% end %>
      #     <% end %>
      #   <% end %>
      #
      # 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
      # attributes hash by adding a form element for the <tt>_delete</tt>
      # parameter with a value that evaluates to +true+
      # (eg. 1, '1', true, or 'true'):
      #
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
      #     ...
      #     <% person_form.fields_for :projects do |project_fields| %>
      #       Delete: <%= project_fields.check_box :_delete %>
      #     <% end %>
      #   <% end %>
478
      def fields_for(record_or_name_or_array, *args, &block)
479
        raise ArgumentError, "Missing block" unless block_given?
480
        options = args.extract_options!
481 482 483 484 485 486 487 488 489

        case record_or_name_or_array
        when String, Symbol
          object_name = record_or_name_or_array
          object = args.first
        else
          object = record_or_name_or_array
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
        end
490 491 492

        builder = options[:builder] || ActionView::Base.default_form_builder
        yield builder.new(object_name, object, self, options, block)
493 494
      end

495 496 497
      # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
      # it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
498 499
      # 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).
500 501 502
      #
      # ==== Examples
      #   label(:post, :title)
P
Pratik Naik 已提交
503
      #   # => <label for="post_title">Title</label>
504 505
      #
      #   label(:post, :title, "A short title")
P
Pratik Naik 已提交
506
      #   # => <label for="post_title">A short title</label>
507 508
      #
      #   label(:post, :title, "A short title", :class => "title_label")
P
Pratik Naik 已提交
509
      #   # => <label for="post_title" class="title_label">A short title</label>
510
      #
511 512 513
      #   label(:post, :privacy, "Public Post", :value => "public")
      #   # => <label for="post_privacy_public">Public Post</label>
      #
514
      def label(object_name, method, text = nil, options = {})
515
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
516 517
      end

D
Initial  
David Heinemeier Hansson 已提交
518 519
      # 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
520
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
D
David Heinemeier Hansson 已提交
521
      # shown.
D
Initial  
David Heinemeier Hansson 已提交
522
      #
523
      # ==== Examples
D
David Heinemeier Hansson 已提交
524
      #   text_field(:post, :title, :size => 20)
525 526 527 528 529 530 531 532 533 534 535
      #   # => <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" />
      #
536
      def text_field(object_name, method, options = {})
537
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("text", options)
D
Initial  
David Heinemeier Hansson 已提交
538 539
      end

540 541
      # 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
542
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
      # shown.
      #
      # ==== Examples
      #   password_field(:login, :pass, :size => 20)
      #   # => <input type="text" id="login_pass" name="login[pass]" size="20" value="#{@login.pass}" />
      #
      #   password_field(:account, :secret, :class => "form_input")
      #   # => <input type="text" id="account_secret" name="account[secret]" value="#{@account.secret}" class="form_input" />
      #
      #   password_field(:user, :password, :onchange => "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }")
      #   # => <input type="text" id="user_password" name="user[password]" value="#{@user.password}" onchange = "if $('user[password]').length > 30 { alert('Your password needs to be shorter!'); }"/>
      #
      #   password_field(:account, :pin, :size => 20, :class => 'form_input')
      #   # => <input type="text" id="account_pin" name="account[pin]" size="20" value="#{@account.pin}" class="form_input" />
      #
558
      def password_field(object_name, method, options = {})
559
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
D
Initial  
David Heinemeier Hansson 已提交
560 561
      end

562 563
      # 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
564
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
565 566
      # shown.
      #
567
      # ==== Examples
568 569 570 571 572 573 574
      #   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)
575
      #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
576
      def hidden_field(object_name, method, options = {})
577
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("hidden", options)
D
Initial  
David Heinemeier Hansson 已提交
578 579
      end

580 581
      # Returns an file upload 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
582
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
583 584 585 586 587 588 589 590 591 592 593 594
      # shown.
      #
      # ==== 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" />
      #
595
      def file_field(object_name, method, options = {})
596
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("file", options)
597 598
      end

D
Initial  
David Heinemeier Hansson 已提交
599 600 601 602
      # 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+.
      #
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
      # ==== 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>
623
      def text_area(object_name, method, options = {})
624
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_text_area_tag(options)
D
Initial  
David Heinemeier Hansson 已提交
625
      end
626

D
Initial  
David Heinemeier Hansson 已提交
627
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
P
Pratik Naik 已提交
628 629 630 631
      # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
      # 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 
      # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
P
Pratik Naik 已提交
632 633 634 635 636
      #
      # ==== 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 已提交
637
      # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
P
Pratik Naik 已提交
638 639 640 641 642 643 644
      # 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 已提交
645 646 647 648 649 650 651 652 653
      # To prevent this the helper generates an auxiliary hidden field before
      # the very check box. The hidden field has the same name and its
      # attributes mimick an unchecked check box.
      #
      # 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 已提交
654 655 656 657 658 659 660 661 662 663
      #
      # Unfortunately that workaround does not work when the check box goes
      # within an array-like parameter, as in
      #
      #   <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
      #     <%= form.check_box :paid %>
      #     ...
      #   <% end %>
      #
      # because parameter name repetition is precisely what Rails seeks to distinguish
P
Pratik Naik 已提交
664 665 666 667 668
      # 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 已提交
669
      #
670
      # ==== Examples
671
      #   # Let's say that @post.validated? is 1:
D
Initial  
David Heinemeier Hansson 已提交
672
      #   check_box("post", "validated")
P
Pratik Naik 已提交
673 674
      #   # => <input name="post[validated]" type="hidden" value="0" />
      #   #    <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
D
Initial  
David Heinemeier Hansson 已提交
675
      #
676
      #   # Let's say that @puppy.gooddog is "no":
D
Initial  
David Heinemeier Hansson 已提交
677
      #   check_box("puppy", "gooddog", {}, "yes", "no")
P
Pratik Naik 已提交
678 679
      #   # => <input name="puppy[gooddog]" type="hidden" value="no" />
      #   #    <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
680
      #
P
Pratik Naik 已提交
681
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
P
Pratik Naik 已提交
682 683
      #   # => <input name="eula[accepted]" type="hidden" value="no" />
      #   #    <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
684
      #
685
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
686
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
687
      end
688 689 690

      # 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 已提交
691 692 693 694
      # 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.
695 696 697
      #
      # ==== Examples
      #   # Let's say that @post.category returns "rails":
698 699
      #   radio_button("post", "category", "rails")
      #   radio_button("post", "category", "java")
P
Pratik Naik 已提交
700 701
      #   # => <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" />
702
      #
703 704
      #   radio_button("user", "receive_newsletter", "yes")
      #   radio_button("user", "receive_newsletter", "no")
P
Pratik Naik 已提交
705 706
      #   # => <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" />
707
      def radio_button(object_name, method, tag_value, options = {})
708
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_radio_button_tag(tag_value, options)
709
      end
D
Initial  
David Heinemeier Hansson 已提交
710 711 712
    end

    class InstanceTag #:nodoc:
713
      include Helpers::TagHelper, Helpers::FormTagHelper
D
Initial  
David Heinemeier Hansson 已提交
714 715

      attr_reader :method_name, :object_name
716 717

      DEFAULT_FIELD_OPTIONS     = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
718
      DEFAULT_RADIO_OPTIONS     = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
719
      DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
D
Initial  
David Heinemeier Hansson 已提交
720

721
      def initialize(object_name, method_name, template_object, object = nil)
722
        @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
723
        @template_object = template_object
724
        @object = object
725 726
        if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]")
          if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
727
            @auto_index = object.to_param
728
          else
729
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
730
          end
731
        end
D
Initial  
David Heinemeier Hansson 已提交
732
      end
733

734
      def to_label_tag(text = nil, options = {})
735
        options = options.stringify_keys
736
        tag_value = options.delete("value")
737
        name_and_id = options.dup
738
        add_default_name_and_id_for_value(tag_value, name_and_id)
739
        options.delete("index")
740
        options["for"] ||= name_and_id["id"]
741
        content = (text.blank? ? nil : text.to_s) || method_name.humanize
742
        label_tag(name_and_id["id"], content, options)
743 744
      end

D
Initial  
David Heinemeier Hansson 已提交
745
      def to_input_field_tag(field_type, options = {})
746
        options = options.stringify_keys
747
        options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
748
        options = DEFAULT_FIELD_OPTIONS.merge(options)
749 750 751 752
        if field_type == "hidden"
          options.delete("size")
        end
        options["type"] = field_type
753
        options["value"] ||= value_before_type_cast(object) unless field_type == "file"
754
        options["value"] &&= html_escape(options["value"])
755 756 757 758 759
        add_default_name_and_id(options)
        tag("input", options)
      end

      def to_radio_button_tag(tag_value, options = {})
760
        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
761 762
        options["type"]     = "radio"
        options["value"]    = tag_value
763 764 765 766 767 768
        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
769
        options["checked"]  = "checked" if checked
770
        add_default_name_and_id_for_value(tag_value, options)
771 772 773
        tag("input", options)
      end

D
Initial  
David Heinemeier Hansson 已提交
774
      def to_text_area_tag(options = {})
775
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
D
Initial  
David Heinemeier Hansson 已提交
776
        add_default_name_and_id(options)
777 778

        if size = options.delete("size")
779
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
780 781
        end

782
        content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
D
Initial  
David Heinemeier Hansson 已提交
783 784 785
      end

      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
786 787 788
        options = options.stringify_keys
        options["type"]     = "checkbox"
        options["value"]    = checked_value
789 790 791
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
792
        else
793
          checked = self.class.check_box_checked?(value(object), checked_value)
794
        end
795
        options["checked"] = "checked" if checked
D
Initial  
David Heinemeier Hansson 已提交
796
        add_default_name_and_id(options)
797 798 799
        hidden = tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
        checkbox = tag("input", options)
        hidden + checkbox
D
Initial  
David Heinemeier Hansson 已提交
800 801 802
      end

      def to_boolean_select_tag(options = {})
803
        options = options.stringify_keys
D
Initial  
David Heinemeier Hansson 已提交
804
        add_default_name_and_id(options)
805
        value = value(object)
D
Initial  
David Heinemeier Hansson 已提交
806 807 808 809 810 811 812 813
        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
814

815
      def to_content_tag(tag_name, options = {})
816
        content_tag(tag_name, value(object), options)
817
      end
818

D
Initial  
David Heinemeier Hansson 已提交
819
      def object
820 821 822 823 824
        @object || @template_object.instance_variable_get("@#{@object_name}")
      rescue NameError
        # As @object_name may contain the nested syntax (item[subobject]) we
        # need to fallback to nil.
        nil
D
Initial  
David Heinemeier Hansson 已提交
825 826
      end

827 828
      def value(object)
        self.class.value(object, @method_name)
D
Initial  
David Heinemeier Hansson 已提交
829 830
      end

831 832 833
      def value_before_type_cast(object)
        self.class.value_before_type_cast(object, @method_name)
      end
834

835 836 837 838
      class << self
        def value(object, method_name)
          object.send method_name unless object.nil?
        end
839

840 841 842 843 844 845 846
        def value_before_type_cast(object, method_name)
          unless object.nil?
            object.respond_to?(method_name + "_before_type_cast") ?
            object.send(method_name + "_before_type_cast") :
            object.send(method_name)
          end
        end
847

848 849 850 851 852 853 854 855 856 857
        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
858 859
          when Array
            value.include?(checked_value)
860 861 862 863
          else
            value.to_i != 0
          end
        end
864

865 866
        def radio_button_checked?(value, checked_value)
          value.to_s == checked_value.to_s
867
        end
868 869
      end

D
Initial  
David Heinemeier Hansson 已提交
870
      private
871 872 873 874 875 876 877 878 879 880 881
        def add_default_name_and_id_for_value(tag_value, options)
          if tag_value
            pretty_tag_value    = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
            specified_id = options["id"]
            add_default_name_and_id(options)
            options["id"] += "_#{pretty_tag_value}" unless specified_id
          else
            add_default_name_and_id(options)
          end
        end

D
Initial  
David Heinemeier Hansson 已提交
882
        def add_default_name_and_id(options)
883 884 885
          if options.has_key?("index")
            options["name"] ||= tag_name_with_index(options["index"])
            options["id"]   ||= tag_id_with_index(options["index"])
886
            options.delete("index")
887
          elsif defined?(@auto_index)
888 889
            options["name"] ||= tag_name_with_index(@auto_index)
            options["id"]   ||= tag_id_with_index(@auto_index)
890
          else
891
            options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
892
            options["id"]   ||= tag_id
893
          end
D
Initial  
David Heinemeier Hansson 已提交
894
        end
895

D
Initial  
David Heinemeier Hansson 已提交
896
        def tag_name
897
          "#{@object_name}[#{sanitized_method_name}]"
D
Initial  
David Heinemeier Hansson 已提交
898
        end
899

900
        def tag_name_with_index(index)
901
          "#{@object_name}[#{index}][#{sanitized_method_name}]"
902
        end
D
Initial  
David Heinemeier Hansson 已提交
903 904

        def tag_id
905
          "#{sanitized_object_name}_#{sanitized_method_name}"
D
Initial  
David Heinemeier Hansson 已提交
906
        end
907 908

        def tag_id_with_index(index)
909
          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
910 911 912
        end

        def sanitized_object_name
913
          @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
914 915 916 917
        end

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

921
    class FormBuilder #:nodoc:
922 923 924
      # The methods which wrap a form helper call.
      class_inheritable_accessor :field_helpers
      self.field_helpers = (FormHelper.instance_methods - ['form_for'])
925

926
      attr_accessor :object_name, :object, :options
927

928
      def initialize(object_name, object, template, options, proc)
929
        @nested_child_index = {}
930
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
931
        @default_options = @options ? @options.slice(:index) : {}
932 933 934 935 936 937 938
        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
939
      end
940

941
      (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
942
        src = <<-end_src
943 944 945 946 947 948 949
          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
950 951 952
        end_src
        class_eval src, __FILE__, __LINE__
      end
953

954
      def fields_for(record_or_name_or_array, *args, &block)
955 956 957 958 959 960 961 962 963
        if options.has_key?(:index)
          index = "[#{options[:index]}]"
        elsif defined?(@auto_index)
          self.object_name = @object_name.to_s.sub(/\[\]$/,"")
          index = "[#{@auto_index}]"
        else
          index = ""
        end

964 965 966 967 968
        if options[:builder]
          args << {} unless args.last.is_a?(Hash)
          args.last[:builder] ||= options[:builder]
        end

969 970
        case record_or_name_or_array
        when String, Symbol
971 972 973 974 975
          if nested_attributes_association?(record_or_name_or_array)
            return fields_for_with_nested_attributes(record_or_name_or_array, args, block)
          else
            name = "#{object_name}#{index}[#{record_or_name_or_array}]"
          end
976 977
        when Array
          object = record_or_name_or_array.last
978
          name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
979 980 981
          args.unshift(object)
        else
          object = record_or_name_or_array
982
          name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
983 984
          args.unshift(object)
        end
985

986 987
        @template.fields_for(name, *args, &block)
      end
988 989

      def label(method, text = nil, options = {})
990
        @template.label(@object_name, method, text, objectify_options(options))
991
      end
992

993
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
994
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
995
      end
996

997
      def radio_button(method, tag_value, options = {})
998
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
999
      end
1000

1001 1002
      def error_message_on(method, *args)
        @template.error_message_on(@object, method, *args)
1003
      end
1004 1005

      def error_messages(options = {})
1006
        @template.error_messages_for(@object_name, objectify_options(options))
1007
      end
1008

1009 1010 1011
      def submit(value = "Save changes", options = {})
        @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
      end
1012 1013 1014 1015 1016

      private
        def objectify_options(options)
          @default_options.merge(options.merge(:object => @object))
        end
1017 1018 1019 1020 1021 1022 1023 1024

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

        def fields_for_with_nested_attributes(association_name, args, block)
          name = "#{object_name}[#{association_name}_attributes]"
          association = @object.send(association_name)
1025
          explicit_object = args.first if args.first.respond_to?(:new_record?)
1026 1027

          if association.is_a?(Array)
1028 1029
            children = explicit_object ? [explicit_object] : association
            explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1030 1031

            children.map do |child|
1032
              fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
1033 1034
            end.join
          else
1035 1036 1037 1038 1039 1040
            fields_for_nested_model(name, explicit_object || association, args, block)
          end
        end

        def fields_for_nested_model(name, object, args, block)
          if object.new_record?
1041
            @template.fields_for(name, object, *args, &block)
1042 1043 1044 1045 1046
          else
            @template.fields_for(name, object, *args) do |builder|
              @template.concat builder.hidden_field(:id)
              block.call(builder)
            end
1047 1048 1049
          end
        end

1050 1051 1052
        def nested_child_index(name)
          @nested_child_index[name] ||= -1
          @nested_child_index[name] += 1
1053
        end
1054
    end
D
Initial  
David Heinemeier Hansson 已提交
1055
  end
1056

J
Jeremy Kemper 已提交
1057 1058
  class << Base
    attr_accessor :default_form_builder
1059
  end
1060

J
Jeremy Kemper 已提交
1061 1062
  Base.default_form_builder = ::ActionView::Helpers::FormBuilder
end