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
resources :sites do resources :page end
namespace :hosting do resources :sites do resources :page end end
resources :site do namespace :hosting do resources :page end end
Those are all the easy ones. Sorry.
The Less Easy Ones
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
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
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.
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
/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!
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.
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.