Recipe 6.1. Creating Nested Resources


Problem

Contributed by: Diego Scataglini

You want your application's URLs to reflect the structure of your models. For example, if a user has many blogs, you want the path to a particular blog to be something like /users/1/blogs/1.

Solution

For this recipe, assume you have an empty Rails application and your database.yml file configured to connect to your database. Use the scaffold_resource generator to create your models, views, and controllers:

$ ruby script/generate scaffold_resource User $ ruby script/generate scaffold_resource Blog             

Next, establish a relationship between these models:

app/models/user.rb:

class User < ActiveRecord::Base   has_many :blogs end

app/models/blog.rb:

class Blog < ActiveRecord::Base   belongs_to :user end

Now you need to define your migrations and create some test data:

db/migrate/001_create_users.rb:

class CreateUsers < ActiveRecord::Migration   def self.up     create_table :users do |t|       t.column :name, :string     end     User.create(:name => "Diego")     User.create(:name => "Chris")     User.create(:name => "Rob")   end   def self.down     drop_table :users   end end

db/migrate/002_create_blogs.rb:

class CreateBlogs < ActiveRecord::Migration   def self.up     create_table :blogs do |t|       t.column :title, :string       t.column :user_id, :integer     end     User.find(1).blogs.create(:title => "My work blog")     User.find(1).blogs.create(:title => "My fun rblog")     User.find(2).blogs.create(:title => "My xml blog")     User.find(3).blogs.create(:title => "my Rails Cookbook blog")   end   def self.down     drop_table :blogs   end end

Then use rake to migrate your database:

$ rake db:migrate             

With your schema created, it's time to configure your nested routes:

config/routes.rb:

  map.resources :users do |user|     user.resources :blogs   end

At this point, all CRUD functionality is working for the User model so you're halfway to the finish line. After a few tweaks to the BlogsController, it will be functional as well. You need to make sure that whenever you refer to a Blog object, you specify whose blog it is. Here is the modified blog_controller.rb file (be warned it's rather lengthy):

app/controllers/blog_controller.rb:

class BlogsController < ApplicationController   # GET /blogs   # GET /blogs.xml   def index     @user = User.find(params[:user_id])     @blogs = Blog.find(:all,                       :conditions => {:user_id => params[:user_id]})     respond_to do |format|       format.html # index.rhtml       format.xml  { render :xml => @blogs.to_xml }     end   end   # GET /blogs/1   # GET /blogs/1.xml   def show     @blog = Blog.find(params[:id],                       :conditions => {:user_id => params[:user_id]})     respond_to do |format|       format.html # show.rhtml       format.xml  { render :xml => @blog.to_xml }     end   end   # GET /blogs/new   def new     @blog = Blog.new(:user_id => params[:user_id])   end   # GET /blogs/1;edit   def edit     @blog = Blog.find(params[:id],                       :conditions => {:user_id => params[:user_id]})   end   # POST /blogs   # POST /blogs.xml   def create     @blog = Blog.new(params[:blog])     @blog.user_id = params[:user_id]     respond_to do |format|       if @blog.save         flash[:notice] = 'Blog was successfully created.'         format.html { redirect_to blog_path(@blog.user_id, @blog) }         format.xml do           headers["Location"] = blog_path(@blog.user_id, @blog)           render :nothing => true, :status => "201 Created"         end       else         format.html { render :action => "new" }         format.xml  { render :xml => @blog.errors.to_xml }       end     end   end   # PUT /blogs/1   # PUT /blogs/1.xml   def update     @blog = Blog.find(params[:id],                       :conditions => {:user_id => params[:user_id]})     respond_to do |format|       if @blog.update_attributes(params[:blog])         format.html { redirect_to blog_path(@blog.user_id, @blog) }         format.xml  { render :nothing => true }       else         format.html { render :action => "edit" }         format.xml  { render :xml => @blog.errors.to_xml }       end     end   end   # DELETE /blogs/1   # DELETE /blogs/1.xml   def destroy     @blog = Blog.find(params[:id],                       :conditions => {:user_id => params[:user_id]})     @blog.destroy     respond_to do |format|       format.html { redirect_to blogs_url(@blog.user_id)   }       format.xml  { render :nothing => true }     end   end end

At this point, you have a working XML-based REST API. You can test your application by starting WEBrick and pointing your browser to http://localhost:3000/users/1/blogs/1.xml.

To get your HTML views in working order, you need to fill in the files created by the scaffold generator. One important thing to keep in mind is that the URL helpers for blogs will need a reference to the user as well as the blog instance for which to generate a link. In practice, this means all references to blogs_path(blog), must take the form blogs_path(user, blog). This change is necessary because of the nested structure created by your routes. Here's a sample view demonstrating the edit_blog_path helper method:

app/views/blogs/show.rhtml:

<h1><%= @blog.title %> by <%= @blog.user.name %></h1> <%= link_to 'Edit', edit_blog_path(@blog.user, @blog) %> | <%= link_to 'Back', blogs_path %>

Discussion

The key to this recipe is the nesting that occurs when mapping blogs to users in routes.rb. Making this change will require you to add an extra parameter when calling the helper methods generated by the call to map.resources. Understanding how routes work is critically important.

Should you desire to add a Post model belonging to Blog (that is, a Blog has_many :posts), the routing looks like this:

config/routes.rb:

map.resources :users do |user|   user.resources :blogs do |blog|     blog.resources :posts   end end

The path helper for posts then requires three parameters: post_path(user, blog, post).

See Also

  • Section 6.5"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net