Beginners Tutorial: Routing in Rails 2.0 (with REST) – Part 6 of n

In part 5 we looked at RESTful routing. In this part we continue to explore RESTful routing, by examining nested resources.

I originally planned to cover nested routes and to cover two questions posted to me in previous comments and emails. One question was with regards to ‘one to many relationships’ e.g. an album has many songs. The second question was from Tom in part 3; this was about adding save as functionality.

In trying to answer these questions I created a new application – a text editor with a version history. This has turned out to be a fairly interesting piece of work. I then changed my mind and decided to include it in a follow up post. I am hoping to have that out later this week (before Friday the 11th of July).  As the ‘follow up’ post will have a practical example of nested resources, this post will not have the usual practical section with the experiments.

Part 6

Introduction to Nested Resources

The Nested URI

Returning to the familiar Music Store application, imagine we added songs to the application. Like Albums, Songs are resources and we could expect to access a song with this URI:

/songs/124

We could then perform the required CRUD operations on that URI based on the REST API as discussed in part 5.

An album has many songs. We can express this within the URI e.g.

/albums/10/songs/124

Once again we could make use of the HTTP verbs to perform CRUD in a RESTful way.

In this particular implementation, this URI does not mean that this is the 124th song of album 10 – it is possible for it to be the fist song of album 10.  The URI is pointing to the 124th song in the system.

If we wanted the URI to state the songtrack number of the specified album then we could alter the above implementation. Then we could expect to see URIs of:

/albums/10/songs/5
and
/albums/11/songs/5

This would not result in a URI conflict.

Implementing Nested resources in Rails

If we decided to add songs to the Music Store application, we could start off by aiming for a URI of:

/songs/124

As you may recall, we can achieve this by declaring this resource in the routes.rb file:

map.resources :songs

This will give us access to helper methods such as songs_url, song_url (@song) etc. This was discussed in part 5 under ‘Experiment 5.2 Showing the Index Page’.

Next we could aim for a nested URI:

/albums/10/songs/124

To achieve this we use the following construct:

  map.resources :albums do |albums|
    albums.resources :songs
  end

Note that it is albums.resources :songs and not map.resources.

With this routing rule, Rails will generate slightly different helper methods. To view these routes we could use the method described in part one. There is also a more convenient rake task: rake routes.
This is one of the ‘things’ I have been meaning to mention. You may have read it in a previous comment.

If you use ‘rake routes’ you will notice that there are a quite a lot helper methods being generated, more than the 7 I pointed out in part 5. The ‘extra’ methods are there to create URIs for different formats.

Below is filtered output from ‘rake routes’

 album_songs 		GET    	/albums/:album_id/songs	            {:action=>"index", :controller=>"songs"}
 			POST 	/albums/:album_id/songs             {:action=>"create", :controller=>"songs"}
 new_album_song	 	GET   	/albums/:album_id/songs/new         {:action=>"new", :controller=>"songs"}
 edit_album_song	GET    	/albums/:album_id/songs/:id/edit    {:action=>"edit", :controller=>"songs"}
 album_song 		GET    	/albums/:album_id/songs/:id         {:action=>"show", :controller=>"songs"}
 			PUT    	/albums/:album_id/songs/:id         {:action=>"update", :controller=>"songs"}
 			DELETE 	/albums/:album_id/songs/:id         {:action=>"destroy", :controller=>"songs"}

As you can see, we now have helper methods which look like album_songs_url and album_song_url(@song). The path versions are also available.

Inside the view we could have:
link_to ‘Edit’, edit_album_song(@album, @song)

Looking at the rake routes output above, we can see that the songs controller can get access to the album ID via params[:album_id]. The song ID can be obtained using params[:id].

Now we have helper methods that generate URIs of the form:

/albums/10/songs/124

Our routing system also knows how to deal with incoming URIs of that form.
Now we would like an implementation that allows us to use URIs like:

/albums/10/songs/5

Where song 5 really is the 5th song in album 10. Therefore we could have:

/albums/10/songs/5
and
/albums/20/songs/5

As with most things, it is easy once you know how. To achieve this we simply need to override the ‘to_param’ method in the song model. I imagine that would look something like this:

def to_param
number # number is the track number of this song on the album
end

After this change we would need to modify the controller as params[:id] would return the song number and not the song id. The controllers would need to use

@song = Song.find_by_number(params[:id])
and not
@song = Song.find(params[:id])

There will be more on this in the follow up text editor post.

Deep Nesting

Jamis Buck recommends that resources should never be nested more than one level deep. You can find his original post about that here. The Rails community seems to be divided about this ‘rule of thumb’. I recommend that you follow this guideline – although remember it is just a guideline, you can use deeper nesting if the need arises.

End of Part 6

This was a quick and short introduction to nested resources. There is a bit more to them, but this enough to start writting Rails applications.

In the follow up, ‘plain text editor’, post we will see a nested route being used a slightly larger application. In that post we will look far more than just routing, that is why I took it out of this part. I will finish that up ASAP.  I hope you have enjoyed this post…

– Thank you, Daryn

