Nested Resources to Build Expressive URLs
At this point, you've learned about RailTrackr's capability to serve resources using REST URLs, such as:
- http://yoursite.com/flickr_users/max: Show details for Flickr user max
- http://yoursite.com/photosets/123: Show photoset with ID 123
- http://yoursite.com/photos/456/edit: Edit photo with ID 456
In fact, if you take a look at the basic resource mapping in the routes.rb file, you'll notice that the scaffolding commands have created the following entries:
ActionController::Routing::Routes.draw do |map|
map.resources :photos
map.resources :photosets
map.resources :flickr_users
# default mapping
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
Rails 2.0 offers a way to further improve URLs and make them more readable. Just like the way you configure a one-to-many relationship in ActiveRecord models, you can now declare mappings between resources and use URLs like these ones to navigate across the references:
- http://yoursite.com/flickr_users/max/photosets: Show all max's photosets
- http://yoursite.com/photosets/123/photos: Show all photos within photoset 123
These URLs have more natural names that are easier to remember, and activating them requires just two steps. First, change the routes.rb to reflect the following contents:
ActionController::Routing::Routes.draw do |map|
map.resources :flickr_users
map.resources :flickr_users, :has_many => :photosets
map.resources :photosets, :has_many => :photos
# default mapping
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
end
Then modify your controller by declaring resource mapping between users and their photosets as follows:
class PhotosetsController < ApplicationController
before_filter :load_user
# Given a sample url like site.com/flickr_user/max/photosets ,
# params[:flickr_user_id] automagically points to 'max'
def load_user
@flickr_user = FlickrUser.find(params[:flickr_user_id])
end
# GET /photosets
def index
@photosets = Photoset.by_user(@flickr_user)[0..50]
respond_to do |format|
format.html # index.html.erb
end
end
end
The before_filter statement ensures that the user is loaded before accessing his photosets. The flickr_user_id variable is automatically populated by Rails using a naming convention from the name of the parent resource (flickr_user).
You can use this nested resource mapping not only within controllers but also to generate URLs in your views. Suppose the @flickr_user points to the user with ID max and the @photoset to one of his photosets with ID 123, all the following statements can be legally used within views to generate URLs that you can associate to linking functions such as link_to:
- flickr_user_photosets_url(@flickr_user) will point to www.site.com/flickr_user/max/photosets (list of all the user's photosets)
- flickr_user_photoset_url(@flickr_user,@photoset) will point to www.site.com/flickr_user/max/photoset/123 (details of one specific photoset)
- photoset_photos_url(@photoset) will point to www.site.com/photosets/123/photos (all the photos within photoset 123)