HTML forms in Rails
CS290F Fall 2006 - UCSB Computer Science - Thorsten von Eicken
Rather confusingly Rails provides multiple ways to generate HTML forms. It's not at all obvious from the Rdoc what goes with what. Here are a few examples that hopefully clarify the matter.
Contents |
HTML forms
First of all, the goal is to generate HTML forms. An HTML form is created by the <form> tag, which by itself doesn't produce anything on the page. It
simply groups a number of <input> tags and allows a button in the form
to submit the contents of all <input> elements in one request. The form tag has a number of attributes of interest here:
-
actionspecifies the URL to which the form gets sent -
methodspecifies agetorpostrequest -
enctypespecifies the mime type used to submit the form,multipart-mixedis useful for uploading files, in particular
The input tag is used to define most fields:
- Text field:
<input type="text" name="firstname"> - Radio buttons:
<input type="radio" name="sex" value="male"> - Checkboxes:
<input type="checkbox" name="bike"> - File upload:
<input type="file" name="document"> - Hidden field:
<input type="hidden" name="userid" value="123"> - Image upload:
<input type="image" name="icon"> - Password field showing "*":
<input type="password" name="password"> - Submit button:
<input type="submit" value="Submit"> - Button to reset fields to initial value:
<input type="reset" value="Reset">
Multi-line text areas use the textarea tag and drop-down menus use the <select> and <option> tages seen previously.
Rails processing of input fields
In Rails the general pattern is that one form updates one object. This doesn't have to be the case, but it often is. The way this works is that the edit action fetches the current version of the object and populates a form with the current values. When the form is submitted, the values entered into the form are intended to replace those in the object using the update action. There are several key pieces of information necessary to accomplish this:
- The URL needs to point to the proper action and it needs to contain the correct object ID for an update, typically /controller/update/id or /controller/create.
- The method needs to be a post.
- Each input field needs to encode which object field to update using the name attribute.
- Each input field needs to be initialized to the current value of the object's corresponding field, typically using the value attribute.
The encoding of the name attribute is worth mentioning explicitly. It is encoded as if it were a Ruby hash holding the object's fields, e.g. for field f of object o the innput field name is o[f] and after Rails processes the form, this value will be available to the invoked action as params[o][f]. This means that all the fields submitted for object o are available as one hash using params[o], which is suitable for calling ActiveRecord's update_attributes method directly.
To summarize the handling of the input fields:
- Edit action fetches object o.
- Field f of o with value v is (typically) output as input tag with name="o[f]" and value="v".
- HTML submission with new value w in above field will contain f[o]="w".
- Rails will assign the value w to params[o][f].
- In the action, the statements
o = O.find(params[id])ando.update_attributes(params[o])will update the appropriate database record.
Primitive tag generators
The primitive tag generators provided in the ActionView::Helpers::FormTagHelper class are very simple convenience methods for outputting HTML. For example the call
text_field_tag("firstname", "Thorsten", :size => 20)
will generate the HTML
<input type="text" name="firstname" id="firstname" value="Thorsten" size="20">
which, with the exception of the id attribute is really just exactly what we provided.
Additional _tag mothods available are:
- check_box_tag
- file_field_tag
- hidden_field_tag
- image_submit_tag
- password_field_tag
- radio_button_tag
- select_tag
- submit_tag
- text_area_tag
- text_field_tag
The HTML form tag is simlarly generated in a straight-forward manner using the start_form_tag and end_form_tag. (The form_tag method is the same as the start_form_tag method.)
The downside of these _tag methods is that you need to remember how the mapping to the params happens, so in the example above we probably should have written:
text_field_tag("customer[firstname]", "Thorsten", :size => 20)
In the end, the _tag methods are only used when one constructs special forms that submit data for multiple objects or other complicated constructs.
Form helpers
The next step up are the form helper methods in the ActionView::Helpers::FormHelper class. The main improvement these methods offer is the abstraction of the name attribute and an automatic initialization of the value attribute. For example, the following call:
text_field("customer", "firstname", :size => 20)
with the assumption that
@customer.firstname returns the value Thorsten will generate the HTML
<input type="text" name="customer[firstname]" id="customer_firstname" value="Thorsten" size="20">
Notice how the object parameter and the field/method parameter are combined to generate the name, id, and value HTML attributes.
The form helper methods available are:
- check_box
- file_field
- hidden_field
- password_field
- radio_button
- text_area
- text_field
Form builders
Confusingly the form builder is also part of the ActionView::Helpers::FormHelper class. It is invoked using the form_for method. Form_for does three things:
- it acts as a block, eliminating the need for a special end_form type of tag
- it creates the correct URL to create or update an object
- it provides a number of methods to generate input tags which already know about the object
The way this looks is as follows:
<% form_for :person, :url => { :action => "update" } do |f| %>
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 %>
Here the object name person is used in all the input fields to fetch the value and generate the appropriate name and id tags.
Note that the Rdoc shows the above as
<% form_for :person, @person, :url => { :action => "update" } do |f| %>
where the :person is used to generate the name and id tags and the object @person is used to fetch the initial values.
Custom form builders
The form builder can be further customized by passing in a :builder argument. For example, suppose we wish to generate many pages that have tabular forms looking something like:
This is accomplished by Using a two-column table for the field labels and actual fields, and using a fieldset for the whole form. The entire form above is generated by the following code in the rhtml template:
<% tabular_form_for :user, :legend => "Personal info",
:url => { :action => :edit} do |f| %>
<%= f.text_field :email %>
<%= f.password_field :password, :label => 'New password' %>
<%= f.password_field :password_confirmation, :label => 'Confirm new password' %>
<%= f.text_row "", submit_tag("Save") %>
<% end %>
All the magic to produce a table and add the appropriate fields happens behnd the scenes. The magic consists of the following method and class:
def tabular_form_for(name, options = {}, &proc)
if options[:legend]
concat('<fieldset class="labeled">', proc.binding)
concat("<legend>#{options[:legend]}</legend><br/>", proc.binding)
end
concat('<table class="vertical">', proc.binding)
options[:builder] = TabularFormBuilder
form_for(name, options, &proc)
concat('</table>', proc.binding)
concat('</fieldset><br/>', proc.binding) if options[:legend]
end
The tabular_form_for method creates the fieldset<tt> tag, the <tt>table tag, then invokes form_for using the TabularFormBuilder, and ends by closing the table and fieldset<tt> tags. Note the calls to <tt>concat which appends to the eRB template output stream.
The TabularFormBuilder is quite a bit more complex. It defines the form helper methods used to generate the input fields by outputting some HTML for the labels and table tags and calling the standard form helper method (notice the call to super) in the middle. The form helper methods are generated dynamically in a loop to avoide repeating the custom code.
class TabularFormBuilder < ActionView::Helpers::FormBuilder
(field_helpers - %w(check_box radio_button hidden_field)).each do |selector|
src = <<-END_SRC
def #{selector}(field, options = {})
label = options[:label] || field.to_s.humanize
@template.content_tag("tr",
@template.content_tag("th",
@template.content_tag("label", "\#{label}: ", "for" => "\#{@object_name}_\#{field}"),
"style" => "padding-top: 0.4em;") +
@template.content_tag("td", super),
:class => ((@template.even_odd+=1)&1 == 0 ? "odd" : "even"))
end
END_SRC
class_eval src, __FILE__, __LINE__
def text_row(label, data, options = {}, &proc)
@template.content_tag("tr",
@template.content_tag("th", label + (label.blank? ? "" : ": ")) +
@template.content_tag("td", data),
:class => ((@template.even_odd+=1)&1 == 0 ? "odd" : "even"))
end
def select(field, select_options, options = {})
label = options[:label] || field.to_s.humanize
@template.content_tag("tr",
@template.content_tag("th",
@template.content_tag("label", "#{label}: ", "for" => "#{@object_name}_#{field}"),
"style" => "padding-top: 0.4em;") +
@template.content_tag("td", @template.select(@object_name.to_s, field.to_s, select_options, options)),
:class => ((@template.even_odd+=1)&1 == 0 ? "odd" : "even"))
end
end
end
Even if you don't understand every line of code here, you can copy and adapt it to your needs...

