Module: ActionDispatch::Routing::Mapper::Resources

Included in:
ActionDispatch::Routing::Mapper
Defined in:
actionpack/lib/action_dispatch/routing/mapper.rb

Overview

Resource routing allows you to quickly declare all of the common routes for a given resourceful controller. Instead of declaring separate routes for your ‘index`, `show`, `new`, `edit`, `create`, `update`, and `destroy` actions, a resourceful route declares them in a single line of code:

resources :photos

Sometimes, you have a resource that clients always look up without referencing an ID. A common example, /profile always shows the profile of the currently logged in user. In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action.

resource :profile

It’s common to have resources that are logically children of other resources:

resources :magazines do
  resources :ads
end

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

By default the ‘:id` parameter doesn’t accept dots. If you need to use dots as part of the ‘:id` parameter add a constraint which overrides this restriction, e.g:

resources :articles, id: /[^\/]+/

This allows any character other than a slash as part of your ‘:id`.

Defined Under Namespace

Classes: Resource, SingletonResource

Constant Summary collapse

VALID_ON_OPTIONS =

CANONICAL_ACTIONS holds all actions that does not need a prefix or a path appended since they fit properly in their scope level.

[:new, :collection, :member]
RESOURCE_OPTIONS =
[:as, :controller, :path, :only, :except, :param, :concerns]
CANONICAL_ACTIONS =
%w(index create new show update destroy)

Instance Method Summary collapse

Instance Method Details

#collection(&block) ⇒ Object

To add a route to the collection:

resources :photos do
  collection do
    get 'search'
  end
end

This will enable Rails to recognize paths such as ‘/photos/search` with GET, and route to the search action of `PhotosController`. It will also create the `search_photos_url` and `search_photos_path` route helpers.



1707
1708
1709
1710
1711
1712
1713
1714
1715
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1707

def collection(&block)
  unless resource_scope?
    raise ArgumentError, "can't use collection outside resource(s) scope"
  end

  with_scope_level(:collection) do
    path_scope(parent_resource.collection_scope, &block)
  end
end

#draw(name) ⇒ Object

Loads another routes file with the given ‘name` located inside the `config/routes` directory. In that file, you can use the normal routing DSL, but *do not* surround it with a `Rails.application.routes.draw` block.

# config/routes.rb
Rails.application.routes.draw do
  draw :admin                 # Loads `config/routes/admin.rb`
  draw "third_party/some_gem" # Loads `config/routes/third_party/some_gem.rb`
end

# config/routes/admin.rb
namespace :admin do
  resources :accounts
end

# config/routes/third_party/some_gem.rb
mount SomeGem::Engine, at: "/some_gem"

CAUTION: Use this feature with care. Having multiple routes files can negatively impact discoverability and readability. For most applications —even those with a few hundred routes — it’s easier for developers to have a single routes file.



1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1816

def draw(name)
  path = @draw_paths.find do |_path|
    File.exist? "#{_path}/#{name}.rb"
  end

  unless path
    msg  = "Your router tried to #draw the external file #{name}.rb,\n" \
           "but the file was not found in:\n\n"
    msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
    raise ArgumentError, msg
  end

  route_path = "#{path}/#{name}.rb"
  instance_eval(File.read(route_path), route_path.to_s)
end

#match(*path_or_actions, as: DEFAULT, via: nil, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block) ⇒ Object

