I've previously written about my approach to namespacing models in Rails. However, as I mentioned in that article, often times namespacing your models will run you up against the edges of the magic in Rails. That's definitely true for routing. As part of my work on a premium scaffolding tool for Rails, I've had to test every combination of namespacing possible and, among other things, ensure the routes generated work as expected. Well, now that I've done that, I wanted to put all the examples down in writing.
* This article assumes you're using shallow nesting for your nested resources. If you're not already using shallow routes or aren't familiar with the feature, check out the related documentation. I think it's the best way to keep the routing helpers for your nested resources sane! (I use shallow routing 100% of the time now.1)
The Easy Ones
Site
and Page
resources :sites do
resources :page
end
Hosting::Site
and Hosting::Page
namespace :hosting do
resources :sites do
resources :page
end
end
Site
and Hosting::Page
resources :site do
namespace :hosting do
resources :page
end
end
Those are all the easy ones. Sorry.
The Less Easy Ones
Project
and Projects::Deliverable
collection_actions = [:index, :new, :create]
resources :projects do
scope module: 'projects' do
resources :deliverables, only: collection_actions
end
end
namespace :projects do
resources :deliverables, except: collection_actions
end
Using scope module: 'projects'
means the router will look for DeliverablesController
in the Projects
namespace, but will only expect projects/1/deliverables
in the URL instead of the redundant projects/1/projects/deliverables
.
However, when addressing actions that don't required the parent resource referenced in the URL (e.g. shallow routing), you still want /projects/
as a "namespace" in the URL, so that's why you're establishing two resources: One for collection actions and one for member actions.
We now have two definitions of resources :deliverables
, so one question remains: If we have resources nested under resources :deliverables
, which of these two definitions do we define the nested resources under? The answer is the second one, the one for member actions.
Projects::Deliverable
and Objective
Under It
namespace :projects do
resources :deliverables
end
resources :projects_deliverables, path: 'projects/deliverables' do
resources :objectives
end
This one is kind of crazy, but to understand how it works you can imagine the various URLs that are enabled by these definitions:
namespace :projects do
resources :deliverables
end
This enables /projects/deliverables/10
and defines the routing helper projects_deliverables_path
. It routes requests to Projects::DeliverablesController
(as we want it to).
resources :projects_deliverables, path: 'projects/deliverables' do
# ...
end
This would also enable /projects/deliverables/10
, except it would try to use ProjectsDeliverablesController
to handle the request. That controller doesn't exist, but that doesn't matter because this URL is already being routed to the correct controller by the previous definition. However, when we use it as a container for the nested resources :objectives
as seen here:
resources :projects_deliverables, path: 'projects/deliverables' do
resources :objectives
end
... this enables /projects/deliverables/10/objectives
to be routed to the un-namespaced ObjectivesController
and ensures that controller receives params[:projects_deliverable_id]
as well. Pretty great!
Abstract::Concept
and Concrete::Thing
namespace :abstract do
resources :concepts
end
resources :abstract_concepts, path: 'abstract/concepts' do
namespace :concrete do
resources :things
end
end
This is very similar to the previous example, but the child resource isn't only not in the parent namespace, but it's in a different namespace. No problem, that's only an incremental change once you understand the last example.
That's It
If you know of a better way to do any of these things, please let me know and I'll update the article. I can't think of any other examples of complicated namespacing relationships that aren't either covered above or can't be covered through some combination of the examples and techniques described above. If you think of one, please send me a DM on Twitter so I can chew on it and update this article!
1 Shallow nesting might seem unideal for SEO in situations where you'd prefer the URL to represent more of a breadcrumb or hierarchy for the resource someone is looking at. That's true, but in those situations where an application's public URL structure needs to be in a particular way for SEO purposes, I don't use Rails' routing DSL at all, but instead use a separate library I've never released to sidestep Rails' routing and route arbitrarily structured URLs to specific controllers and resources. I started doing this because, for SEO purposes, a URL like /los-angeles/family-photographers
is more desirable to me than the vanilla Rails + FriendlyId result of /areas/los-angeles/categories/family-photographers
. I'm sorry I don't have a better resource to link to for this, but feel free to reach out to me via DM on Twitter if you need something like this.