Friday, 23 January 2009

The View Part 3 - Using Forms

The basic component of a form for a view on Rails is the form_for method in ActionView::Helpers::FormHelper. This takes an object as an argument, and applies that object to the form components in a block. The variabe f is a FormBuilder object.
form_for(@post) do |f|
f.label :name
f.text_field :name
f.label :body
f.text_area :body
f.submit "Update"

Actually, it is not quite as simple as that, as you need to mix in the HTML, so in your view, the above becomes:
<% form_for(@post) do |f| %>
<%= f.label :name %>

<%= f.text_field :name %>
<%= f.label :body %>

<%= f.text_area :body %>
<%= f.submit "Update" %>
<% end %>

It is a good idea to put in <%= f.error_messages %> as the second line in your form (and indeed Rails will do this for you), as this will display error messages for you (for example, if the user's input fails validation).

There are a number of methods available for the FormHelper object that will place widgets on the web page. These ones are listed in the API:

The check_box method should be associated with a Boolean field. The hidden_field is useful for data you do not want the user to have access to, but which has to saved with the rest of the fields (but if a field is missing from the form, the record will retain the previous value).

Using radio_button
Suppose you have a column for integers in your database table (let us say "status", for example, in a table called "posts"). You want the user to be able to select an option.

First, you need a set of options, and this is best defined in your model. I am going to use a hash, so in posts.rb, there will be this constant defined:
STATUS_OPTIONS = {'Read' => 1, 'Unread' => 2, 'Deleted' => 12}

Then, in the view, you just need something like this:
<% Post::STATUS_OPTIONS.each_pair do |k, v| %>
<%= k + f.radio_button("status", v) %>
<% end %>

Ruby will iterate through the hash, Post::STATUS_OPTIONS. For each element, it will create a radio button.

Buttons that do not submit
Sometimes you want a button that does not submit the form. You can still use the submit method, just set the :type to map to "button. Here is an example of how to create a button that will invoke a JavaScript function called calc.
<%= f.submit 'Calc', { :onclick => 'calc()', :type => "button", } %>

Other Forms
Sometimes you want to create a form that is not associated with a particular record, such as a search form. For this you use methods from the ActionView::Helpers::FormTagHelper module. The same sort of methods are available, but with _tag as a suffix.

Use form_tag to create the basic form. You can give it a URL segment, or the usual URL parameters. The form_tag method seems to default to POST, so for this show example, I had to specify the method as GET.
<% form_tag({:action => :show, :id => 1}, :method => :get) do %>
<%= submit_tag 'Show 1' %>
<% end %>

Or using the URL:
<% form_tag '/posts/update/1' do %>
<%= submit_tag 'Update 1' %>
<% end %>

Here is a more interesting example, doing the same as the first example (just for illustration - there is no good reason to not do it the other way), with radio buttons. Note that the method is now PUT for update. Also, the tag name is of the form post[status] (for the model called "post", and the field called "status"). When Rails receives the request, the value of status will be put in a hash called post, which will go inside the params hash. This is the standard Rails technique, and is what happens in the earlier example, behind the scenes. This means the controller does not need changing.
<% form_tag({:action => :update, :id =>},
:method => :put) do %>
<%= label_tag 'Name' %>

<%= text_field_tag 'post[name]', %>
<%= label_tag 'Body' %>

<%= text_area_tag 'post[body]', @post.body %>
<% i = 0
Post::STATUS_OPTIONS.each_pair do |k, v| %>
<%= k + radio_button_tag('post[status]', v,
@post.status == v) %>
<% end %>
<%= submit_tag "Update" %>
<% end %>

Hopefully tomorrow I will post about select.

The API:

Struggling with Ruby: Contents Page

1 comment:

Žarko Aćimović said...

thank you for this post