Generating Forms for Manipulating Model Objects

Problem

You want to define actions that let a user create or edit objects stored in the database.

Solution

Lets create a simple model, and then build forms for it. Heres some MySQL code to create a table of key-value pairs:

	use mywebapp_development;
	DROP TABLE IF EXISTS items;
	CREATE TABLE `items` (
	 id int(11) NOT NULL auto_increment,
	 
ame varchar(255) NOT NULL default \,
	 value varchar(40) NOT NULL default [empty],
	 PRIMARY KEY (id)
	);

Now, from the command line, create the model class, along with a controller and views:

	$ ./script/generate model Item
	 exists app/models/
	 exists test/unit/
	 exists test/fixtures/
	 create app/models/item.rb
	 create test/unit/item_test.rb
	 create test/fixtures/items.yml
 	 create db/migrate
	 create db/migrate/001_create_items.rb
	$ ./script/generate controller items new create edit
	 exists app/controllers/
	 exists app/helpers/
	 create app/views/items
	 exists test/functional/
	 create app/controllers/items_controller.rb
	 create test/functional/items_controller_test.rb
	 create app/helpers/items_helper.rb
	 create app/views/items/new.rhtml
	 create app/views/items/edit.rhtml

The first step is to customize a view. Lets start with app/views/items/new.rhtml. Edit it to look like this:

	
	 
	<%= form_tag :action => "create" %>
	 Name: <%= text_field "item", "name" %>
Value: <%= text_field "item", "value" %>
<%= submit_tag %> <%= end_form_tag %>

All these method calls generate HTML: form_tag opens a

tag, submit_tag generates a submit button, and so on. You can type out the same HTML by hand and Rails won care, but its easier to make method calls, and it makes your templates neater.

The text_field call is a little more involved. It creates an tag that shows up in the HTML form as a text entry field. But it also binds the value of that field to one of the members of the @item instance variable. This code creates a text entry field thats bound to the name member of @item:

	<%= text_field "item", "name" %>

But whats the @item instance variable? Well, its not defined yet, because we e still using the generated controller. If you try to access the page /items/new page right now, you may get an error complaining about an unexpected nil value. The nil value is the @item variable, which gets used (in text_field calls) without ever being defined.

Lets customize the ItemsController class so that the new action sets the @item instance variable properly. Well also implement the create action so that something actually happens when the user hits the submit button on our generated form.

	class ItemsController < ApplicationController
	 def new
	 @item = Item.new
	 end
	
	 def create
	 @item = Item.create(params[:item])
	 redirect_to :action => edit, :id => @item.id
	 end
	end

Now if you access the /items/new page, youll see what youd expect: a form with two text entry fields. The "Name" field will be blank, and the "Value" field will contain the default database value of "[empty]".

Fill out the form and submit, and a new row will be created in the items table. Youll be redirected to the edit action, which doesn exist yet. Lets create it now. Heres the controller part (note the similarity between ItemsController#edit and ItemsController#create above):

	class ItemsController < ApplicationController
	 def edit
	 @item = Item.find(params[:id])

	 if request.post?
	 @item.update_attributes(params[:item])
	 redirect_to :action => edit, :id => @item.id
	 end
	 end
	end

In fact, the edit action is so similar to the create action that its form can be almost identical. The only differences are in the arguments to form_tag:

	

	<%= form_tag :action => "edit", :id => @item.id %>
	 Name: <%= text_field "item", "name" %>
Value: <%= text_field "item", "value" %>
<%= submit_tag %> <%= end_form_tag %>

Discussion

This is probably the most common day-to-day task faced by web developers. Its so common that Rails comes with a tool called scaffold that generates this kind of code for you. If youd invoked generate this way instead of with the arguments given above, Rails would have generate code for the actions given in the Solution, plus a few more:

	$ ./script/generate scaffold Items

Starting off with scaffolding doesn mean you can get away with not knowing how Rails form generation works, because youll definitely want to customize the scaffolding code.

There are two places in our code where magic happens. The first is the text_field call in the view, which is explained in the Solution. It binds a member of an object (@item.name, for instance) to an HTML form control. If you view the source of the /items/new page, you will see that the form fields look something like this:

	Name: name="item[name]" value="" />
Value name="item[value]" value="[empty]" />

These special field names are used by the second piece of magic, located in the calls to Item.create (in new) and Item#update_attributes. In both cases, an Item object is fed a hash of new values for its members. This hash is embedded into the params hash, which contains CGI form values.

The names of the HTML form fields (item[name] and item[value]) translate into a params hash that looks like this:

	{
	 :item => {
	 :name => "Name of the item",
	 :value => "Value of the item"
	 },
	 :controller => "items",
	 :action => "create"
	}

So this line of code:

	Item.create(params[:item])

is effectively the same as this line:

	Item.create(:name => "Name of the item", :value => "Value of the item")

The call to Item#update_attributes in the edit action works exactly the same way.

As mentioned above, the views for edit and new are very similar, differing only in the destination of the form. With some minor refactoring, we can remove one of the view files completely.

A call to <%= form_tag %> without any parameters at all sets the form destination to the current URL. Lets change the new.rhtml file appropriately:

	
	<%= form_tag %>

	Name: <%= text_field "item", "name" %>

	Value: <%= text_field "item", "value" %>


	<%= submit_tag %>
	<%= end_form_tag %>

Now the new.rhtml view is suitable for use by both new and edit. We just need to change the new action to call the create method (since the form doesn go there anymore), and change the edit action to render new.rhtml instead of edit.rhtml (which can be removed):

	class ItemsController < ApplicationController
	 def new
	 @item = Item.new
	 create if request.post?
	 end

	 def edit
	 @item = Item.find(params[:id])

	 if request.post?
	 @item.update_attributes(params[:item])
	 redirect_to :action => edit, :id => @item.id and return
	 end
	 render :action => 
ew
	 end
	end

Remember from Recipe 15.5 that a render call only specifies the template file to be used. The render call in edit won actually call the new method, so we don need to worry about the new method overwriting our value of @item.

In real life, there would be enough differences in the content surrounding the add and edit forms to a separate view for each action. However, theres usually enough similarity between the forms themselves that they can be refactored into a single partial view (see Recipe 15.14) which both views share. This is a great example of the DRY (Don Repeat Yourself) principle. If there is a single form for both the add and edit views, its easier and less error-prone to maintain that form as the database schema changes.

See Also

  • Recipe 15.5, "Displaying Templates with Render"
  • Recipe 15.14, "Refactoring the View into Partial Snippets of Views"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net