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

Included in:
ActionDispatch::Routing::Mapper
Defined in:
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.



1548
1549
1550
1551
1552
1553
1554
1555
1556
# File 'lib/action_dispatch/routing/mapper.rb', line 1548

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.



1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
# File 'lib/action_dispatch/routing/mapper.rb', line 1658

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, *rest, &block) ⇒ Object

Matches a URL pattern to one or more routes. For more information, see match.

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


1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
# File 'lib/action_dispatch/routing/mapper.rb', line 1680

def match(path, *rest, &block)
  if rest.empty? && Hash === path
    options  = path
    path, to = options.find { |name, _value| name.is_a?(String) }

    raise ArgumentError, "Route path not specified" if path.nil?

    case to
    when Symbol
      options[:action] = to
    when String
      if to.include?("#")
        options[:to] = to
      else
        options[:controller] = to
      end
    else
      options[:to] = to
    end

    options.delete(path)
    paths = [path]
  else
    options = rest.pop || {}
    paths = [path] + rest
  end

  if options.key?(:defaults)
    defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
  else
    map_match(paths, options, &block)
  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.



1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
# File 'lib/action_dispatch/routing/mapper.rb', line 1569

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(path, options = {}) ⇒ Object

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



1616
1617
1618
1619
1620
1621
1622
# File 'lib/action_dispatch/routing/mapper.rb', line 1616

def namespace(path, options = {})
  if resource_scope?
    nested { super }
  else
    super
  end
end

#nested(&block) ⇒ Object



1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
# File 'lib/action_dispatch/routing/mapper.rb', line 1595

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



1585
1586
1587
1588
1589
1590
1591
1592
1593
# File 'lib/action_dispatch/routing/mapper.rb', line 1585

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, &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:

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

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

Options

Takes same options as resources



1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
# File 'lib/action_dispatch/routing/mapper.rb', line 1340

def resource(*resources, &block)
  options = resources.extract_options!.dup

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

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

      concerns(options[:concerns]) if options[: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, &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 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"


1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
# File 'lib/action_dispatch/routing/mapper.rb', line 1506

def resources(*resources, &block)
  options = resources.extract_options!.dup

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

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

      concerns(options[:concerns]) if options[: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



1305
1306
1307
# File 'lib/action_dispatch/routing/mapper.rb', line 1305

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.



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

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



1624
1625
1626
1627
1628
1629
# File 'lib/action_dispatch/routing/mapper.rb', line 1624

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

#shallow?Boolean

Returns:

  • (Boolean)


1631
1632
1633
# File 'lib/action_dispatch/routing/mapper.rb', line 1631

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