form_helper.rb 46.0 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
        object = convert_to_model(object)

293 294 295 296 297 298 299
        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

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

305 306 307 308 309
      # 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
310
      #
311
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
312 313
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
314
      #
315
      #     <% fields_for @person.permission do |permission_fields| %>
316 317 318 319
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
      #   <% end %>
      #
320 321
      # ...or if you have an object that needs to be represented as a different
      # parameter, like a Client that acts as a Person:
322 323 324 325 326
      #
      #   <% fields_for :person, @client do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
327
      # ...or if you don't have an object, just a name of the parameter:
328 329 330 331 332
      #
      #   <% fields_for :person do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
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 478 479
      # 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 %>
480
      def fields_for(record_or_name_or_array, *args, &block)
481
        raise ArgumentError, "Missing block" unless block_given?
482
        options = args.extract_options!
483 484 485 486 487 488 489 490 491

        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
492

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

497 498 499
      # 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
500 501
      # 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).
502 503 504
      #
      # ==== Examples
      #   label(:post, :title)
P
Pratik Naik 已提交
505
      #   # => <label for="post_title">Title</label>
506 507
      #
      #   label(:post, :title, "A short title")
P
Pratik Naik 已提交
508
      #   # => <label for="post_title">A short title</label>
509 510
      #
      #   label(:post, :title, "A short title", :class => "title_label")
P
Pratik Naik 已提交
511
      #   # => <label for="post_title" class="title_label">A short title</label>
512
      #
513 514 515
      #   label(:post, :privacy, "Public Post", :value => "public")
      #   # => <label for="post_privacy_public">Public Post</label>
      #
516
      def label(object_name, method, text = nil, options = {})
517
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
518 519
      end

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

542 543
      # 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
544
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
      # 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" />
      #
560
      def password_field(object_name, method, options = {})
561
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_input_field_tag("password", options)
D
Initial  
David Heinemeier Hansson 已提交
562 563
      end

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

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

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

D
Initial  
David Heinemeier Hansson 已提交
629
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
P
Pratik Naik 已提交
630
      # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object.
Y
Yehuda Katz 已提交
631 632
      # 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 已提交
633
      # while the default +unchecked_value+ is set to 0 which is convenient for boolean values.
P
Pratik Naik 已提交
634 635 636 637 638
      #
      # ==== 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 已提交
639
      # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid
P
Pratik Naik 已提交
640 641 642 643 644 645 646
      # 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 已提交
647 648 649 650 651 652 653 654 655
      # 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 已提交
656 657 658 659 660 661 662 663 664 665
      #
      # 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 已提交
666 667 668 669 670
      # 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 已提交
671
      #
672
      # ==== Examples
673
      #   # Let's say that @post.validated? is 1:
D
Initial  
David Heinemeier Hansson 已提交
674
      #   check_box("post", "validated")
P
Pratik Naik 已提交
675 676
      #   # => <input name="post[validated]" type="hidden" value="0" />
      #   #    <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
D
Initial  
David Heinemeier Hansson 已提交
677
      #
678
      #   # Let's say that @puppy.gooddog is "no":
D
Initial  
David Heinemeier Hansson 已提交
679
      #   check_box("puppy", "gooddog", {}, "yes", "no")
P
Pratik Naik 已提交
680 681
      #   # => <input name="puppy[gooddog]" type="hidden" value="no" />
      #   #    <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
682
      #
P
Pratik Naik 已提交
683
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
P
Pratik Naik 已提交
684 685
      #   # => <input name="eula[accepted]" type="hidden" value="no" />
      #   #    <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
686
      #
687
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
688
        InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
689
      end
690 691 692

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

Y
Yehuda Katz 已提交
714 715
    module InstanceTagMethods #:nodoc:
      extend ActiveSupport::Concern
716
      include Helpers::TagHelper, Helpers::FormTagHelper
D
Initial  
David Heinemeier Hansson 已提交
717 718

      attr_reader :method_name, :object_name
719 720

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

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

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

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

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

D
Initial  
David Heinemeier Hansson 已提交
777
      def to_text_area_tag(options = {})
778
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
D
Initial  
David Heinemeier Hansson 已提交
779
        add_default_name_and_id(options)
780 781

        if size = options.delete("size")
782
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
783 784
        end

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

      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
789 790 791
        options = options.stringify_keys
        options["type"]     = "checkbox"
        options["value"]    = checked_value
792 793 794
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
795
        else
796
          checked = self.class.check_box_checked?(value(object), checked_value)
797
        end
798
        options["checked"] = "checked" if checked
D
Initial  
David Heinemeier Hansson 已提交
799
        add_default_name_and_id(options)
800 801 802
        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 已提交
803 804 805
      end

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

818
      def to_content_tag(tag_name, options = {})
819
        content_tag(tag_name, value(object), options)
820
      end
821

D
Initial  
David Heinemeier Hansson 已提交
822
      def object
823 824 825 826 827
        @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 已提交
828 829
      end

830 831
      def value(object)
        self.class.value(object, @method_name)
D
Initial  
David Heinemeier Hansson 已提交
832 833
      end

834 835 836
      def value_before_type_cast(object)
        self.class.value_before_type_cast(object, @method_name)
      end
837

Y
Yehuda Katz 已提交
838
      module ClassMethods
839 840 841
        def value(object, method_name)
          object.send method_name unless object.nil?
        end
842

