[Rails Notes] Nested routes – scope vs namespace

[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:

PrefixVerbURI PatternController#Action
resourcesGET/resources(.:format)resources#index
POST/resources(.:format)resources#create
new_resource GET/resources/new(.:format)resources#new
edit_resourceGET/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
PrefixVerbURI PatternController#Action
namespace_resourcesGET/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_resourceGET/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:

PrefixVerbURI PatternController#Action
path_helper_prefix_resourcesGET/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_resourceGET/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:

PrefixVerbURI PatternController#Action
resourcesGET/uri_prefix/resources(.:format)resources#index
POST/uri_prefix/resources(.:format)resources#create
new_resource GET/uri_prefix/resources/new(.:format)resources#new
edit_resourceGET/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 in namespace :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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.