[Rails Notes] Nested routes – scope vs namespace
(Credit: this article is heavily based on this post)
Overview
Both namespace and scope accepts a set of options to customize nesting behaviors. Specifically in this article, we focus on the nesting behavior on controller, URI and named path helper.
According to the Rails 5.2 API doc and source code, namespace delegates its options to scope. So essentially everything goes through scope.
When not nested
Say we have following in routes.rb:
resources :resources
Running rake routes will return:
| Prefix | Verb | URI Pattern | Controller#Action |
|---|---|---|---|
resources | GET | /resources(.:format) | resources#index |
| POST | /resources(.:format) | resources#create | |
new_resource | GET | /resources/new(.:format) | resources#new |
edit_resource | GET | /resources/:id/edit(.:format) | resources#edit |
resource | GET | /resources/:id(.:format) | resources#show |
| PATCH | /resources/:id(.:format) | resources#update | |
| PUT | /resources/:id(.:format) | resources#update | |
| DELETE | /resources/:id(.:format) | resources#destroy |
With namespace – one size fits all
If we nest resources inside a namespace, by default, it will respect the given namespace and expect it to be:
- the name of a module that encloses the controller;
- URI prefix;
- prefix of named path helpers
namespace :namespace do
resources :resources
end
| Prefix | Verb | URI Pattern | Controller#Action |
|---|---|---|---|
namespace_resources | GET | /namespace/resources(.:format) | namespace/resources#index |
| POST | /namespace/resources(.:format) | namespace/resources#create | |
new_namespace_resource | GET | /namespace/resources/new(.:format) | namespace/resources#new |
edit_namespace_resource | GET | /namespace/resources/:id/edit(.:format) | namespace/resources#edit |
namespace_resource | GET | /namespace/resources/:id(.:format) | namespace/resources#show |
| PATCH | /namespace/resources/:id(.:format) | namespace/resources#update | |
| PUT | /namespace/resources/:id(.:format) | namespace/resources#update | |
| DELETE | /namespace/resources/:id(.:format) | namespace/resources#destroy |
Behind the scenes
By doing namespace :namespace without any options, it uses the specified namespace as default value for all these 3 options: :module, :path and :as. In other words,
namespace :namespace do
resources :resources
end
is equivalent to
namespace :namespace, module: :namespace, path: :namespace, as: :namespace do
resources :resources
end
And since namespace passes these options to scope, these 2 configurations above are also equivalent to:
scope module: :namespace, path: :namespace, as: :namespace do
resources :resources
end
A bit more customization
These 3 options above give us flexibility to customize:
- enclosing module of controller (
:moduleoption); - URI prefix (
:pathoption); - named path helper prefix (
:asoption)
If we completely customize these options and do:
namespace :namespace, module: :controller_module, path: :uri_prefix, as: :path_helper_prefix do
resources :resources
end
# or, equivalently
scope module: :controller_module, path: :uri_prefix, as: :path_helper_prefix do
resources :resources
end
we get:
| Prefix | Verb | URI Pattern | Controller#Action |
|---|---|---|---|
path_helper_prefix_resources | GET | /uri_prefix/resources(.:format) | controller_module/resources#index |
| POST | /uri_prefix/resources(.:format) | controller_module/resources#create | |
new_path_helper_prefix_resource | GET | /uri_prefix/resources/new(.:format) | controller_module/resources#new |
edit_path_helper_prefix_resource | GET | /uri_prefix/resources/:id/edit(.:format) | controller_module/resources#edit |
path_helper_prefix_resource | GET | /uri_prefix/resources/:id(.:format) | controller_module/resources#show |
| PATCH | /uri_prefix/resources/:id(.:format) | controller_module/resources#update | |
| PUT | /uri_prefix/resources/:id(.:format) | controller_module/resources#update | |
| DELETE | /uri_prefix/resources/:id(.:format) | controller_module/resources#destroy |
What if I want nesting, but only partially?
One of the advantage of scope is that it is more flexible. For example, say I just want the URI prefix, but without changing the named helper or nesting the controller, then I can do something like:
scope path: :uri_prefix do
resources :resources
end
And I get:
| Prefix | Verb | URI Pattern | Controller#Action |
|---|---|---|---|
resources | GET | /uri_prefix/resources(.:format) | resources#index |
| POST | /uri_prefix/resources(.:format) | resources#create | |
new_resource | GET | /uri_prefix/resources/new(.:format) | resources#new |
edit_resource | GET | /uri_prefix/resources/:id/edit(.:format) | resources#edit |
resource | GET | /uri_prefix/resources/:id(.:format) | resources#show |
| PATCH | /uri_prefix/resources/:id(.:format) | resources#update | |
| PUT | /uri_prefix/resources/:id(.:format) | resources#update | |
| DELETE | /uri_prefix/resources/:id(.:format) | resources#destroy |
On the other hand, if I use namespace, I can’t do the same thing without affecting named path helper and nesting controller – unless explicitly pass nil to these options:
namespace :namespace, module: nil, path: :uri_prefix, as: nil do
resources :resources
end
# equivalent to
scope path: :uri_prefix do
resources :resources
end
Summary
If you just want a single “prefix” and apply it to URI, named path helper and controller’s enclosing module, then using namespace is the simplest way.
If you want more customization, or just want partial nesting, then scope provides the flexibility you need:
:moduleoption to set the module that encloses the controller;:pathoption to set URI prefix;:asoption to set prefix of named path helper
These options apply to both scope and namespace. The difference is the way they treat “default” value of absent options:
- For
namespace, it take the specified name (i.e. ‘my_namespace’ as innamespace :my_namespace) as implicit value of absent option and apply nesting; - For
scope, it simply ignore absent options and does not apply the respective nesting.