Module: FakeRails3Routes::Mapper::Resources

Included in:
FakeRails3Routes::Mapper
Defined in:
lib/fake_rails3_routes/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, :format]
CANONICAL_ACTIONS =
%w(index create new show update destroy)

Instance Method Summary collapse

Instance Method Details

#add_route(action, options) ⇒ Object

:nodoc:



1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
# File 'lib/fake_rails3_routes/mapper.rb', line 1210

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 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, path, options)
  #puts mapping.to_route.inspect
  conditions, requirements, defaults, as, anchor = mapping.to_route
  defaults[:conditions] ||= {}
  if conditions[:request_method]
    via = conditions.delete(:request_method).map { |v| v.downcase.to_sym }
    via = via.first if via.size == 1
    defaults[:conditions][:method] = via
  end
  @set.add_route(conditions, requirements, defaults, as, anchor)
end

#collectionObject

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.



1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
# File 'lib/fake_rails3_routes/mapper.rb', line 1057

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

#concern(name, &block) ⇒ Object



1176
1177
1178
1179
# File 'lib/fake_rails3_routes/mapper.rb', line 1176

def concern(name, &block)
  @concerns ||= {}
  @concerns[name] = block
end

#concerns(*names) ⇒ Object



1181
1182
1183
1184
1185
1186
1187
1188
1189
# File 'lib/fake_rails3_routes/mapper.rb', line 1181

def concerns(*names)
  Array(names).flatten.compact.each do |name|
    if @concerns && concern = @concerns[name]
      instance_eval(&concern)
    else
      raise "No concern named #{name} was found!"
    end
  end
end

#decomposed_match(path, options) ⇒ Object

:nodoc:



1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
# File 'lib/fake_rails3_routes/mapper.rb', line 1195

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

#match(path, *rest) ⇒ Object



1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
# File 'lib/fake_rails3_routes/mapper.rb', line 1145

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

  path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
  if using_match_shorthand?(path_without_format, options)
    options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
  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 !options.key?(:format)
    options[:format] = false
  end

  paths.each { |_path| decomposed_match(_path, options.dup) }
  self
end

#memberObject

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.



1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
# File 'lib/fake_rails3_routes/mapper.rb', line 1080

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

  with_scope_level(:member) do
    scope(parent_resource.member_scope) do
      yield
    end
  end
end

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

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



1127
1128
1129
1130
1131
1132
1133
# File 'lib/fake_rails3_routes/mapper.rb', line 1127

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

#nestedObject



1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
# File 'lib/fake_rails3_routes/mapper.rb', line 1104

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

  with_scope_level(:nested) do
    if shallow?
      with_exclusive_scope do
        if @scope[:shallow_path].blank?
          scope(parent_resource.nested_scope, nested_options) { yield }
        else
          scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
            scope(parent_resource.nested_scope, nested_options) { yield }
          end
        end
      end
    else
      scope(parent_resource.nested_scope, nested_options) { yield }
    end
  end
end

#newObject



1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
# File 'lib/fake_rails3_routes/mapper.rb', line 1092

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

#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 :geocoder

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

GET     /geocoder/new
POST    /geocoder
GET     /geocoder
GET     /geocoder/edit
PUT     /geocoder
DELETE  /geocoder

Options

Takes same options as resources.



867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
# File 'lib/fake_rails3_routes/mapper.rb', line 867

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
    named_concerns = options.delete(:concerns)
    concerns(named_concerns)
    yield if block_given?

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

    member do
      get    :edit if parent_resource.actions.include?(:edit)
      get    :show if parent_resource.actions.include?(:show)
      put    :update if parent_resource.actions.include?(:update)
      delete :destroy if parent_resource.actions.include?(:destroy)
    end

    collection do
      post :create
    end if parent_resource.actions.include?(:create)
  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
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
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          PUT    /sekret/comments/:id(.:format)
comment          DELETE /sekret/comments/:id(.:format)

Examples

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

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


1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
# File 'lib/fake_rails3_routes/mapper.rb', line 1007

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
    named_concerns = options.delete(:concerns)
    concerns(named_concerns)
    yield if block_given?

    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)

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

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

    member do
      put    :update if parent_resource.actions.include?(:update)
      delete :destroy if parent_resource.actions.include?(:destroy)
    end
  end

  self
end

#resources_path_names(options) ⇒ Object



842
843
844
# File 'lib/fake_rails3_routes/mapper.rb', line 842

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

#root(options = {}) ⇒ Object



1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
# File 'lib/fake_rails3_routes/mapper.rb', line 1239

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

#shallowObject



1135
1136
1137
1138
1139
# File 'lib/fake_rails3_routes/mapper.rb', line 1135

def shallow
  scope(:shallow => true, :shallow_path => @scope[:path]) do
    yield
  end
end

#shallow?Boolean



1141
1142
1143
# File 'lib/fake_rails3_routes/mapper.rb', line 1141

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

#using_match_shorthand?(path, options) ⇒ Boolean



1191
1192
1193
# File 'lib/fake_rails3_routes/mapper.rb', line 1191

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