Rails Forum
Rails Work - the best place to post and find great Ruby on Rails jobs.
Username
Password

You are not logged in.

New Posts in this thread

#1 2006-11-02 03:21:57

bp
Passenger
From: Seattle, WA
Registered: 2006-06-22
Posts: 32
Website

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.


Code :   - fold - unfold
  1. saturn:~ bp$ rails jukebox
  2.  
  3. saturn:~ bp$ cd jukebox/
Until Rails 1.2 is released we'll need to grab Edge Rails, which can be done with a rake task.



Code :   - fold - unfold
  1. saturn:~/jukebox bp$ rake rails:freeze:edge
  2. [... lots of output ...]
  3. Exported revision 5383.
Of course we will need a database:


Code :   - fold - unfold
  1. saturn:~/jukebox bp$ echo "create database jukebox" | mysql -u root -p
  2. Enter password:
Don't forget to edit config/database.yml appropriately! Then go ahead and start the server.


Code :   - fold - unfold
  1. saturn:~/jukebox bp$ ./script/server
  2. => 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.


Code :   - fold - unfold
  1. saturn:~/jukebox bp$  ./script/generate scaffold_resource Artist name:string
  2.       exists  app/models/
  3.       exists  app/controllers/
  4.       exists  app/helpers/
  5.       create  app/views/artists
  6.       exists  test/functional/
  7.       exists  test/unit/
  8.       create  app/views/artists/index.rhtml
  9.       create  app/views/artists/show.rhtml
  10.       create  app/views/artists/new.rhtml
  11.       create  app/views/artists/edit.rhtml
  12.       create  app/models/artist.rb
  13.       create  app/controllers/artists_controller.rb
  14.       create  test/functional/artists_controller_test.rb
  15.       create  app/helpers/artists_helper.rb
  16.       create  test/unit/artist_test.rb
  17.       create  test/fixtures/artists.yml
  18.       create  db/migrate
  19.       create  db/migrate/001_create_artists.rb
  20.        route  map.resources :artists
NOTE: I was working on this with revision 5378 yesterday (until I got side-tracked by real work, how annoying wink and at that point still needed to edit config/routes.rb manually. It looks as if that is taken care of for you now. Pretty cool to see something evolve while you are using it.

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.


Code :   - fold - unfold
  1. saturn:~/jukebox bp$ ./script/generate scaffold_resource Album title:string artist_id:integer
  2. [...]
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:


Code :   - fold - unfold
  1. class Artist < ActiveRecord::Base
  2.   
  3.   has_many :albums
  4.   
  5. end

Code :   - fold - unfold
  1. class Album < ActiveRecord::Base
  2.   
  3.   belongs_to :artist
  4.   
  5. end
Add Some Data

We can create the tables by running a db migration.


Code :   - fold - unfold
  1. saturn:~/jukebox bp$ rake db:migrate
And we are ready to start adding to the jukebox. Adding a couple will do.


Code :   - fold - unfold
  1. 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:


Code :   - fold - unfold
  1. ActionController::Routing::Routes.draw do |map|
  2.   
  3.   map.resources :albums
  4.  
  5.   map.resources :artists
  6.  
  7. end
Notice that I have removed the line that usually handles the routing for CRUD operations in scaffolding.


Code :   - fold - unfold
  1. 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:


Code :   - fold - unfold
  1. <td><%= link_to 'Show', artist_path(artist) %></td>
  2. <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.


Code :   - fold - unfold
  1. 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):


Code :   - fold - unfold
  1. ActionController::Routing::Routes.draw do |map|
  2.  
  3.   map.resources :artists do |artist|
  4.     artist.resources :albums
  5.   end
  6.  
  7. end
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:


Code :   - fold - unfold
  1. <%= link_to 'Edit', edit_album_path(params[:artist_id], @album) %>
And now this:


Code :   - fold - unfold
  1. http://localhost:3000/artists/1/albums/1
And there it is! The "show" action for album 1. Ok, how about going to this one?


Code :   - fold - unfold
  1. 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:


Code :   - fold - unfold
  1. <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:


Code :   - fold - unfold
  1. <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:


Code :   - fold - unfold
  1. <td><%= link_to 'Show', album_path(params[:artist_id], album) %></td>
  2. <td><%= link_to 'Edit', edit_album_path(params[:artist_id], album) %></td>
  3. <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):


Code :   - fold - unfold
  1. @albums = Album.find(:all)
To this:


Code :   - fold - unfold
  1. @albums = Artist.find(params[:artist_id]).albums
