Skip to Content Skip to Search

You may wish to organize groups of controllers under a namespace. Most commonly, you might group a number of administrative controllers under an admin namespace. You would place these controllers under the app/controllers/admin directory, and you can group them together in your router:

namespace "admin" do
  resources :posts, :comments
end

This will create a number of routes for each of the posts and comments controller. For Admin::PostsController, Rails will create:

GET       /admin/posts
GET       /admin/posts/new
POST      /admin/posts
GET       /admin/posts/1
GET       /admin/posts/1/edit
PATCH/PUT /admin/posts/1
DELETE    /admin/posts/1

If you want to route /posts (without the prefix /admin) to Admin::PostsController, you could use

scope module: "admin" do
  resources :posts
end

or, for a single case

resources :posts, module: "admin"

If you want to route /admin/posts to PostsController (without the Admin:: module prefix), you could use

scope "/admin" do
  resources :posts
end

or, for a single case

resources :posts, path: "/admin/posts"

In each of these cases, the named routes remain the same as if you did not use scope. In the last case, the following paths map to PostsController:

GET       /admin/posts
GET       /admin/posts/new
POST      /admin/posts
GET       /admin/posts/1
GET       /admin/posts/1/edit
PATCH/PUT /admin/posts/1
DELETE    /admin/posts/1
Methods
C
D
N
S

Instance Public methods

constraints(constraints = {}, &block)

Parameter Restriction ↑

Allows you to constrain the nested routes based on a set of rules. For instance, in order to change the routes to allow for a dot character in the id parameter:

constraints(id: /\d+\.\d+/) do
  resources :posts
end

Now routes such as /posts/1 will no longer be valid, but /posts/1.1 will be. The id parameter must match the constraint passed in for this example.

You may use this to also restrict other parameters:

resources :posts do
  constraints(post_id: /\d+\.\d+/) do
    resources :comments
  end
end

Restricting based on IP ↑

Routes can also be constrained to an IP or a certain range of IP addresses:

constraints(ip: /192\.168\.\d+\.\d+/) do
  resources :posts
end

Any user connecting from the 192.168.* range will be able to see this resource, where as any user connecting outside of this range will be told there is no such route.

Dynamic request matching ↑

Requests to routes can be constrained based on specific criteria:

constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
  resources :iphones
end

You are able to move this logic out into a class if it is too complex for routes. This class must have a matches? method defined on it which either returns true if the user should be given access to that route, or false if the user should not.

class Iphone
  def self.matches?(request)
    /iPhone/.match?(request.env["HTTP_USER_AGENT"])
  end
end

An expected place for this code would be lib/constraints.

This class is then used like this:

constraints(Iphone) do
  resources :iphones
end
# File actionpack/lib/action_dispatch/routing/mapper.rb, line 1191
def constraints(constraints = {}, &block)
  scope(constraints: constraints, &block)
end

controller(controller)

Scopes routes to a specific controller

controller "food" do
  match "bacon", action: :bacon, via: :get
end
# File actionpack/lib/action_dispatch/routing/mapper.rb, line 1067
def controller(controller)
  @scope = @scope.new(controller: controller)
  yield
ensure
  @scope = @scope.parent
end

defaults(defaults = {})

Allows you to set default parameters for a route, such as this:

defaults id: 'home' do
  match 'scoped_pages/(:id)', to: 'pages#show'
end

Using this, the :id parameter here will default to ‘home’.

# File actionpack/lib/action_dispatch/routing/mapper.rb, line 1202
def defaults(defaults = {})
  @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
  yield
ensure
  @scope = @scope.parent
end

namespace(name, deprecated_options = nil, as: DEFAULT, path: DEFAULT, shallow_path: DEFAULT, shallow_prefix: DEFAULT, **options, &block)

Scopes routes to a specific namespace. For example:

namespace :admin do
  resources :posts
end

This generates the following routes:

    admin_posts GET       /admin/posts(.:format)          admin/posts#index
    admin_posts POST      /admin/posts(.:format)          admin/posts#create
 new_admin_post GET       /admin/posts/new(.:format)      admin/posts#new
