form_helper.rb 33.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'
D
Initial  
David Heinemeier Hansson 已提交
4 5 6

module ActionView
  module Helpers
7 8 9 10
    # 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 已提交
11
    #
12
    # There are two types of form helpers: those that specifically work with model attributes and those that don't.
13 14
    # 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 已提交
15
    #
16
    # The core method of this helper, form_for, gives you the ability to create a form for a model instance;
17
    # 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 已提交
18
    #
19
    #     # Note: a @person variable will have been created in the controller.
20 21
    #     # For example: @person = Person.new
    #     <% form_for :person, @person, :url => { :action => "create" } do |f| %>
D
David Heinemeier Hansson 已提交
22 23
    #       <%= f.text_field :first_name %>
    #       <%= f.text_field :last_name %>
24
    #       <%= submit_tag 'Create' %>
D
David Heinemeier Hansson 已提交
25
    #     <% end %>
D
Initial  
David Heinemeier Hansson 已提交
26
    #
27
    # The HTML generated for this would be:
D
Initial  
David Heinemeier Hansson 已提交
28
    #
29
    #     <form action="/persons/create" method="post">
D
David Heinemeier Hansson 已提交
30 31
    #       <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" />
32
    #       <input name="commit" type="submit" value="Create" />
D
David Heinemeier Hansson 已提交
33
    #     </form>
D
Initial  
David Heinemeier Hansson 已提交
34
    #
35 36 37 38 39 40 41 42 43
    # 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 %>
    #
    # This example will render the <tt>people/_form</tt> partial, setting a local variable called <tt>form</tt> which references the yielded FormBuilder.
    #
44
    # The <tt>params</tt> object created when this form is submitted would look like:
D
Initial  
David Heinemeier Hansson 已提交
45
    #
46
    #     {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}}
47
    #
48 49 50
    # 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).
51
    #
52
    # If the object name contains square brackets the id for the object will be inserted. For example:
53
    #
54
    #   <%= text_field "person[]", "name" %> 
55
    #
56
    # ...will generate the following ERb.
57 58 59
    #
    #   <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" />
    #
60
    # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
61
    # used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example:
62 63 64
    #
    #   <%= text_field "person", "name", "index" => 1 %>
    #
65
    # ...becomes...
66
    #
67 68
    #   <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" />
    #
69 70 71
    # 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.
    #
D
David Heinemeier Hansson 已提交
72
    # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
D
Initial  
David Heinemeier Hansson 已提交
73 74
    # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
    module FormHelper
75
      # Creates a form and a scope around a specific model object that is used as a base for questioning about
76
      # values for the fields.
77
      #
N
Nicholas Seckar 已提交
78
      #   <% form_for :person, @person, :url => { :action => "update" } do |f| %>
P
Pratik Naik 已提交
79
      #     <%= f.error_messages %>
80 81 82 83 84 85
      #     First name: <%= f.text_field :first_name %>
      #     Last name : <%= f.text_field :last_name %>
      #     Biography : <%= f.text_area :biography %>
      #     Admin?    : <%= f.check_box :admin %>
      #   <% end %>
      #
86
      # 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>,
87
      # not <tt><%= %></tt>. Also worth noting is that form_for yields a <tt>form_builder</tt> object, in this example as <tt>f</tt>, which emulates
88
      # the API for the stand-alone FormHelper methods, but without the object name. So instead of <tt>text_field :person, :name</tt>,
P
Pratik Naik 已提交
89 90
      # you get away with <tt>f.text_field :name</tt>. Notice that you can even do <tt><%= f.error_messages %></tt> to display the
      # error messsages of the model object in question.
91
      #
92 93
      # Even further, the form_for method allows you to more easily escape the instance variable convention. So while the stand-alone
      # approach would require <tt>text_field :person, :name, :object => person</tt>
94
      # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with 
95
      # <tt>:person, person</tt> and all subsequent field calls save <tt>:person</tt> and <tt>:object => person</tt>.
96 97
      #
      # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
98
      # and methods from FormTagHelper. For example:
99
      #
100
      #   <% form_for :person, @person, :url => { :action => "update" } do |f| %>
101 102 103 104 105 106
      #     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 %>
      #
107 108
      # 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.