And try again


Code :   - fold - unfold
  1. 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 15:37:59)

Offline

 

#2 2006-11-02 06:06:33

ryanb
Moderator
Registered: 2006-06-14
Posts: 6323
Website

Re: Getting Started with RESTful Rails

Awesome job with the tutorial! The links at the end are a very nice touch too.


Railscasts - Free Ruby on Rails Screencasts

Offline

 

#3 2006-11-02 14:05:02

vin
Back in action!
From: South Florida
Registered: 2006-05-10
Posts: 842
Website

Re: Getting Started with RESTful Rails

Nice work, and good links!

bp wrote:

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.)

Mongrel's been the default in edge rails for at least a couple of months now, but if you don't install it yourself it will revert to lighttpd/webrick.


vinnie - rails forum admin

Offline

 

#4 2006-11-02 14:09:52

bp
Passenger
From: Seattle, WA
Registered: 2006-06-22
Posts: 32
Website

Re: Getting Started with RESTful Rails

Thanks to both of you.

vin wrote:

Mongrel's been the default in edge rails for at least a couple of months now, but if you don't install it yourself it will revert to lighttpd/webrick.

Hehe. I'd gotten so used to just typing 'mongrel_rails start' I didn't even notice. The only reason I used './script/server' here was because I figured people may not have mongrel installed.

Offline

 

#5 2006-11-02 14:53:44

jardeon
Passenger
From: Raleigh
Registered: 2006-08-15
Posts: 25

Re: Getting Started with RESTful Rails

I ran into one problem with this tutorial, which straightened itself out by the end.

The examples provided work perfectly if you're creating your first album for your first artist.  In my case, I created two artists, and created one album, attributed to the second artist.  But the URL <localhost:3000/artists/2/albums/1> failed, because it wasn't reading the 2 properly.  Putting in /artists/1/albums/1 showed the correct album for artist two.

Once I edited my url helpers to include params[:artist_id] and edited the show action to: @albums = Artist.find(params[:artist_id]).albums, then it all cleared up and worked as intended.

I'm not sure if it's worthy of a note in the tutorial itself, because I don't know how many people will go and do something strange like I did!

Offline

 

#6 2006-11-02 15:18:10

bp
Passenger
From: Seattle, WA
Registered: 2006-06-22
Posts: 32
Website

Re: Getting Started with RESTful Rails

jardeon wrote:

The examples provided work perfectly if you're creating your first album for your first artist.  In my case, I created two artists, and created one album, attributed to the second artist.  But the URL <localhost:3000/artists/2/albums/1> failed, because it wasn't reading the 2 properly.  Putting in /artists/1/albums/1 showed the correct album for artist two.

Thanks for pointing this out. I'm not really sure what would cause that. The show method in the albums controller doesn't rely on the artist_id, so in theory, it shouldn't matter what you put there. I get the same album if I hit /artists/12/albums/4 or /artists/2/albums/4. In practice I would probably want to load the artist and the album, so it would matter, but I'm not sure why this happened. Out of curiosity what revision of Rails are you using?

Offline

 

#7 2006-11-02 15:29:26

jardeon
Passenger
From: Raleigh
Registered: 2006-08-15
Posts: 25

Re: Getting Started with RESTful Rails

It's today's revision 5404.

I think I've found the culprit, this is the error I received:

Code :   - fold - unfold
  1. edit_album_url failed to generate from {:artist_id=>"1", :controller=>"albums", :action=>"edit"}, expected: {:controller=>"albums", :action=>"edit"}, diff: {:artist_id=>"1"}
It's necessary to change the edit_album_url(@album) line to

Code :   - fold - unfold
  1. edit_album_url(params[:artist_id], @album)
before that page will work.

Offline

 

#8 2006-11-02 15:33:39

ryanb
Moderator
Registered: 2006-06-14
Posts: 6323
Website

Re: Getting Started with RESTful Rails

One of my favorite REST-related articles is Refactoring to REST. I thought I would mention it as I didn't see it in the list of links. Seems the comments have been removed on that post - too bad, some were useful.

Edit: Hmm, seems the link stopped working. Well you can go to scottraymond.net and scroll down.

Last edited by ryanb (2006-11-02 15:35:58)


Railscasts - Free Ruby on Rails Screencasts

Offline

 

#9 2006-11-02 15:33:40

bp
Passenger
From: Seattle, WA
Registered: 2006-06-22
Posts: 32
Website

Re: Getting Started with RESTful Rails

Ah, yep, thanks for catching that! I'll add an update.