edit_admin_post GET       /admin/posts/:id/edit(.:format) admin/posts#edit
     admin_post GET       /admin/posts/:id(.:format)      admin/posts#show
     admin_post PATCH/PUT /admin/posts/:id(.:format)      admin/posts#update
     admin_post DELETE    /admin/posts/:id(.:format)      admin/posts#destroy

Options ↑

The :path, :as, :module, :shallow_path, and :shallow_prefix options all default to the name of the namespace.

For options, see Base#match. For :shallow_path option, see Resources#resources.

# accessible through /sekret/posts rather than /admin/posts
namespace :admin, path: "sekret" do
  resources :posts
end

# maps to Sekret::PostsController rather than Admin::PostsController
namespace :admin, module: "sekret" do
  resources :posts
end

# generates sekret_posts_path rather than admin_posts_path
namespace :admin, as: "sekret" do
  resources :posts
end
# File actionpack/lib/action_dispatch/routing/mapper.rb, line 1112
def namespace(name, deprecated_options = nil, as: DEFAULT, path: DEFAULT, shallow_path: DEFAULT, shallow_prefix: DEFAULT, **options, &block)
  if deprecated_options.is_a?(Hash)
    as = assign_deprecated_option(deprecated_options, :as, :namespace) if deprecated_options.key?(:as)
    path = assign_deprecated_option(deprecated_options, :path, :namespace) if deprecated_options.key?(:path)
    shallow_path = assign_deprecated_option(deprecated_options, :shallow_path, :namespace) if deprecated_options.key?(:shallow_path)
    shallow_prefix = assign_deprecated_option(deprecated_options, :shallow_prefix, :namespace) if deprecated_options.key?(:shallow_prefix)
    assign_deprecated_options(deprecated_options, options, :namespace)
  end

  name = name.to_s
  options[:module] ||= name
  as = name if as == DEFAULT
  path = name if path == DEFAULT
  shallow_path = path if shallow_path == DEFAULT
  shallow_prefix = as if shallow_prefix == DEFAULT

  path_scope(path) do
    scope(**options, as:, shallow_path:, shallow_prefix:, &block)
  end
end

scope(*args, only: nil, except: nil, **options)

Scopes a set of routes to the given default options.

Take the following route definition as an example:

scope path: ":account_id", as: "account" do
  resources :projects
end

This generates helpers such as account_projects_path, just like resources does. The difference here being that the routes generated are like /:account_id/projects, rather than /accounts/:account_id/projects.

Options ↑

Takes same options as Base#match and Resources#resources.

# route /posts (without the prefix /admin) to Admin::PostsController
scope module: "admin" do
  resources :posts
end

# prefix the posts resource's requests with '/admin'
scope path: "/admin" do
  resources :posts
end

# prefix the routing helper name: sekret_posts_path instead of posts_path
scope as: "sekret" do
  resources :posts
end
# File actionpack/lib/action_dispatch/routing/mapper.rb, line 1004
def scope(*args, only: nil, except: nil, **options)
  if args.grep(Hash).any? && (deprecated_options = args.extract_options!)
    only ||= assign_deprecated_option(deprecated_options, :only, :scope)
    except ||= assign_deprecated_option(deprecated_options, :except, :scope)
    assign_deprecated_options(deprecated_options, options, :scope)
  end

  scope = {}

  options[:path] = args.flatten.join("/") if args.any?
  options[:constraints] ||= {}

  unless nested_scope?
    options[:shallow_path] ||= options[:path] if options.key?(:path)
    options[:shallow_prefix] ||= options[:as] if options.key?(:as)
  end

  if options[:constraints].is_a?(Hash)
    defaults = options[:constraints].select do |k, v|
      URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
    end

    options[:defaults] = defaults.merge(options[:defaults] || {})
  else
    block, options[:constraints] = options[:constraints], {}
  end

  if only || except
    scope[:action_options] = { only:, except: }
  end

  if options.key? :anchor
    raise ArgumentError, "anchor is ignored unless passed to `match`"
  end

  @scope.options.each do |option|
    if option == :blocks
      value = block
    elsif option == :options
      value = options
    else
      value = options.delete(option) { POISON }
    end

    unless POISON == value
      scope[option] = send("merge_#{option}_scope", @scope[option], value)
    end
  end

  @scope = @scope.new scope
  yield
  self
ensure
  @scope = @scope.parent
end