109
      #
110
      # HTML attributes for the form tag can be given as :html => {...}. For example:
111
      #
112 113 114 115
      #   <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
      #     ...
      #   <% end %>
      #
116 117 118
      # The above form will then have the <tt>id</tt> attribute with the value </tt>person_form</tt>, which you can then
      # style with CSS or manipulate with JavaScript.
      #
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
      # === Relying on record identification
      #
      # In addition to manually configuring the form_for call, you can also rely on record identification, which will use
      # the conventions and named routes of that approach. Examples:
      #
      #   <% form_for(@post) do |f| %>
      #     ...
      #   <% end %>
      #
      # This will expand to be the same as:
      #
      #   <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
      #     ...
      #   <% end %>
      #
      # And for new records:
      #
      #   <% form_for(Post.new) do |f| %>
      #     ...
      #   <% end %>
      #
      # This will expand to be the same as:
      #
      #   <% form_for :post, @post, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
      #     ...
      #   <% end %>
      #
      # You can also overwrite the individual conventions, like this:
      #
      #   <% form_for(@post, :url => super_post_path(@post)) do |f| %>
      #     ...
      #   <% end %>
      #
152 153 154 155 156 157
      # And for namespaced routes, like admin_post_url: 
      #
      #   <% form_for([:admin, @post]) do |f| %>
      #    ...
      #   <% end %>
      #
158 159
      # === Customized form builders
      #
160
      # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
161 162
      # then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs.
      #
163 164 165 166 167 168
      #   <% 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 %>
169
      #
170 171 172 173 174 175
      # In this case, if you use this:
      #
      #   <%= render :partial => f %>
      #
      # The rendered template is <tt>people/_labelling_form</tt> and the local variable referencing the form builder is called <tt>labelling_form</tt>.
      #
176
      # In many cases you will want to wrap the above in another helper, so you could do something like the following:
177
      #
178 179
      #   def labelled_form_for(record_or_name_or_array, *args, &proc)
      #     options = args.extract_options!
180
      #     form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc)
181 182
      #   end
      #
183
      # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag.
184
      def form_for(record_or_name_or_array, *args, &proc)
185
        raise ArgumentError, "Missing block" unless block_given?
186

187
        options = args.extract_options!
188

189
        case record_or_name_or_array
190
        when String, Symbol
191
          object_name = record_or_name_or_array
192
        when Array
193
          object = record_or_name_or_array.last
194
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
195
          apply_form_for_options!(record_or_name_or_array, options)
196
          args.unshift object
197
        else
198
          object = record_or_name_or_array
199
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
200
          apply_form_for_options!([object], options)
201
          args.unshift object
202 203
        end

204
        concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
205
        fields_for(object_name, *(args << options), &proc)
206
        concat('</form>', proc.binding)
207
      end
208

209 210
      def apply_form_for_options!(object_or_array, options) #:nodoc:
        object = object_or_array.is_a?(Array) ? object_or_array.last : object_or_array
211

212 213 214 215 216 217 218
        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

219 220
        options[:html] ||= {}
        options[:html].reverse_merge!(html_options)
221
        options[:url] ||= polymorphic_path(object_or_array)
222
      end
223 224

      # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
225
      # fields_for suitable for specifying additional model objects in the same form:
226
      #
227
      # ==== Examples
228
      #   <% form_for @person, :url => { :action => "update" } do |person_form| %>
229 230
      #     First name: <%= person_form.text_field :first_name %>
      #     Last name : <%= person_form.text_field :last_name %>
231
      #
232
      #     <% fields_for @person.permission do |permission_fields| %>
233 234 235 236
      #       Admin?  : <%= permission_fields.check_box :admin %>
      #     <% end %>
      #   <% end %>
      #
237 238 239 240 241 242 243 244 245 246 247 248
      # ...or if you have an object that needs to be represented as a different parameter, like a Client that acts as a Person:
      #
      #   <% fields_for :person, @client do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
      # ...or if you don't have an object, just a name of the parameter
      #
      #   <% fields_for :person do |permission_fields| %>
      #     Admin?: <%= permission_fields.check_box :admin %>
      #   <% end %>
      #
249 250
      # 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.