Offline

 

#10 2006-11-02 15:44:16

bp
Passenger
From: Seattle, WA
Registered: 2006-06-22
Posts: 32
Website

Re: Getting Started with RESTful Rails

ryanb wrote:

One of my favorite REST-related articles is Refactoring to REST. I thought I would mention it as I didn't see it in the list of links. Seems the comments have been removed on that post - too bad, some were useful.

Edit: Hmm, seems the link stopped working. Well you can go to scottraymond.net and scroll down.

Thanks, I forgot to link that one. A great real-world example!

Offline

 

#11 2007-01-05 19:40:17

dinooz
Passenger
From: Albertville, AL
Registered: 2007-01-05
Posts: 29
Website

Re: Getting Started with RESTful Rails

<b> "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." <b> <br>

I should said great Post, really enjoy it and follow up to understand better Restful Applications with Ruby, however this last step still makes me have a hard time, try to edit edit.rhtml, new.rhtml and show.rhtml but seems to mix the artist_id with the regular id.

Once I create a new album the insert put the record into the DB but the show seems to have confusion in the URL put the :id of the Album instead of the :artist_id

Best Regards Dino.

Offline

 

#12 2007-01-09 18:02:50

dinooz
Passenger
From: Albertville, AL
Registered: 2007-01-05
Posts: 29
Website

Re: Getting Started with RESTful Rails

# In albums_controller.rb

@albums = Album.find(:all)
    Replaced with:
@albums = Artist.find(params[:artist_id]).albums

When add New Album or Edit existing Album show Error on URL putting :id instead of :artist_id.
Generate Error
ActiveRecord::RecordNotFound in AlbumsController#index
Couldn't find Artist with ID=9
The URL Looks Like:
http://192.168.1.36:3000/artists/9/albums
Instead of:
http://192.168.1.36:3000/artists/4/albums [ To show the List of Albums] or
http://192.168.1.36:3000/artists/4/albums/9 [ To display the currently added/edited album ]

On Click Show session dump:
---
flash: !map:ActionController::Flash::FlashHash
  :notice: Album was successfully created.

I can tell the new Album or the Edit Album successfully update the DB.
This makes me wonder where after the flash[:notice] my application is being redirected to the wrong URL ???

I wonder if @album = Album.new(params[:id]) need to be changed to something else !!!

Offline

 

#13 2007-01-10 15:57:41

goodieboy
Coach Class
Registered: 2007-01-10
Posts: 61

Re: Getting Started with RESTful Rails

Thanks for the tutorial. I'm still really scratching my head on a few things though...

What is the best way to create a "Administrative" area for your site?? I can't figure this one out. Does it mean I have to put code in every controller just to get an admin layout and views? How do you specify that in the url; the "admin/" part?

What's the point of having a url like /products/32/orders/122? Isn't that a little confusing and this be better: /orders/122? Nested resources don't make sense to me. I can understand using the list view, to show only the related children, but to create, edit or delete seems strange.

Offline

 

#14 2007-01-10 17:51:02

ryanb
Moderator
Registered: 2006-06-14
Posts: 6323
Website

Re: Getting Started with RESTful Rails

Hope you don't mind if I answer this one bp.

goodieboy wrote:

What is the best way to create a "Administrative" area for your site?? I can't figure this one out. Does it mean I have to put code in every controller just to get an admin layout and views? How do you specify that in the url; the "admin/" part?

Good question. Normally in a RESTful design, there is no special administration section. Instead, user authentication is added and used to determine if a given user is an admin. If he is, he receives special privileges (access to certain actions). You can use simple if conditions in the view to show certain links for the admins. You can then use a before_filter to determine the user going to the given action has the proper access.

For example, if you create an Albums controller and you only want users to access the list/show actions, you can add a before_filter to the create/update/destroy actions to make sure the logged in user is an administrator. If you want a special layout for the admins, you can do this too by passing a symbol to the layout method so it calls another method to determine the layout (see the example in the link).


goodieboy wrote:

What's the point of having a url like /products/32/orders/122? Isn't that a little confusing and this be better: /orders/122? Nested resources don't make sense to me. I can understand using the list view, to show only the related children, but to create, edit or delete seems strange.

Another good question. The only benefit I see is the "/products/32/orders" url where this will show all orders for the given product. Once you get to specifying two ids it gets a little ridiculous IMO. I'm honestly not a fan of nested resources and prefer to just stick with the simple URLs, but other people like them and that's fine with me. smile


Railscasts - Free Ruby on Rails Screencasts