33 thoughts on “Beginners Tutorial: Routing in Rails 2.0 (with REST) – Part 6 of n

  1. Pingback: Beginners Tutorial: Routing in Rails 2.0 (with REST) - Part 5 of n « Project Entropy

  2. Pingback: Thinking in Rails – Part 0 « Project Entropy

  3. Thanks for this excellent tutorial. While I have a lot of programming experience, I am a total newb to Ruby On Rails. This helped clarify much of the magic going on under the hood.

  4. Nice series of tutorials. I have passed them on to friends just starting out.
    One question about part 6 tho…

    Wouldn’t “@song = Song.find_by_number(params[:id])” return an array of all songs which are the fifth on their album? Perhaps a different way to order songs to albums would be a has_many_through relationship where the join table held the track number and the getter in the Album model defaulted to order by album_songs.track?

    Then you could say: @song = Album.find(:album_id).songs[:id] or something.

  5. suppose i want to ad a new method in my controller whoes name will be create_menu_item
    and i have set the view for it . and if i call item/create_menu_item
    suppose item will be my controller name and create_menu_item will be method name
    what rout should i set in rout.rb. so that i can use redirect_to :action=>’create_menu_item’

  6. Hi rohit,

    Try this and let me know if it worked:
    map.connect ‘item/create_menu_item’, :controller => “item”, :action => ‘create_menu_item’

    Then use redirect_to :controller => ‘item’, :action=>’create_menu_item’

    That should get you into that action…

    If you are in the item controller you don’t need to specify the controller.

    – Daryn

  7. Hi Sir
    if i remove this line from rout.rb map.resources :items
    and added this line map.connect ‘item/create_menu_item’, :controller => “item”, :action => ‘create_menu_item’ then it will work.
    and if i remove this line i.e. map.connect then it will give me error “Only get, head, post, put, and delete requests are allowed.”and if i keep both the line i.e. map.connect and map.resource then it will go to show method and give the error id not found

    can i override route means item/create_menu_item should go to this method only?

  8. please continue
    means by keeping both the line i.e. map.resource and map.cooent
    so that i can use the restful resources as well as my own method call

  9. Hi rohit,

    You can mix in your own custom routes with restful routes.
    Use to rake routes at the command line to view all the defined routes in your system.

    Play around, perhaps read parts 1 – 5 again, and some other material. If you still have problems, get back to me and I will try to help.

    Thanks

  10. Great job!
    You’ve cleared all my doubts about REST paradigm and “magical” scaffolding code generated by Rails!
    Thank you very much!

  11. First, thanks for a great tutorial. One interesting thing to point out, in the show.html.erb, I substituted link_to with button_to for the Back action. This caused Rails to actually do an insert and not send the request back to the index.html.erb.

  12. Hi Daryn,
    I am a new to ROR , earlier was working on J2EE technologies. Read through all the 6 posts & they are really helpful in decyphering the Rails magic :).

    I had a general question which is not related to your post, but thought you could answer this question based on your expertise & indepth understanding of Rails.

    I have earlier been involved in a website development where we developed RESTful webservices & on top of it built a single page web app (completely on AJAX). This was completely on J2EE technologies.

    We are currently in the process of developing a new website & are evaluating all the latest technologies to build the front end.
    When I started using Rails, one thing I could notice was it hides a lot from the programmer & adds in this so called magic. Surely this helps in building a test application or a simple site quickly, but when one wants to build a enterprise level application most of the stuff generated by scaffold will be of no use, (we would have our own html, styles etc..). Also the way the DB tables are created via scaffold, I am not sure if that could be used to create all the tables at enterprise level.
    My questions:
    1. Would ROR really reduce the development time when building enterprise level applications. (since most of the stuff generated by scaffold would be of no use). ?
    2. Are Rails applications scalable & what about the load they can handle?
    3. Till now I have been creating rails 2.0 applications via scaffolding. i.e. first generate all the required controller/view & migrate scripts. Then create the DB from the migration script. Is there a way to do the reverse, i.e. I create the DB tables all by myself (direct SQLs), from there create a migration script which reflects my DB & from the migration script ask scaffold to generate controller & views? (having worked directly with SQLs for somany yrs for table creation & alter, creating them through rails migration script was sth which I did not like)
    4. Are there enough tools to check the quality of the code & performance for a rails app. (similar to EMMA reports or PMD metrics?)
    5. Do you know of any sites where they talk of using Rails for building apps at Enterprise level?Sites which talk of Best practices on building enterprise applications on Rails?

    Would be really helpful, if you could answer the above questions.

    Thanks in Advance,
    Jagz!!!

  13. Hi Jags,

    just recently we have done a small competition implementing a simple webapp using different technology stacks inlcuding RoR. The time was limited. You got points for level of functionality multiplied by req/s. You can read about it on http://jaksa.wordpress.com/

    1. My personal conclusion is that Rails will not give you a significant productivity improvement if you’re not experienced with it. The code is more compact but it felt a little bit like a trial and error approach to programming. Moreover it seems difficult to customize. I’m certain an experienced RoR developer would write a small app (somewhat) faster than an experienced J2EE developer, but I doubt that would be true for any non trivial application. But probably I’m not qualified to talk about this, since I haven’t done a thorough productivity study.

    2. For performance you can see the results I published on my blog. Ruby (Ramaze) seems 2-3 times slower than the EJB implementation, but none of them is tuned properly. Moreover Ruby (both Rails and Ramaze) drops connections under heavy load instead of queueing them. If you want to know more about the speed of ruby check also http://shootout.alioth.debian.org/u32q/benchmark.php?test=all&lang=all

    I’m also conducting a small “research” project http://code.google.com/p/cloudspeed/ to measure the scalability of webapp technologies in the cloud, so I will be deploying these implementations on clusters and measuring scalability when the number of servers goes up. Check the site for updates during the next couple of months.

    Cheers,

    Jaksa

  14. Pingback: Bookmarks about Project

  15. Thank you so much for this entire series !!

    Like many other newbies routing was always difficult for me to grasp. I read many tutorials, chapters in books and never got that “warm fuzzy” until I found your series.

    THANKS !!!

  16. Very Good Work, Daryn!!! Many Thanks and I Look forward to reading your contributions in future on other technologies also (Google’s Go Maybe?)

Leave a Reply

Your email address will not be published. Required fields are marked *