251
      def fields_for(record_or_name_or_array, *args, &block)
252
        raise ArgumentError, "Missing block" unless block_given?
253
        options = args.extract_options!
254 255 256 257 258 259 260 261 262 263 264 265 266

        case record_or_name_or_array
        when String, Symbol
          object_name = record_or_name_or_array
          object = args.first
        when Array
          object = record_or_name_or_array.last
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
          apply_form_for_options!(record_or_name_or_array, options)
        else
          object = record_or_name_or_array
          object_name = ActionController::RecordIdentifier.singular_class_name(object)
        end
267 268 269

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

272 273 274
      # 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
275
      # onto the HTML as an HTML element attribute as in the example shown.
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
      #
      # ==== Examples
      #   label(:post, :title)
      #   #=> <label for="post_title">Title</label>
      #
      #   label(:post, :title, "A short title")
      #   #=> <label for="post_title">A short title</label>
      #
      #   label(:post, :title, "A short title", :class => "title_label")
      #   #=> <label for="post_title" class="title_label">A short title</label>
      #
      def label(object_name, method, text = nil, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_label_tag(text, options)
      end

D
Initial  
David Heinemeier Hansson 已提交
291 292
      # 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
293
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
D
David Heinemeier Hansson 已提交
294
      # shown.
D
Initial  
David Heinemeier Hansson 已提交
295
      #
296
      # ==== Examples
D
David Heinemeier Hansson 已提交
297
      #   text_field(:post, :title, :size => 20)
298 299 300 301 302 303 304 305 306 307 308
      #   # => <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" />
      #
309 310
      def text_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
D
Initial  
David Heinemeier Hansson 已提交
311 312
      end

313 314
      # 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
315
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
      # 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" />
      #
331 332
      def password_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
D
Initial  
David Heinemeier Hansson 已提交
333 334
      end

335 336
      # 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
337
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
338 339 340 341 342 343 344 345 346 347
      # shown.
      #
      # ==== Examples 
      #   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)
348
      #   # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" />
349
      def hidden_field(object_name, method, options = {})
350
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
D
Initial  
David Heinemeier Hansson 已提交
351 352
      end

353 354
      # 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
355
      # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
356 357 358 359 360 361 362 363 364 365 366 367
      # 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" />
      #
368 369
      def file_field(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
370 371
      end

D
Initial  
David Heinemeier Hansson 已提交
372 373 374 375
      # 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+.
      #
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
      # ==== 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>
396 397
      def text_area(object_name, method, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
D
Initial  
David Heinemeier Hansson 已提交
398
      end
399

D
Initial  
David Heinemeier Hansson 已提交
400 401 402 403
      # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
      # assigned to the template (identified by +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+
404 405
      # is set to 0 which is convenient for boolean values. Since HTTP standards say that unchecked checkboxes don't post anything,
      # we add a hidden value with the same name as the checkbox as a work around.
D
Initial  
David Heinemeier Hansson 已提交
406
      #
407 408
      # ==== Examples 
      #   # Let's say that @post.validated? is 1:
D
Initial  
David Heinemeier Hansson 已提交
409
      #   check_box("post", "validated")
P
Pratik Naik 已提交
410
      #   # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" />
411
      #   #    <input name="post[validated]" type="hidden" value="0" />
D
Initial  
David Heinemeier Hansson 已提交
412
      #
413
      #   # Let's say that @puppy.gooddog is "no":
D
Initial  
David Heinemeier Hansson 已提交
414
      #   check_box("puppy", "gooddog", {}, "yes", "no")
415 416 417
      #   # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" />
      #   #    <input name="puppy[gooddog]" type="hidden" value="no" />
      #
P
Pratik Naik 已提交
418 419
      #   check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no")
      #   # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" />
420 421
      #   #    <input name="eula[accepted]" type="hidden" value="no" />
      #
422 423
      def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
424
      end
425 426 427 428

      # 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
      # radio button will be checked. Additional options on the input tag can be passed as a
429
      # hash with +options+.
430 431 432
      #
      # ==== Examples
      #   # Let's say that @post.category returns "rails":
433 434
      #   radio_button("post", "category", "rails")
      #   radio_button("post", "category", "java")
P
Pratik Naik 已提交
435 436
      #   # => <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" />
437
      #
438 439
      #   radio_button("user", "receive_newsletter", "yes")
      #   radio_button("user", "receive_newsletter", "no")
P
Pratik Naik 已提交
440 441
      #   # => <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" />
442 443
      def radio_button(object_name, method, tag_value, options = {})
        InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
444
      end
D
Initial  
David Heinemeier Hansson 已提交
445 446 447 448 449 450
    end

    class InstanceTag #:nodoc:
      include Helpers::TagHelper

      attr_reader :method_name, :object_name
451 452

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

456
      def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
457
        @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
D
Initial  
David Heinemeier Hansson 已提交
458
        @template_object, @local_binding = template_object, local_binding
459
        @object = object
460
        if @object_name.sub!(/\[\]$/,"")
461 462
          if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
            @auto_index = object.to_param
463
          else
464
            raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
465
          end
466
        end
D
Initial  
David Heinemeier Hansson 已提交
467
      end
468

469 470 471 472 473 474 475 476
      def to_label_tag(text = nil, options = {})
        name_and_id = options.dup
        add_default_name_and_id(name_and_id)
        options["for"] = name_and_id["id"]
        content = (text.blank? ? nil : text.to_s) || method_name.humanize
        content_tag("label", content, options)
      end

D
Initial  
David Heinemeier Hansson 已提交
477
      def to_input_field_tag(field_type, options = {})
478
        options = options.stringify_keys
479
        options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size")
480
        options = DEFAULT_FIELD_OPTIONS.merge(options)
481 482 483 484
        if field_type == "hidden"
          options.delete("size")
        end
        options["type"] = field_type
485
        options["value"] ||= value_before_type_cast(object) unless field_type == "file"
486 487 488 489 490
        add_default_name_and_id(options)
        tag("input", options)
      end

      def to_radio_button_tag(tag_value, options = {})
491
        options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
492 493
        options["type"]     = "radio"
        options["value"]    = tag_value
494 495 496 497 498 499
        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
500
        options["checked"]  = "checked" if checked
501
        pretty_tag_value    = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
502
        options["id"]     ||= defined?(@auto_index) ?
503 504
          "#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" :
          "#{tag_id}_#{pretty_tag_value}"
505 506 507 508
        add_default_name_and_id(options)
        tag("input", options)
      end

D
Initial  
David Heinemeier Hansson 已提交
509
      def to_text_area_tag(options = {})
510
        options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
D
Initial  
David Heinemeier Hansson 已提交
511
        add_default_name_and_id(options)
512 513

        if size = options.delete("size")
514
          options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
515 516
        end

517
        content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
D
Initial  
David Heinemeier Hansson 已提交
518 519 520
      end

      def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
521 522 523
        options = options.stringify_keys
        options["type"]     = "checkbox"
        options["value"]    = checked_value
524 525 526
        if options.has_key?("checked")
          cv = options.delete "checked"
          checked = cv == true || cv == "checked"
527
        else
528
          checked = self.class.check_box_checked?(value(object), checked_value)
529
        end
530
        options["checked"] = "checked" if checked
D
Initial  
David Heinemeier Hansson 已提交
531
        add_default_name_and_id(options)
532
        tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => options['disabled'] && checked ? checked_value : unchecked_value)
D
Initial  
David Heinemeier Hansson 已提交
533 534 535
      end

      def to_boolean_select_tag(options = {})
536
        options = options.stringify_keys
D
Initial  
David Heinemeier Hansson 已提交
537
        add_default_name_and_id(options)
538
        value = value(object)
D
Initial  
David Heinemeier Hansson 已提交
539 540 541 542 543 544 545 546
        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
547

548
      def to_content_tag(tag_name, options = {})
549
        content_tag(tag_name, value(object), options)
550
      end
551

D
Initial  
David Heinemeier Hansson 已提交
552
      def object
553
        @object || (@template_object.instance_variable_get("@#{@object_name}") rescue nil)
D
Initial  
David Heinemeier Hansson 已提交
554 555
      end

556 557
      def value(object)
        self.class.value(object, @method_name)
D
Initial  
David Heinemeier Hansson 已提交
558 559
      end

560 561 562
      def value_before_type_cast(object)
        self.class.value_before_type_cast(object, @method_name)
      end
563

564 565 566 567
      class << self
        def value(object, method_name)
          object.send method_name unless object.nil?
        end
568

569 570 571 572 573 574 575
        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
576

577 578 579 580 581 582 583 584 585 586 587 588 589 590
        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
          else
            value.to_i != 0
          end
        end
591

592 593
        def radio_button_checked?(value, checked_value)
          value.to_s == checked_value.to_s
594
        end
595 596
      end

D
Initial  
David Heinemeier Hansson 已提交
597 598
      private
        def add_default_name_and_id(options)
599 600 601
          if options.has_key?("index")
            options["name"] ||= tag_name_with_index(options["index"])
            options["id"]   ||= tag_id_with_index(options["index"])
602
            options.delete("index")
603
          elsif defined?(@auto_index)
604 605
            options["name"] ||= tag_name_with_index(@auto_index)
            options["id"]   ||= tag_id_with_index(@auto_index)
606
          else
607
            options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
608
            options["id"]   ||= tag_id
609
          end
D
Initial  
David Heinemeier Hansson 已提交
610
        end
611

D
Initial  
David Heinemeier Hansson 已提交
612 613 614
        def tag_name
          "#{@object_name}[#{@method_name}]"
        end
615

616 617 618
        def tag_name_with_index(index)
          "#{@object_name}[#{index}][#{@method_name}]"
        end
D
Initial  
David Heinemeier Hansson 已提交
619 620

        def tag_id
621
          "#{sanitized_object_name}_#{@method_name}"
D
Initial  
David Heinemeier Hansson 已提交
622
        end
623 624

        def tag_id_with_index(index)
625 626 627 628 629
          "#{sanitized_object_name}_#{index}_#{@method_name}"
        end

        def sanitized_object_name
          @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
630
        end
D
Initial  
David Heinemeier Hansson 已提交
631
    end
632

633
    class FormBuilder #:nodoc:
634 635 636
      # The methods which wrap a form helper call.
      class_inheritable_accessor :field_helpers
      self.field_helpers = (FormHelper.instance_methods - ['form_for'])
637

638
      attr_accessor :object_name, :object, :options
639

640
      def initialize(object_name, object, template, options, proc)
641
        @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
642
        @default_options = @options ? @options.slice(:index) : {}
643
      end
644

645
      (field_helpers - %w(label check_box radio_button fields_for)).each do |selector|
646 647
        src = <<-end_src
          def #{selector}(method, options = {})
648
            @template.send(#{selector.inspect}, @object_name, method, objectify_options(options))
649 650 651 652
          end
        end_src
        class_eval src, __FILE__, __LINE__
      end
653

654 655 656 657 658 659 660 661 662 663 664 665 666
      def fields_for(record_or_name_or_array, *args, &block)
        case record_or_name_or_array
        when String, Symbol
          name = "#{object_name}[#{record_or_name_or_array}]"
        when Array
          object = record_or_name_or_array.last
          name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
          args.unshift(object)
        else
          object = record_or_name_or_array
          name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]"
          args.unshift(object)
        end
667

668 669
        @template.fields_for(name, *args, &block)
      end
670 671

      def label(method, text = nil, options = {})
672
        @template.label(@object_name, method, text, objectify_options(options))
673
      end
674

675
      def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
676
        @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value)
677
      end
678

679
      def radio_button(method, tag_value, options = {})
680
        @template.radio_button(@object_name, method, tag_value, objectify_options(options))
681
      end
682

683
      def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError")
684
        @template.error_message_on(@object, method, prepend_text, append_text, css_class)
685
      end
686 687

      def error_messages(options = {})
688
        @template.error_messages_for(@object_name, objectify_options(options))
689
      end
690

691 692 693
      def submit(value = "Save changes", options = {})
        @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit"))
      end
694 695 696 697 698

      private
        def objectify_options(options)
          @default_options.merge(options.merge(:object => @object))
        end
699
    end
D
Initial  
David Heinemeier Hansson 已提交
700
  end
701 702 703 704 705

  class Base
    cattr_accessor :default_form_builder
    self.default_form_builder = ::ActionView::Helpers::FormBuilder
  end
706
end