Offline

 

#15 2007-01-10 20:40:09

goodieboy
Coach Class
Registered: 2007-01-10
Posts: 61

Re: Getting Started with RESTful Rails

OK thanks for that. I get it, it's different than what I'm used to but I'll give it a try.

Another questions here... I've noticed that when you go to a resource/new location... and then post with errors, the errors are missing... because the for actually does a redirect to "create". How do we handle errors from here??  Not sessions? sad

Thanks again!

- matt

p.s. I just bought the peep code video and it's really great!.

Offline

 

#16 2007-01-10 20:46:51

goodieboy
Coach Class
Registered: 2007-01-10
Posts: 61

Re: Getting Started with RESTful Rails

Another question!

If you create a named resource route... and you want to redirect to the named route instead of the action name... how is that done?

I've assigned "/login" to the restful_authentication plug-in 'sessions/new' url. But when there is an error, it brings me back to '/sessions' url (it's using redirect_back_or_default).

I just don't want to hard code: "redirect_back_or_default '/login'" into my controller, when that "/Login" reference is being created in routes.rb. Do you know what I mean? Or is that just the way it has to work?

- matt

Offline

 

#17 2007-01-10 21:29:28

ryanb
Moderator
Registered: 2006-06-14
Posts: 6323
Website

Re: Getting Started with RESTful Rails

goodieboy wrote:

Another questions here... I've noticed that when you go to a resource/new location... and then post with errors, the errors are missing... because the for actually does a redirect to "create". How do we handle errors from here??  Not sessions? sad

It is common to just render the "new" template instead of redirecting, this way the instance variable containing the validation errors is not lost. You could also use sessions, but this is easier.


Code :  ruby - fold - unfold
  1. def create
  2.   @album = Album.new(params[:album])
  3.   if @album.save
  4.     flash[:notice] = "Successfully saved album!"
  5.     redirect_to :action => 'index'
  6.   else
  7.     render :action => 'new' # render here, not redirect
  8.   end
  9. end 
Also, you can display the errors in the form view like this:


Code :  ruby - fold - unfold
  1. # new.rhtml
  2. <%= error_messages_for :album %>

goodieboy wrote:

I've assigned "/login" to the restful_authentication plug-in 'sessions/new' url. But when there is an error, it brings me back to '/sessions' url (it's using redirect_back_or_default).

Try placing it above the other map.connect/map.resource definitions in the routes.rb file. When building the URL, the first possible route that passes the conditions is used.


Railscasts - Free Ruby on Rails Screencasts

Offline

 

#18 2007-01-10 21:49:48

goodieboy
Coach Class
Registered: 2007-01-10
Posts: 61

Re: Getting Started with RESTful Rails

OK thanks getting somewhere now... but strangely (I'm VERY new to rails and ruby) I can't seem to set an instance variable in the create action, and view it in the new action template. . If I set the instance variable in the new action, the template gets it. How can I set the error for a login (session resource) in the create action, render the new action, and then have the new action template display the error message? The session is not an activerecord model. (but a resource).

- matt

Offline

 

#19 2007-01-10 22:06:38

ryanb
Moderator
Registered: 2006-06-14
Posts: 6323
Website

Re: Getting Started with RESTful Rails

goodieboy wrote:

OK thanks getting somewhere now... but strangely (I'm VERY new to rails and ruby) I can't seem to set an instance variable in the create action, and view it in the new action template. . If I set the instance variable in the new action, the template gets it. How can I set the error for a login (session resource) in the create action, render the new action, and then have the new action template display the error message? The session is not an activerecord model. (but a resource).

Hmm, I'm not sure what the problem is, the template should have access to any instance variables defined in the action. For example:


Code :  ruby - fold - unfold
  1. # controller
  2. def new
  3.   @foo = 'bar'
  4. end
  5.  
  6. def create
  7.   @foo = 'blah'
  8.   render :action => 'new'
  9. end
  10.  
  11. # in new.rhtml
  12. <%= @foo %>
This should return 'bar' or 'blah' depending upon what action ran.


Railscasts - Free Ruby on Rails Screencasts

Offline

 

#20 2007-01-17 17:44:23

bp
Passenger
From: Seattle, WA
Registered: 2006-06-22
Posts: 32
Website

Re: Getting Started with RESTful Rails

Thanks for following up on these ryan! I've been out of the country for a while and just got back. I'm hoping to dig back into this in the next few days.

Offline

 

Board footer

Powered by PunBB
© Copyright 2002–2005 Rickard Andersson