Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
Almost Painless Nested Resources
It’s often the case that you have a restful resource that needs to be accessible nested, and at root. For example, let’s say we have a storefront with a bunch of users and products. Each user can play with their own products, and the admin users can play with everyone’s products. You’ve got a :products resource at root, and a nested version to scope it under :users
1 2 3 4 5 6 7 |
# Admin see all products at /products map.resources :products map.resources :users do |users| # Users see theirs at /users/:id/products users.resources :products, :name_prefix => "user_" end |
That’s all fine and dandy, but your controller is gonna get really messy, real quick:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ProductsController < ApplicationController def index if params[:user_id] @products = User.find(params[:user_id]).products else @products = Product.find(:all) end end def show if params[:user_id] @products = User.find(params[:user_id]).products.find(params[:id]) else @products = Product.find(params[:id]) end end #... end |
Now, you’ll immediately see that this can be dried up fairly nicely by taking advantage of the duck-similarities between Product and @user.products:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class ProductsController < ApplicationController before_filter :load_user def index @products = products.find(:all) end def show @products = products.find(params[:id]) end #... private def load_user @user = User.find_by_id(params[:user_id]) end def products @user ? @user.products : Product end end |
This is definitely a step in the right direction. But we get a real good kick in the face by our ActiveRecord associations. Instead of defining user.products.new(), they went with user.products.build(), completely un-drying our :new and :create actions!
Now, this will be fixed by a simple :alias_method call in Rails 2.0, but since we’re sticking with stable code for now, we’ll have to do it by hand.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module ActiveRecord module Associations class HasManyThroughAssociation alias_method :new, :build end class HasManyAssociation alias_method :new, :build end class HasAndBelongsToManyAssociation alias_method :new, :build end end end |
Just put that in your /lib, and require it. Now you’ve got yourself a painless nested (or non) resource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
class ProductsController < ApplicationController before_filter :load_user before_filter :authorize def index @products = products.search(params[:search]) end def show @product = products.find(params[:id]) end def new # This might use our alias... or it might not... how exciting! @product = products.new end def edit @product = products.find(params[:id]) end def create # Once again, meta programming to the rescue! @product = products.new(params[:product]) if @product.save flash[:notice] = :success redirect_to_product(@product) else render :action => "new" end end def update @product = products.find(params[:id]) if @product.update_attributes(params[:product]) flash[:notice] = :success redirect_to_product(@product) else render :action => "edit" end end def destroy @product = products.find(params[:id]) @product.destroy flash[:notice] = :success redirect_to_collection end private def authorize # blah blah deny access... end def load_user @user = User.find_by_id(params[:user_id]) end def products @user ? @user.products : Product end # These are just sugar... def redirect_to_collection redirect_to(@user ? user_products_url(@user) : products_url) end def redirect_to_product(p) redirect_to(p.user ? user_product_url(p.user, p) : product_url(p)) end end |
This should prove even easier with all of the resource and url changes coming in Rails 2.0.
About this entry
You're reading an entry on GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS, the company weblog of thoughtbot, inc.
- Author:
- Tammer Saleh
- Published:
- October 23rd 04:35 PM
- Updated:
- October 23rd 04:35 PM
- Sections:
- Development
thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; Jester, a REST/ActiveResource client library written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.
9 comments
Jump to comment form