[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 (
:module
option); - URI prefix (
:path
option); - named path helper prefix (
:as
option)
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:
:module
option to set the module that encloses the controller;:path
option to set URI prefix;:as
option 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.