Matches a URL pattern to one or more routes. For more information, see [match](Base#match).

match 'path', to: 'controller#action', via: :post
match 'path', 'otherpath', on: :member, via: :get


1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1837

def match(*path_or_actions, as: DEFAULT, via: nil, to: nil, controller: nil, action: nil, on: nil, defaults: nil, constraints: nil, anchor: nil, format: nil, path: nil, internal: nil, **mapping, &block)
  if path_or_actions.grep(Hash).any? && (deprecated_options = path_or_actions.extract_options!)
    as = assign_deprecated_option(deprecated_options, :as, :match) if deprecated_options.key?(:as)
    via ||= assign_deprecated_option(deprecated_options, :via, :match)
    to ||= assign_deprecated_option(deprecated_options, :to, :match)
    controller ||= assign_deprecated_option(deprecated_options, :controller, :match)
    action ||= assign_deprecated_option(deprecated_options, :action, :match)
    on ||= assign_deprecated_option(deprecated_options, :on, :match)
    defaults ||= assign_deprecated_option(deprecated_options, :defaults, :match)
    constraints ||= assign_deprecated_option(deprecated_options, :constraints, :match)
    anchor = assign_deprecated_option(deprecated_options, :anchor, :match) if deprecated_options.key?(:anchor)
    format = assign_deprecated_option(deprecated_options, :format, :match) if deprecated_options.key?(:format)
    path ||= assign_deprecated_option(deprecated_options, :path, :match)
    internal ||= assign_deprecated_option(deprecated_options, :internal, :match)
    assign_deprecated_options(deprecated_options, mapping, :match)
  end

  ActionDispatch.deprecator.warn(<<-MSG.squish) if path_or_actions.count > 1
    Mapping a route with multiple paths is deprecated and
    will be removed in Rails 8.1. Please use multiple method calls instead.
  MSG

  if path_or_actions.none? && mapping.any?
    hash_path, hash_to = mapping.find { |key, _| key.is_a?(String) }
    if hash_path.nil?
      raise ArgumentError, "Route path not specified"
    else
      mapping.delete(hash_path)
    end

    if hash_path
      path_or_actions.push hash_path
      case hash_to
      when Symbol
        action ||= hash_to
      when String
        if hash_to.include?("#")
          to ||= hash_to
        else
          controller ||= hash_to
        end
      else
        to ||= hash_to
      end
    end
  end

  path_or_actions.each do |path_or_action|
    if defaults
      defaults(defaults) { map_match(path_or_action, as:, via:, to:, controller:, action:, on:, constraints:, anchor:, format:, path:, internal:, mapping:, &block) }
    else
      map_match(path_or_action, as:, via:, to:, controller:, action:, on:, constraints:, anchor:, format:, path:, internal:, mapping:, &block)
    end
  end
end

#member(&block) ⇒ Object

To add a member route, add a member block into the resource block:

resources :photos do
  member do
    get 'preview'
  end
end

This will recognize ‘/photos/1/preview` with GET, and route to the preview action of `PhotosController`. It will also create the `preview_photo_url` and `preview_photo_path` helpers.



1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1728

def member(&block)
  unless resource_scope?
    raise ArgumentError, "can't use member outside resource(s) scope"
  end

  with_scope_level(:member) do
    if shallow?
      shallow_scope {
        path_scope(parent_resource.member_scope, &block)
      }
    else
      path_scope(parent_resource.member_scope, &block)
    end
  end
end

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

See ActionDispatch::Routing::Mapper::Scoping#namespace.



1775
1776
1777
1778
1779
1780
1781
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1775

def namespace(name, deprecated_options = nil, as: DEFAULT, path: DEFAULT, shallow_path: DEFAULT, shallow_prefix: DEFAULT, **options, &block)
  if resource_scope?
    nested { super }
  else
    super
  end
end

#nested(&block) ⇒ Object



1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1754

def nested(&block)
  unless resource_scope?
    raise ArgumentError, "can't use nested outside resource(s) scope"
  end

  with_scope_level(:nested) do
    if shallow? && shallow_nesting_depth >= 1
      shallow_scope do
        path_scope(parent_resource.nested_scope) do
          scope(**nested_options, &block)
        end
      end
    else
      path_scope(parent_resource.nested_scope) do
        scope(**nested_options, &block)
      end
    end
  end
end

#new(&block) ⇒ Object



1744
1745
1746
1747
1748
1749
1750
1751
1752
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1744

def new(&block)
  unless resource_scope?
    raise ArgumentError, "can't use new outside resource(s) scope"
  end

  with_scope_level(:new) do
    path_scope(parent_resource.new_scope(action_path(:new)), &block)
  end
end

#resource(*resources, concerns: nil, **options, &block) ⇒ Object

Sometimes, you have a resource that clients always look up without referencing an ID. A common example, /profile always shows the profile of the currently logged in user. In this case, you can use a singular resource to map /profile (rather than /profile/:id) to the show action:

resource :profile

This creates six different routes in your application, all mapping to the ‘Profiles` controller (note that the controller is named after the plural):

GET       /profile/new
GET       /profile
GET       /profile/edit
PATCH/PUT /profile
DELETE    /profile
POST      /profile

If you want instances of a model to work with this resource via record identification (e.g. in ‘form_with` or `redirect_to`), you will need to call [resolve](CustomUrls#resolve):

resource :profile
resolve('Profile') { [:profile] }

# Enables this to work with singular routes:
form_with(model: @profile) {}

### Options Takes same options as [resources](#resources)



1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1490

def resource(*resources, concerns: nil, **options, &block)
  if resources.grep(Hash).any? && (deprecated_options = resources.extract_options!)
    concerns = assign_deprecated_option(deprecated_options, :concerns, :resource) if deprecated_options.key?(:concerns)
    assign_deprecated_options(deprecated_options, options, :resource)
  end

  if apply_common_behavior_for(:resource, resources, concerns:, **options, &block)
    return self
  end

  with_scope_level(:resource) do
    options = apply_action_options :resource, options
    resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], **options)) do
      yield if block_given?

      concerns(*concerns) if concerns

      new do
        get :new
      end if parent_resource.actions.include?(:new)

      set_member_mappings_for_resource

      collection do
        post :create
      end if parent_resource.actions.include?(:create)
    end
  end

  self
end

#resources(*resources, concerns: nil, **options, &block) ⇒ Object

In Rails, a resourceful route provides a mapping between HTTP verbs and URLs and controller actions. By convention, each action also maps to particular CRUD operations in a database. A single entry in the routing file, such as

resources :photos

creates seven different routes in your application, all mapping to the ‘Photos` controller:

GET       /photos
GET       /photos/new
POST      /photos
GET       /photos/:id
GET       /photos/:id/edit
PATCH/PUT /photos/:id
DELETE    /photos/:id

Resources can also be nested infinitely by using this block syntax:

resources :photos do
  resources :comments
end

This generates the following comments routes:

GET       /photos/:photo_id/comments
GET       /photos/:photo_id/comments/new
POST      /photos/:photo_id/comments
GET       /photos/:photo_id/comments/:id
GET       /photos/:photo_id/comments/:id/edit
PATCH/PUT /photos/:photo_id/comments/:id
DELETE    /photos/:photo_id/comments/:id

### Options Takes same options as [match](Base#match) as well as:

:path_names : Allows you to change the segment component of the ‘edit` and `new`

actions. Actions not specified are not changed.

    resources :posts, path_names: { new: "brand_new" }

The above example will now change /posts/new to /posts/brand_new.

:path : Allows you to change the path prefix for the resource.

    resources :posts, path: 'postings'

The resource and all segments will now route to /postings instead of
/posts.

:only : Only generate routes for the given actions.

resources :cows, only: :show
resources :cows, only: [:show, :index]

:except : Generate all routes except for the given actions.

resources :cows, except: :show
resources :cows, except: [:show, :index]

:shallow : Generates shallow routes for nested resource(s). When placed on a parent

resource, generates shallow routes for all nested resources.

    resources :posts, shallow: true do
      resources :comments
    end

Is the same as:

    resources :posts do
      resources :comments, except: [:show, :edit, :update, :destroy]
    end
    resources :comments, only: [:show, :edit, :update, :destroy]

This allows URLs for resources that otherwise would be deeply nested such
as a comment on a blog post like `/posts/a-long-permalink/comments/1234`
to be shortened to just `/comments/1234`.

Set `shallow: false` on a child resource to ignore a parent's shallow
parameter.

:shallow_path : Prefixes nested shallow routes with the specified path.

    scope shallow_path: "sekret" do
      resources :posts do
        resources :comments, shallow: true
      end
    end

The `comments` resource here will have the following routes generated for
it:

    post_comments    GET       /posts/:post_id/comments(.:format)
    post_comments    POST      /posts/:post_id/comments(.:format)
    new_post_comment GET       /posts/:post_id/comments/new(.:format)
    edit_comment     GET       /sekret/comments/:id/edit(.:format)
    comment          GET       /sekret/comments/:id(.:format)
    comment          PATCH/PUT /sekret/comments/:id(.:format)
    comment          DELETE    /sekret/comments/:id(.:format)

:shallow_prefix : Prefixes nested shallow route names with specified prefix.

    scope shallow_prefix: "sekret" do
      resources :posts do
        resources :comments, shallow: true
      end
    end

The `comments` resource here will have the following routes generated for
it:

    post_comments           GET       /posts/:post_id/comments(.:format)
    post_comments           POST      /posts/:post_id/comments(.:format)
    new_post_comment        GET       /posts/:post_id/comments/new(.:format)
    edit_sekret_comment     GET       /comments/:id/edit(.:format)
    sekret_comment          GET       /comments/:id(.:format)
    sekret_comment          PATCH/PUT /comments/:id(.:format)
    sekret_comment          DELETE    /comments/:id(.:format)

:format : Allows you to specify the default value for optional ‘format` segment or

disable it by supplying `false`.

:param : Allows you to override the default param name of ‘:id` in the URL.

### Examples

# routes call Admin::PostsController
resources :posts, module: "admin"

# resource actions are at /admin/posts.
resources :posts, path: "admin/posts"


1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1663

def resources(*resources, concerns: nil, **options, &block)
  if resources.grep(Hash).any? && (deprecated_options = resources.extract_options!)
    concerns = assign_deprecated_option(deprecated_options, :concerns, :resources) if deprecated_options.key?(:concerns)
    assign_deprecated_options(deprecated_options, options, :resources)
  end

  if apply_common_behavior_for(:resources, resources, concerns:, **options, &block)
    return self
  end

  with_scope_level(:resources) do
    options = apply_action_options :resources, options
    resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], **options)) do
      yield if block_given?

      concerns(*concerns) if concerns

      collection do
        get  :index if parent_resource.actions.include?(:index)
        post :create if parent_resource.actions.include?(:create)
      end

      new do
        get :new
      end if parent_resource.actions.include?(:new)

      set_member_mappings_for_resource
    end
  end

  self
end

#resources_path_names(options) ⇒ Object



1457
1458
1459
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1457

def resources_path_names(options)
  @scope[:path_names].merge!(options)
end

#root(path, options = {}) ⇒ Object

You can specify what Rails should route “/” to with the root method:

root to: 'pages#main'

For options, see ‘match`, as `root` uses it internally.

You can also pass a string which will expand

root 'pages#main'

You should put the root route at the top of ‘config/routes.rb`, because this means it will be matched first. As this is the most popular route of most Rails applications, this is beneficial.



1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1906

def root(path, options = {})
  if path.is_a?(String)
    options[:to] = path
  elsif path.is_a?(Hash) && options.empty?
    options = path
  else
    raise ArgumentError, "must be called with a path and/or options"
  end

  if @scope.resources?
    with_scope_level(:root) do
      path_scope(parent_resource.path) do
        match_root_route(options)
      end
    end
  else
    match_root_route(options)
  end
end

#shallowObject



1783
1784
1785
1786
1787
1788
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1783

def shallow
  @scope = @scope.new(shallow: true)
  yield
ensure
  @scope = @scope.parent
end

#shallow?Boolean

Returns:

  • (Boolean)


1790
1791
1792
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1790

def shallow?
  !parent_resource.singleton? && @scope[:shallow]
end