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

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)
RESOURCE_METHOD_SCOPES =
[:collection, :member, :new]
RESOURCE_SCOPES =
[:resource, :resources]

Instance Method Summary (collapse)

Instance Method Details

- (Object) add_route(action, options)

:nodoc:



1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1458

def add_route(action, options) # :nodoc:
  path = path_for_action(action, options.delete(:path))
  action = action.to_s.dup

  if action =~ /^[\w\-\/]+$/
    options[:action] ||= action.tr('-', '_') unless action.include?("/")
  else
    action = nil
  end

  if !options.fetch(:as, true)
    options.delete(:as)
  else
    options[:as] = name_for_action(options[:as], action)
  end

  mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
  app, conditions, requirements, defaults, as, anchor = mapping.to_route
  @set.add_route(app, conditions, requirements, defaults, as, anchor)
end

- (Object) collection

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.



1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1317

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

  with_scope_level(:collection) do
    scope(parent_resource.collection_scope) do
      yield
    end
  end
end

- (Object) decomposed_match(path, options)

:nodoc:



1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1443

def decomposed_match(path, options) # :nodoc:
  if on = options.delete(:on)
    send(on) { decomposed_match(path, options) }
  else
    case @scope[:scope_level]
    when :resources
      nested { decomposed_match(path, options) }
    when :resource
      member { decomposed_match(path, options) }
    else
      add_route(path, options)
    end
  end
end

- (Object) match(path, *rest)

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



1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1402

def match(path, *rest)
  if rest.empty? && Hash === path
    options  = path
    path, to = options.find { |name, _value| name.is_a?(String) }
    options[:to] = to
    options.delete(path)
    paths = [path]
  else
    options = rest.pop || {}
    paths = [path] + rest
  end

  options[:anchor] = true unless options.key?(:anchor)

  if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
    raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
  end

  if @scope[:controller] && @scope[:action]
    options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
  end

  paths.each do |_path|
    route_options = options.dup
    route_options[:path] ||= _path if _path.is_a?(String)

    path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
    if using_match_shorthand?(path_without_format, route_options)
      route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
      route_options[:to].tr!("-", "_")
    end

    decomposed_match(_path, route_options)
  end
  self
end

- (Object) member

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.



1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1340

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

  with_scope_level(:member) do
    if shallow?
      shallow_scope(parent_resource.member_scope) { yield }
    else
      scope(parent_resource.member_scope) { yield }
    end
  end
end

- (Object) namespace(path, options = {})

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



1381
1382
1383
1384
1385
1386
1387
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1381

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

- (Object) nested



1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1366

def nested
  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(parent_resource.nested_scope, nested_options) { yield }
    else
      scope(parent_resource.nested_scope, nested_options) { yield }
    end
  end
end

- (Object) new



1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1354

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

  with_scope_level(:new) do
    scope(parent_resource.new_scope(action_path(:new))) do
      yield
    end
  end
end

- (Object) resource(*resources, &block)

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

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
POST      /profile
GET       /profile
GET       /profile/edit
PATCH/PUT /profile
DELETE    /profile

Options

Takes same options as resources.



1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1120

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

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

  resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
    yield if block_given?

    concerns(options[:concerns]) if options[:concerns]

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

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

    set_member_mappings_for_resource
  end

  self
end

- (Object) resources(*resources, &block)

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 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.

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

Examples

# routes call <tt>Admin::PostsController</tt>
resources :posts, module: "admin"

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


1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1278

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

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

  resource_scope(:resources, Resource.new(resources.pop, 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

  self
end

- (Object) resources_path_names(options)



1095
1096
1097
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1095

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

- (Object) root(path, options = {})



1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1479

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

  if @scope[:scope_level] == :resources
    with_scope_level(:root) do
      scope(parent_resource.path) do
        super(options)
      end
    end
  else
    super(options)
  end
end

- (Object) shallow



1389
1390
1391
1392
1393
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1389

def shallow
  scope(:shallow => true) do
    yield
  end
end

- (Boolean) shallow?

Returns:

  • (Boolean)


1395
1396
1397
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1395

def shallow?
  parent_resource.instance_of?(Resource) && @scope[:shallow]
end

- (Boolean) using_match_shorthand?(path, options)

Returns:

  • (Boolean)


1439
1440
1441
# File 'actionpack/lib/action_dispatch/routing/mapper.rb', line 1439

def using_match_shorthand?(path, options)
  path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
end