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.



1558
1559
1560
1561
1562
1563
1564
1565
1566
# File 'lib/action_dispatch/routing/mapper.rb', line 1558

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.



1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
# File 'lib/action_dispatch/routing/mapper.rb', line 1667

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](Base#match).

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


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
1713
1714
1715
1716
1717
1718
1719
1720
# File 'lib/action_dispatch/routing/mapper.rb', line 1688

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.



1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
# File 'lib/action_dispatch/routing/mapper.rb', line 1579

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.



1626
1627
1628
1629
1630
1631
1632
# File 'lib/action_dispatch/routing/mapper.rb', line 1626

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

#nested(&block) ⇒ Object



1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
# File 'lib/action_dispatch/routing/mapper.rb', line 1605

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



1595
1596
1597
1598
1599
1600
1601
1602
1603
# File 'lib/action_dispatch/routing/mapper.rb', line 1595

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](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)



1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
# File 'lib/action_dispatch/routing/mapper.rb', line 1347

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 :resource, 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](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"


1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
# File 'lib/action_dispatch/routing/mapper.rb', line 1517

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



1314
1315
1316
# File 'lib/action_dispatch/routing/mapper.rb', line 1314

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.



1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
# File 'lib/action_dispatch/routing/mapper.rb', line 1735

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



1634
1635
1636
1637
1638
1639
# File 'lib/action_dispatch/routing/mapper.rb', line 1634

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

#shallow?Boolean

Returns:

  • (Boolean)


1641
1642
1643
# File 'lib/action_dispatch/routing/mapper.rb', line 1641

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