Routing in Rails is a breeze, but when dealing with polymorphic resources it is less than polished. Consider this example: an eCommerce application consists of products, which I can view in the scope of the store, a category or my shopping cart. This would require the following routes:
/products/1
/category/2/products/1
/cart/3/products/15
This is simple to represent, but how do we handle these scopes in the controller? The accepted solution seems to be:
def find_products
if params[:category_id]
@p = Product.find :all, :conditions => { :category_id => params[:category_id] }
elsif params[:cart_id]
@p = Product.find :all, :conditions => { :cart_id => params[:cart_id] }
else
@p = Product.find :all
end
end
This is an ugly solution. What if I now want to find products in the scope of a Supplier, or an Order? This makes highly coupled code, that will be hard to maintain.
The Solution
The solution starts in the controller. We need to find the scope dynamically:
def find_scope
if params[:productable_type]
@scope_class = params[:productable_type].singularize.classify.constantize
@scope = @scope_class.find params[:productable_id]
else
@scope_class = nil
@scope = Product
end
end
Then, we find products in the dynamic scope:
def find_products
@products = @scope.find :all
end
def find_product
@product = @scope.find params[:id]
end
But where does the productable_type parameter come from? The routes file.
Routing the Resource
In the routing file, we can now set up the routes to the products controller:
map.resources :products
map.resources :categories do |category|
category.resources :products, :path_prefix => ':productable_type/:productable_id'
end
map.resources :carts do |cart|
cart.resources :products, :path_prefix => ':productable_type/:productable_id'
end
This looks like perfectly normal nested resources, but we defined a path_prefix on the instances of products. Why? It allows us to capture more of the URL. If we use rake routes to print out the routing table, we find:
/:jobable_type/:jobable_id/jobs
When we make a request to /categories/2/jobs, the parameters hash contains:
Parameters: {"productable_id"=>"2", "productable_type"=>"categories"}
Using the methods we defined in the controller, the scope is found and the products are found in the correct scope.
Using Named Routes
We have solved the routing problem, but there’s a problem. I want to use named routes, such as category_products_path(2) to link to the products page of category 2. The problem is, calling this raises an exception, because Rails is expecting the new productable_type variable to be passed.
I have yet to find a perfect solution to this. A reasonable suggestion is to define a library to override routes.
module PolymorphicRoutes
def category_products_path(*args)
super 'category', *args
end
def category_product_path(*args)
super 'category', *args
end
end
This automatically adds the ‘category’ argument to the start of the path.
To make this available to both controllers and views, we include it twice: once in ApplicationController and again in ApplicationHelper.
Polymorphic Routing is solved.

