Beginners Tutorial: Routing in Rails 2.0 (with REST) - Part 4 of n
The routes we have discussed so far are known as simple routes. I have been referring to them as custom routes, but ‘simple routes’ is the correct term. The default route we discussed in part 3 is also a simple route. The aim is to move onto RESTful routes, sometimes called resource routes. RESTful routes are built on top of named routes. Therefore we must first cover named routes, and that is what part 4 is all about.
Part 4
Introduction
Referring back to part 1, the routing system has two main functions:
Interpreting a request URL
Generating a request URL
We had a detailed look at route generation and interpretation in part 2, but let’s have another quick look at it. Let’s assume, for some insane reason, that we defined the following route in the music_store routes.rb file:
map.connect ‘apples/bananas/:id’, :controller => “albums”, :action => “show”
link_to would use this rule to generate a URL, this can be seen in the following line extracted from index.html.erb:
link_to ‘Show’, :controller => “albums”, :action => “show”, :id => album.id
This could generate a URL of: http://localhost:3000/apples/bananas/7
And when given this request, Rails would handle it correctly e.g. the show action will be called on the albums controller and the show view will be sent as the response to the browser.
Coming back to the real world, we are more likely to define the routing rule like this:
map.connect ‘albums/show/:id’, :controller => “albums”, :action => “show”
link_to (and friends) would then generate a URL like this:
http://localhost:3000/albums/show/7.
To show the index (the listing of the albums) we could define a rule like this:
map.connect ‘albums/index’, :controller => “albums”, :action => “index”
The matching link_to in edit.html.erb is:
link_to ‘Back’, :controller => “albums”, :action => “index”
Looking at the last example, there is some repetition. Both lines contain:
:controller => “albums”, :action => “index”
The link_to method needs this (controller, action) information to obtain the URL. If we had the URL stored as some global variable, or method, then we could simply pass that into link_to. link_to could use this ‘literal’ URL without needing to generate one.
This could look something like: link_to ‘Back’, albums_index_url
This is one of the main ideas behind named routes.
Named Routes
Named routes provide a way to ‘clean up’ code, specifically the code within link_to and other similar functions. When a named route is defined, new helper methods are dynamically generated. These helper methods provide a simplified way of obtaining the required request URL. The names of these helper methods are based on the name of the route. Let’s clarify this by coding.
Experiment 4.0 Preparation
At the end of part 3 the routes.rb had the default route in it:
ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' end
It is common to accompany the default route with this:
map.connect ‘:controller/:action/:id.:format’
We can think of this as a default route which allows clients to request a different format for the response. :format was discussed in part 1, under the title of Experimenting 1 - View the generated routes.
To prepare for this session, we want to remove all the routes and completely break the site. Therefore you should make your routes.rb look like this:
ActionController::Routing::Routes.draw do |map| # map.connect ':controller/:action/:id' # map.connect ':controller/:action/:id.:format' end
Now the site is good for two things, serving up routing errors and learning about named routes…
Experiment 4.1 Adding a Named Route for the Albums Index
In part 2 we defined a route which invoked the index action on the albums controller. That route looked like this: map.connect ‘music’, :controller => “albums”, :action => “index”
To follow Rails convention, we should write it like this:
map.connect ‘albums’, :controller => “albums”, :action => “index”
Making it more readable we could write it like this:
map.connect ‘albums’,
:controller => “albums”,
:action => “index”
The pattern string is ‘albums’, therefore a URL of /albums would match.
In order to name this route, we need to decide what to name or call it. A top down approach is recommended for this. We want to show a list of albums, so it seems reasonable to call the route ‘albums’.
To name the route, we make a small change to our route definition. We change the route definition from:
map.connect ‘albums’,
:controller => “albums”,
:action => “index”
To:
map.albums ‘albums’,
:controller => “albums”,
:action => “index”
All we had to do is change map.connect to map.albums. Modify your routes.rb file to look like this:
ActionController::Routing::Routes.draw do |map|
map.albums 'albums',
:controller => "albums",
:action => "index"
# map.connect ':controller/:action/:id'
# map.connect ':controller/:action/:id.:format'
end
Once this file is saved, you will have created a named route. We are still missing most of the routes our site requires therefore we can’t run the site just yet. Let’s simplify the index page. Make the index.html.erb look like this:
<h1>Listing albums</h1>
<table>
<tr>
<th>Title</th>
<th>Review</th>
</tr>
<% for album in @albums %>
<tr>
<td><%=h album.title %></td>
<td><%=h album.review %></td>
</tr>
<% end %>
</table>
<br />
Now you should be able to see the simplified index page. Next we will take a look at the methods that Rails generates when using named routes.
Experiment 4.2 Exploring the ‘Named Route’ Generated Methods
When a named route is defined, Rails will dynamically generate two methods, these are:
name_url and name_path (where name is the name of the route). name_url will return the fully qualified URL, name_path returns the relative path – it does not include the protocol and host.
In our case, we have a route named albums. When we run the site, rails will generate albums_url and albums_path. Let’s make the view display these values, change the index.html.erb to this:
I had to use different formatting to stop WordPress from getting up to its shenanigans…
<h1>Listing albums</h1>
<table>
<tr>
<th>Title</th>
<th>Review</th>
</tr>
<% for album in @albums %>
<tr>
<td><%=h album.title %></td>
<td><%=h album.review %></td>
</tr>
<% end %>
</table>
<br />
albums_url: <%= albums_url %>
<br />
albums_path: <%= albums_path %>
Brows to http://localhost:3000/albums and you should see:
albums_url: http://localhost:3000/albums
albums_path: /albums
Now that we have seen the output of these methods, you can now remove those last three lines.
Academically speaking it is probably more correct to use name_url but name_path also works in most cases. The Rails way is to use name_path if you can. These methods are available in the views and controllers.
Experiment 4.3 Defining a Named Route for Show
Showing the index is fairly straight forward as it is basically a static URL. Now we look at defining a named route for show. Show needs to pass through the ID of the specific album. When defining a simple route, we could use:
map.connect ‘album/:id’,
:controller => “albums”,
:action => “show”
Once again, we can convert this into a named route by replacing connect with the name of the route. Let’s name the route ‘album’. Update the routes.rb to look as follows:
ActionController::Routing::Routes.draw do |map|
map.albums 'albums',
:controller => "albums",
:action => "index"
map.album 'album/:id',
:controller => "albums",
:action => "show"
# map.connect ':controller/:action/:id'
# map.connect ':controller/:action/:id.:format'
end
Rails realises that it needs a value for :id, therefore the helper methods accept a parameter to be assigned to it. Therefore link_to can use album_url(3). This will hard code 3, but it illustrates the point.
In the code we are likely to use:
link_to ‘Show’, album_path(album.id)
Thanks to some syntactic sugar we can also use:
link_to ‘Show’, album_path(album)
Let’s get the ‘show’ functionality working again. Update index.erb.html to look like this:
<h1>Listing albums</h1>
<table>
<tr>
<th>Title</th>
<th>Review</th>
</tr>
<% for album in @albums %>
<tr>
<td><%=h album.title %></td>
<td><%=h album.review %></td>
<td><%= link_to 'Show', album_path(album) %></td>
</tr>
<% end %>
</table>
<br />
And update show.erb.html to:
<p> <b>Title:</b> <%=h @album.title %> </p> <p> <b>Review:</b> <%=h @album.review %> </p> <%= link_to 'Back', albums_path %>
We have removed the link to ‘edit’, we will replace it later.
At this stage the site should be able:
- to show the list of albums in the index page
- show a single album
Experiment 4.4 Defining a Named Route for Edit
Now lets fix the edit functionality. Update routes.rb to look like this:
ActionController::Routing::Routes.draw do |map|
map.albums 'albums',
:controller => "albums",
:action => "index"
map.album 'album/:id',
:controller => "albums",
:action => "show"
map.edit_album 'albums/:id/edit',
:controller => "albums",
:action => "edit"
map.update_album 'albums/:id/update',
:controller => "albums",
:action => "update"
# map.connect ':controller/:action/:id'
# map.connect ':controller/:action/:id.:format'
end
You may have noticed that I am using slightly different pattern strings to what we have been using. The pattern strings (and URLs) that I am using now are fairly close to those used in REST. Therefore we are already getting familiar with the RESTful interface.
Update the index.erb.html to:
<h1>Listing albums</h1> <table> <tr> <th>Title</th> <th>Review</th> </tr> <% for album in @albums %> <tr> <td><%=h album.title %></td> <td><%=h album.review %></td> <td><%= link_to 'Show', album_path(album) %></td> <td><%= link_to 'Edit', edit_album_path(album) %></td> </tr> <% end %> </table> <br />
Edit.erb.html should look like this:
<h1>Editing album</h1>
<%= error_messages_for :album %>
<% form_for (@album), :url => update_album_path(@album) do |f| %>
<p>
<b>Title</b><br />
<%= f.text_field :title %>
</p>
<p>
<b>Review</b><br />
<%= f.text_area :review %>
</p>
<p>
<%= f.submit "Update" %>
</p>
<% end %>
<%= link_to 'Show', album_path(@album) %> |
<%= link_to 'Back', albums_path %>
Add the edit link back to show.erb.html
<p> <b>Title:</b> <%=h @album.title %> </p> <p> <b>Review:</b> <%=h @album.review %> </p> <%= link_to 'Edit', edit_album_path(@album) %> | <%= link_to 'Back', albums_path %>
At this stage the site should be able:
- to show the list of albums in the index page
- show a single album
- Edit an existing album
Experiment 4.5 Defining a Named Route for Destroy
Update the routes.rb to this:
ActionController::Routing::Routes.draw do |map|
map.albums 'albums',
:controller => "albums",
:action => "index"
map.album 'album/:id',
:controller => "albums",
:action => "show"
map.edit_album 'albums/:id/edit',
:controller => "albums",
:action => "edit"
map.update_album 'albums/:id/update',
:controller => "albums",
:action => "update"
map.destroy_album 'albums/:id/destroy',
:controller => "albums",
:action => "destroy"
# map.connect ':controller/:action/:id'
# map.connect ':controller/:action/:id.:format'
end
The index.erb.html should look like this:
<h1>Listing albums</h1>
<table>
<tr>
<th>Title</th>
<th>Review</th>
</tr>
<% for album in @albums %>
<tr>
<td><%=h album.title %></td>
<td><%=h album.review %></td>
<td><%= link_to 'Show', album_path(album) %></td>
<td><%= link_to 'Edit', edit_album_path(album) %></td>
<td><%= link_to 'Destroy', destroy_album_path(album.id), :confirm => 'Are you sure?' %></td>
</tr>
<% end %>
</table>
<br />
I am sure that you are getting a feel for named routes now…
At this stage the site should be able:
- to show the list of albums in the index page
- show a single album
- Edit an existing album
- Delete an album
Experiment 4.6 Defining a Named Route for New
Change the routes.rb file to this:
ActionController::Routing::Routes.draw do |map|
map.albums 'albums',
:controller => "albums",
:action => "index"
map.album 'album/:id',
:controller => "albums",
:action => "show"
map.edit_album 'albums/:id/edit',
:controller => "albums",
:action => "edit"
map.update_album 'albums/:id/update',
:controller => "albums",
:action => "update"
map.destroy_album 'albums/:id/destroy',
:controller => "albums",
:action => "destroy"
map.new_album 'albums/new',
:controller => "albums",
:action => "new"
map.create_album 'albums/create',
:controller => "albums",
:action => "create"
# map.connect ':controller/:action/:id'
# map.connect ':controller/:action/:id.:format'
end
The index.erb.html should now be:
<h1>Listing albums</h1>
<table>
<tr>
<th>Title</th>
<th>Review</th>
</tr>
<% for album in @albums %>
<tr>
<td><%=h album.title %></td>
<td><%=h album.review %></td>
<td><%= link_to 'Show', album_path(album) %></td>
<td><%= link_to 'Edit', edit_album_path(album) %></td>
<td><%= link_to 'Destroy', destroy_album_path(album.id), :confirm => 'Are you sure?' %></td>
</tr>
<% end %>
</table>
<br /><%= link_to 'New album', new_album_path %>
To stop WordPress from deleting my code, I put link_to ‘New album’ on the same line as the br tag. They are on separate lines in my actual code.
With new.erb.html looking like this:
<h1>New album</h1>
<%= error_messages_for :album %>
<% form_for :album, @album, :url => create_album_path do |f| %>
<p>
<b>Title</b><br />
<%= f.text_field :title %>
</p>
<p>
<b>Review</b><br />
<%= f.text_area :review %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= link_to 'Back', albums_path %>
At this stage the site should be able:
- to show the list of albums in the index page
- show a single album
- Edit an existing album
- Add a new album
Are we done? What about the controller?
Experiment 4.7 Using Named Route Helper Methods in the Controller
Open the controller and look at the destroy action, you will notice this line:
format.html { redirect_to(:controller => “albums”, :action => “index”) }
This shows you that we can still generate routes as if we were using the simple routes. We have defined named routes, we can use the helper methods in the controller, so we might as well.
Make the controller look like this:
class AlbumsController < ApplicationController
# GET /albums
# GET /albums.xml
def index
@albums = Album.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @albums }
end
end
# GET /albums/1
# GET /albums/1.xml
def show
@album = Album.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @album }
end
end
# GET /albums/new
# GET /albums/new.xml
def new
@album = Album.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @album }
end
end
# GET /albums/1/edit
def edit
@album = Album.find(params[:id])
end
# POST /albums
# POST /albums.xml
def create
@album = Album.new(params[:album])
respond_to do |format|
if @album.save
flash[:notice] = 'Album was successfully created.'
format.html { redirect_to(album_path(@album)) }
format.xml { render :xml => @album, :status => :created, :location => @album }
else
format.html { render :action => "new" }
format.xml { render :xml => @album.errors, :status => :unprocessable_entity }
end
end
end
# PUT /albums/1
# PUT /albums/1.xml
def update
@album = Album.find(params[:id])
respond_to do |format|
if @album.update_attributes(params[:album])
flash[:notice] = 'Album was successfully updated.'
format.html { redirect_to(album_path(@album)) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @album.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /albums/1
# DELETE /albums/1.xml
def destroy
@album = Album.find(params[:id])
@album.destroy
respond_to do |format|
format.html { redirect_to(albums_path) }
format.xml { head :ok }
end
end
end
Sure we have a larger routes.rb file, but I am sure you will agree that redirect_to(albums_path)
is much better than redirect_to(:controller => “albums”, :action => “index”)
The End of Part 4
I hope this has helped you to get familiar with named routes. There is a lot more to them, and there is also a lot more syntactic sugar. Once I have completed the section on RESTful routing, I might do a summary post. This would be a recap and it will show some tips and tricks that I skipped over in these posts. I am skipping certain topics as I would like to get onto RESTful routing as soon as possible.
Speaking of RESTful routing, the way we have named the routes is very close to the RESTful way. You will see this in the next post.
It is very easy to make mistakes when writing a tutorial like this. Especially with the gremlins inside of the WordPress formatting engine. If you are one of the first to complete this session successfully then please inform me. More importantly, if you find a problem please let me know.
Thank you, Daryn

May 12, 2008 at 6:49 am
[...] Tutorial: Routing in Rails 2.0 (with REST) - Part 3 of n Part 4 is [...]
May 13, 2008 at 9:06 am
I have fixed the errors brought to my attention by Philip - thanks again.
It turns out that WordPress does not always like the <%= tag. That is why the last two lines of index.erb.html in 4.6 are on the same line. If I separate them, WordPress eats the last line.
Please let me know if you come across any problems…
- Thanks Daryn
May 17, 2008 at 5:39 pm
good article, waiting for the restful one.
Keep up the good work.
May 19, 2008 at 1:44 am
Daryn,
I’m back after having to do some other stuff - do you have an ETA for part 5? (No pressure!)
Regards,
Phil.
May 19, 2008 at 10:43 am
Hi Phil, I did not work on part 5 this weekend. I needed to support the South African teams in the Super 14 - as an Australian, I am sure you understand
Pablo and Phil (and the others waiting for part 5),
Part 5 should be out by next week Monday. My apologise for the delay. I plan on putting a fair amount of effort into it, so it should be worth the wait.
- Daryn
May 19, 2008 at 3:58 pm
Hi Daryn,
I’ve just followed your tutorial and thought it was nicely done. Shame about those formatting problems but the message got through all the same. Looking forward to the next installment.
cheers,
Tony.
May 19, 2008 at 8:09 pm
Daryn,
At least we have one team in the semis - and you should have more time after next weekend!
Regards,
Phil.
May 20, 2008 at 2:57 am
Back again after a diversion.
This time I went back and actually did each tut and able to finish without any problems.
Slowly getting the hang of it.
Thanks and look fwd for the remainder…
Tony
May 21, 2008 at 1:56 pm
Thanks a lot for these tutorials these have come in really hand in understanding just how routing in rails 2.0 works. So when should we expect Part 5?
May 21, 2008 at 3:02 pm
I am glad to help AikiGhost.
I will do my best complete part 5 this weekend. If it is not out on Monday, I will give you a full refund.
May 27, 2008 at 9:35 pm
[...] part 4 we created named routes. The routes.rb file looked like [...]
May 29, 2008 at 11:59 pm
[...] The fourth part of a tutorial on Routing in Rails 2 was recently published. If you haven’t done so, follow the links to part 1, 2, 3 and 4. [...]