843 844 845 846 847 848 849
        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
850

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

868 869
        def radio_button_checked?(value, checked_value)
          value.to_s == checked_value.to_s
870
        end
871 872
      end

D
Initial  
David Heinemeier Hansson 已提交
873
      private
874
        def add_default_name_and_id_for_value(tag_value, options)
875 876
          unless tag_value.nil?
            pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
877 878 879 880 881 882 883 884
            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 已提交
885
        def add_default_name_and_id(options)
886 887 888
          if options.has_key?("index")
            options["name"] ||= tag_name_with_index(options["index"])
            options["id"]   ||= tag_id_with_index(options["index"])
889
            options.delete("index")
890
          elsif defined?(@auto_index)
891 892
            options["name"] ||= tag_name_with_index(@auto_index)
            options["id"]   ||= tag_id_with_index(@auto_index)
893
          else
894
            options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
895
            options["id"]   ||= tag_id
896
          end
D
Initial  
David Heinemeier Hansson 已提交
897
        end
898

D
Initial  
David Heinemeier Hansson 已提交
899
        def tag_name
900
          "#{@object_name}[#{sanitized_method_name}]"
D
Initial  
David Heinemeier Hansson 已提交
901
        end
902

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

        def tag_id
908
          "#{sanitized_object_name}_#{sanitized_method_name}"
D
Initial  
David Heinemeier Hansson 已提交
909
        end
910 911

        def tag_id_with_index(index)
912
          "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
913 914 915
        end

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

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

Y
Yehuda Katz 已提交
924 925 926 927
    class InstanceTag
      include InstanceTagMethods
    end

928
    class FormBuilder #:nodoc:
929 930 931
      # The methods which wrap a form helper call.
      class_inheritable_accessor :field_helpers
      self.field_helpers = (FormHelper.instance_methods - ['form_for'])
932

933
      attr_accessor :object_name, :object, :options
934

Y
Yehuda Katz 已提交
935 936 937 938
      def self.model_name
        @model_name ||= Struct.new(:partial_path).new(name.demodulize.underscore.sub!(/_builder$/, ''))
      end

939 940 941 942
      def to_model
        self
      end

943
      def initialize(object_name, object, template, options, proc)
944
        @nested_child_index = {}
945
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
946
        @default_options = @options ? @options.slice(:index) : {}
947 948 949 950 951 952 953
        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
954
      end
955

956
      (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
957
        src = <<-end_src
958 959 960 961 962 963 964
          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
965 966 967
        end_src
        class_eval src, __FILE__, __LINE__
      end
968

969
      def fields_for(record_or_name_or_array, *args, &block)
970 971 972 973 974 975 976 977 978
        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

979 980 981 982 983
        if options[:builder]
          args << {} unless args.last.is_a?(Hash)
          args.last[:builder] ||= options[:builder]
        end

984 985
        case record_or_name_or_array
        when String, Symbol
986 987 988 989 990
          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
991 992
        when Array
          object = record_or_name_or_array.last
993
          name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
994 995 996
          args.unshift(object)
        else
          object = record_or_name_or_array
997
          name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
998 999
          args.unshift(object)
        end
1000

1001 1002
        @template.fields_for(name, *args, &block)
      end
1003 1004

      def label(method, text = nil, options = {})
1005
        @template.label(@object_name, method, text, objectify_options(options))
1006
      end
1007

1008
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
1009
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
1010
      end
1011

1012
      def radio_button(method, tag_value, options = {})
1013
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
1014
      end
1015

1016 1017
      def error_message_on(method, *args)
        @template.error_message_on(@object, method, *args)
1018
      end
1019 1020

      def error_messages(options = {})
1021
        @template.error_messages_for(@object_name, objectify_options(options))
1022
      end
1023

1024 1025 1026
      def submit(value = "Save changes", options = {})
        @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
      end
1027 1028 1029 1030 1031

      private
        def objectify_options(options)
          @default_options.merge(options.merge(:object => @object))
        end
1032 1033 1034 1035 1036 1037 1038 1039

        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)
Y
Yehuda Katz 已提交
1040
          explicit_object = args.first.to_model if args.first.respond_to?(:to_model)
1041 1042

          if association.is_a?(Array)
1043 1044
            children = explicit_object ? [explicit_object] : association
            explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
1045 1046

            children.map do |child|
1047
              fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
1048 1049
            end.join
          else
1050 1051 1052 1053 1054 1055
            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?
1056
            @template.fields_for(name, object, *args, &block)
1057 1058 1059 1060 1061
          else
            @template.fields_for(name, object, *args) do |builder|
              @template.concat builder.hidden_field(:id)
              block.call(builder)
            end
1062 1063 1064
          end
        end

1065 1066 1067
        def nested_child_index(name)
          @nested_child_index[name] ||= -1
          @nested_child_index[name] += 1
1068
        end
1069
    end
D
Initial  
David Heinemeier Hansson 已提交
1070
  end
1071

1072
  class << ActionView
J
Jeremy Kemper 已提交
1073
    attr_accessor :default_form_builder
1074
  end
1075

1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
  self.default_form_builder = ::ActionView::Helpers::FormBuilder

  # 2.3 compatibility
  class << Base
    def default_form_builder=(builder)
      ActionView.default_form_builder = builder
    end

    def default_form_builder
      ActionView.default_form_builder
    end
  end

J
Jeremy Kemper 已提交
1089
end