Topic: Getting Started with RESTful Rails
Since DHH's keynote at RailsConf there has been a lot of talk about RESTful rails. I finally decided to get up to speed and start exploring. This is a look at the new scaffold_resource and routing that will be in Rails 1.2.
The Rails application is a sort of "jukebox" or more accurately a list of artists and their albums. But enough about the name, let's get started.
saturn:~ bp$ rails jukeboxsaturn:~ bp$ cd jukebox/
Until Rails 1.2 is released we'll need to grab Edge Rails, which can be done with a rake task.
saturn:~/jukebox bp$ rake rails:freeze:edge
[... lots of output ...]
Exported revision 5383.
Of course we will need a database:
saturn:~/jukebox bp$ echo "create database jukebox" | mysql -u root -p
Enter password:
Don't forget to edit config/database.yml appropriately! Then go ahead and start the server.
saturn:~/jukebox bp$ ./script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
NOTE: Whoa! Default mongrel server. Sweet! (This might have changes a long time ago and I've just noticed. If that's the case please forgive my tardy elatedness.)
Scaffold Time
Halt right there! Didn't you read the first paragraph? There is a new generator in town and his name is scaffold_resource and has he got some fancy tricks. He'll give us RESTful scaffolding along with a migration WITH a table definition all in one fell swoop. Oh, AND some routing love. We get all of that by describing the columns and data types we want (separated by a ":") in the generate command.
saturn:~/jukebox bp$ ./script/generate scaffold_resource Artist name:string
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/artists
exists test/functional/
exists test/unit/
create app/views/artists/index.rhtml
create app/views/artists/show.rhtml
create app/views/artists/new.rhtml
create app/views/artists/edit.rhtml
create app/models/artist.rb
create app/controllers/artists_controller.rb
create test/functional/artists_controller_test.rb
create app/helpers/artists_helper.rb
create test/unit/artist_test.rb
create test/fixtures/artists.yml
create db/migrate
create db/migrate/001_create_artists.rb
route map.resources :artists
NOTE: I was working on this with revision 5378 yesterday (until I got side-tracked by real work, how annoying
Let's keep both models as simple as possible and only give the Album model a title column and an artist_id for the association.
saturn:~/jukebox bp$ ./script/generate scaffold_resource Album title:string artist_id:integer
[...]
Discography
An artist has_many albums so we will need appropriate associations in each model. Here is what I have in artist.rb and album.rb respectively:
class Artist < ActiveRecord::Base
has_many :albums
end
class Album < ActiveRecord::Base
belongs_to :artist
end
Add Some Data
We can create the tables by running a db migration.
saturn:~/jukebox bp$ rake db:migrate
And we are ready to start adding to the jukebox. Adding a couple will do.
http://localhost:3000/artists
Once that is done we can start adding albums, but before doing that let's take a quick peak at config/routes.rb. I've deleted everything except the lines added by scaffold_resource and it now looks like this:
ActionController::Routing::Routes.draw do |map|
map.resources :albumsmap.resources :artists
end
Notice that I have removed the line that usually handles the routing for CRUD operations in scaffolding.
map.connect ':controller/:action/:id'
We will have something similar with the two map.resources lines. These give us a couple of things, one of which is named routes (http://wiki.rubyonrails.org/rails/pages/NamedRoutes) and another is convenience methods for those routes. Taking a look at app/views/artists/index.html we see these two lines:
<td><%= link_to 'Show', artist_path(artist) %></td>
<td><%= link_to 'Edit', edit_artist_path(artist) %></td>
artist_path() and edit_artist_path() are the convenience methods for the named routes for artists. Named routes are not new, but there are a bunch more now (link to http://peepcode.com/articles/2006/10/08/restful-rails).
I think these are mighty useful. Instead of doing :controller => 'artist', :action => 'show', :id => artist, we have artist_path(artist). This is especially helpful if you make changes down the road.
Ok, now how about some albums.
http://localhost:3000/albums/new
Enter the title of the album and the _id_ of the artist you want to file it under. This is also different from the regular scaffolding. Foreign key _id columns are not included in the regular scaffolding and they here. Add some albums for each of the artists you entered and we'll get on with associations.
Nesting
It would be nice to only show albums for a certain artist. I mean, I don't wanna see any Britney Spears mixed in with my Willie & Lobo. We can do this with nested routes (in routes.rb):
ActionController::Routing::Routes.draw do |map|map.resources :artists do |artist|
artist.resources :albums
endend
Which is like saying that albums are mapped within artists. Now if you go to an album url like before, for example, http://localhost:3000/albums/1, you will get a routing error. However, knowing that album 1 is by artist one, we try this:
UPDATE Thanks to jardeon for pointing out that you need to change app/views/albums.rhtml before this will work with artists other than 1. The edit link needs the artist_id in it:
<%= link_to 'Edit', edit_album_path(params[:artist_id], @album) %>
And now this:
http://localhost:3000/artists/1/albums/1
And there it is! The "show" action for album 1. Ok, how about going to this one?
http://localhost:3000/artists/1/albums/
Hrm.. That didn't work. Why not? The error we get is saying something about a RoutingError around line 14 of app/views/albums/index.rhtml, which is this:
<td><%= link_to 'Edit', edit_album_path(album) %></td>
The reason it isn't working anymore is because of the nesting. What we need to do is specify the artist _and_ the album, like this:
<td><%= link_to 'Edit', edit_album_path(params[:artist_id], album) %></td>
We'll also need to change the other convenience methods to include the artist_id. All the new link_tos in index.rhtml will look like this:
<td><%= link_to 'Show', album_path(params[:artist_id], album) %></td>
<td><%= link_to 'Edit', edit_album_path(params[:artist_id], album) %></td>
<td><%= link_to 'Destroy', album_path(params[:artist_id], album), :confirm => 'Are you sure?', :method => :delete %></td>
Ok, great, now the albums are showing up again, but we are getting all albums when we only want albums for the specified artist. We can do this by changing the index method in albums_controller a bit. Change this (line 5):
@albums = Album.find(:all)
To this:
@albums = Artist.find(params[:artist_id]).albums
And try again
http://localhost:3000/artists/1/albums
Jackpot! We get a listing of albums for artist 1. Too bad the links on this page are now broken. We need to make changes to edit.rhtml, new.rhtml, and show.rhtml just like we did for index.rhtml and include the artist_id in the convenience methods.
That All?
No, I've really only scratched the surface here. If you want to dig in some more, I highly recommend the Restful Rails screencast from peepcode.com. There is also a REST cheat sheet on that page. And there are a ton of blog posts out there. One thing that I'd like to play around with more is the combination of a RESTful service and using ActiveResource to consume it:
http://www.ryandaigle.com/articles/2006
ce-is-here
http://blog.mauricecodik.com/2006/06/in
ource.html
Oh, and a note on using scaffolding, regular or resource flavored. It's a nice way to get up and running quickly, but don't rely on it. ryanb just posted a nice piece related to this: http://railsforum.com/viewtopic.php?id=509. And check out Amy Hoy's post too: http://www.slash7.com/articles/2005/12/ caffolding
Lots O Links
DHH's keynote:
http://blog.scribestudio.com/articles/2
te-address
http://www.ryandaigle.com/articles/2006 ce-is-here
http://jimonwebgames.com/articles/2006/ d-say-fucd
http://blog.hasmanythrough.com/articles ship-model
http://cwilliams.textdriven.com/article -rails-1-2
http://casperfabricius.com/blog/2006/06 sconf-dhh/
http://www.xml-blog.com/articles/2006/0 hh-keynote
http://www.loudthinking.com/arc/000593.html
http://peepcode.com/articles/2006/10/08/restful-rails
Last edited by bp (2006-11-02 11:37:59)