module ActionDispatch::Routing::Mapper::Resources
def action_options?(options) #:nodoc:
def action_options?(options) #:nodoc: options[:only] || options[:except] end
def action_path(name, path = nil) #:nodoc:
def action_path(name, path = nil) #:nodoc: name = name.to_sym if name.is_a?(String) path || @scope[:path_names][name] || name.to_s end
def add_route(action, options) # :nodoc:
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, URI.parser.escape(path), options) app, conditions, requirements, defaults, as, anchor = mapping.to_route @set.add_route(app, conditions, requirements, defaults, as, anchor) end
def apply_common_behavior_for(method, resources, options, &block) #:nodoc:
def apply_common_behavior_for(method, resources, options, &block) #:nodoc: if resources.length > 1 resources.each { |r| send(method, r, options, &block) } return true end if resource_scope? nested { send(method, resources.pop, options, &block) } return true end options.keys.each do |k| (options[:constraints] ||= {})[k] = options.delete(k) if options[k].is_a?(Regexp) end scope_options = options.slice!(*RESOURCE_OPTIONS) unless scope_options.empty? scope(scope_options) do send(method, resources.pop, options, &block) end return true end unless action_options?(options) options.merge!(scope_action_options) if scope_action_options? end false end
def canonical_action?(action, flag) #:nodoc:
def canonical_action?(action, flag) #:nodoc: flag && resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) end
def collection
create the search_photos_url and search_photos_path
with GET, and route to the search action of +PhotosController+. It will also
This will enable Rails to recognize paths such as /photos/search
end
end
get 'search'
collection do
resources :photos do
To add a route to the collection:
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
def decomposed_match(path, options) # :nodoc:
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
def match(path, *rest)
match 'path', to: 'controller#action'
match 'path' => 'controller#action'
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') end decomposed_match(_path, route_options) end self end
def member
preview action of +PhotosController+. It will also create the
This will recognize /photos/1/preview with GET, and route to the
end
end
get 'preview'
member do
resources :photos do
To add a member route, add a member block into the resource block:
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
def name_for_action(as, action) #:nodoc:
def name_for_action(as, action) #:nodoc: prefix = prefix_name_for_action(as, action) prefix = Mapper.normalize_name(prefix) if prefix name_prefix = @scope[:as] if parent_resource return nil unless as || action collection_name = parent_resource.collection_name member_name = parent_resource.member_name end name = case @scope[:scope_level] when :nested [name_prefix, prefix] when :collection [prefix, name_prefix, collection_name] when :new [prefix, :new, name_prefix, member_name] when :member [prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name] when :root [name_prefix, collection_name, prefix] else [name_prefix, member_name, prefix] end if candidate = name.select(&:present?).join("_").presence # If a name was not explicitly given, we check if it is valid # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. if as.nil? candidate unless @set.routes.find { |r| r.name == candidate } || candidate !~ /\A[_a-z]/i else candidate end end end
def namespace(path, options = {})
def namespace(path, options = {}) if resource_scope? nested { super } else super end end
def nested
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
def nested_options #:nodoc:
def nested_options #:nodoc: options = { :as => parent_resource.member_name } options[:constraints] = { parent_resource.nested_param => param_constraint } if param_constraint? options end
def new
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
def param_constraint #:nodoc:
def param_constraint #:nodoc: @scope[:constraints][parent_resource.param] end
def param_constraint? #:nodoc:
def param_constraint? #:nodoc: @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end
def parent_resource #:nodoc:
def parent_resource #:nodoc: @scope[:scope_level_resource] end
def path_for_action(action, path) #:nodoc:
def path_for_action(action, path) #:nodoc: prefix = shallow_scoping? ? "#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path] if canonical_action?(action, path.blank?) prefix.to_s else "#{prefix}/#{action_path(action, path)}" end end
def prefix_name_for_action(as, action) #:nodoc:
def prefix_name_for_action(as, action) #:nodoc: if as as.to_s elsif !canonical_action?(action, @scope[:scope_level]) action.to_s end end
def resource(*resources, &block)
=== Options
DELETE /geocoder
PATCH/PUT /geocoder
GET /geocoder/edit
GET /geocoder
POST /geocoder
GET /geocoder/new
the plural):
the +GeoCoders+ controller (note that the controller is named after
creates six different routes in your application, all mapping to
resource :geocoder
the show action:
a singular resource to map /profile (rather than /profile/:id) to
profile of the currently logged in user. In this case, you can use
referencing an ID. A common example, /profile always shows the
Sometimes, you have a resource that clients always look up without
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
def resource_method_scope? #:nodoc:
def resource_method_scope? #:nodoc: RESOURCE_METHOD_SCOPES.include? @scope[:scope_level] end
def resource_scope(kind, resource) #:nodoc:
def resource_scope(kind, resource) #:nodoc: with_scope_level(kind, resource) do scope(parent_resource.resource_scope) do yield end end end
def resource_scope? #:nodoc:
def resource_scope? #:nodoc: RESOURCE_SCOPES.include? @scope[:scope_level] end
def resources(*resources, &block)
# resource actions are at /admin/posts.
resources :posts, module: "admin"
# routes call Admin::PostsController
=== Examples
segment or disable it by supplying +false+.
Allows you to specify the default value for optional +format+
[:format]
sekret_comment DELETE /comments/:id(.:format)
sekret_comment PATCH/PUT /comments/:id(.:format)
sekret_comment GET /comments/:id(.:format)
edit_sekret_comment GET /comments/:id/edit(.:format)
new_post_comment GET /posts/:post_id/comments/new(.:format)
post_comments POST /posts/:post_id/comments(.:format)
post_comments GET /posts/:post_id/comments(.:format)
The +comments+ resource here will have the following routes generated for it:
end
end
resources :comments, shallow: true
resources :posts do
scope shallow_prefix: "sekret" do
Prefixes nested shallow route names with specified prefix.
[:shallow_prefix]
comment DELETE /sekret/comments/:id(.:format)
comment PATCH/PUT /sekret/comments/:id(.:format)
comment GET /sekret/comments/:id(.:format)
edit_comment GET /sekret/comments/:id/edit(.:format)
new_post_comment GET /posts/:post_id/comments/new(.:format)
post_comments POST /posts/:post_id/comments(.:format)
post_comments GET /posts/:post_id/comments(.:format)
The +comments+ resource here will have the following routes generated for it:
end
end
resources :comments, shallow: true
resources :posts do
scope shallow_path: "sekret" do
Prefixes nested shallow routes with the specified path.
[:shallow_path]
to be shortened to just /comments/1234.
as a comment on a blog post like /posts/a-long-permalink/comments/1234
This allows URLs for resources that otherwise would be deeply nested such
resources :comments, only: [:show, :edit, :update, :destroy]
end
resources :comments, except: [:show, :edit, :update, :destroy]
resources :posts do
Is the same as:
end
resources :comments
resources :posts, shallow: true do
generates shallow routes for all nested resources.
Generates shallow routes for nested resource(s). When placed on a parent resource,
[:shallow]
resources :cows, except: [:show, :index]
resources :cows, except: :show
Generate all routes except for the given actions.
[:except]
resources :cows, only: [:show, :index]
resources :cows, only: :show
Only generate routes for the given actions.
[:only]
The resource and all segments will now route to /postings instead of /posts
resources :posts, path: 'postings'
Allows you to change the path prefix for the resource.
[:path]
The above example will now change /posts/new to /posts/brand_new
resources :posts, path_names: { new: "brand_new" }
Actions not specified are not changed.
Allows you to change the segment component of the +edit+ and +new+ actions.
[:path_names]
Takes same options as Base#match as well as:
=== Options
DELETE /photos/:photo_id/comments/:id
PATCH/PUT /photos/:photo_id/comments/:id
GET /photos/:photo_id/comments/:id/edit
GET /photos/:photo_id/comments/:id
POST /photos/:photo_id/comments
GET /photos/:photo_id/comments/new
GET /photos/:photo_id/comments
This generates the following comments routes:
end
resources :comments
resources :photos do
Resources can also be nested infinitely by using this block syntax:
DELETE /photos/:id
PATCH/PUT /photos/:id
GET /photos/:id/edit
GET /photos/:id
POST /photos
GET /photos/new
GET /photos
the +Photos+ controller:
creates seven different routes in your application, all mapping to
resources :photos
routing file, such as
to particular CRUD operations in a database. A single entry in the
and URLs and controller actions. By convention, each action also maps
In Rails, a resourceful route provides a mapping between HTTP verbs
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
def resources_path_names(options)
def resources_path_names(options) @scope[:path_names].merge!(options) end
def root(path, options={})
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
def scope_action_options #:nodoc:
def scope_action_options #:nodoc: @scope[:options].slice(:only, :except) end
def scope_action_options? #:nodoc:
def scope_action_options? #:nodoc: @scope[:options] && (@scope[:options][:only] || @scope[:options][:except]) end
def set_member_mappings_for_resource
def set_member_mappings_for_resource member do get :edit if parent_resource.actions.include?(:edit) get :show if parent_resource.actions.include?(:show) if parent_resource.actions.include?(:update) patch :update put :update end delete :destroy if parent_resource.actions.include?(:destroy) end end
def shallow
def shallow scope(:shallow => true, :shallow_path => @scope[:path]) do yield end end
def shallow?
def shallow? parent_resource.instance_of?(Resource) && @scope[:shallow] end
def shallow_scoping? #:nodoc:
def shallow_scoping? #:nodoc: shallow? && @scope[:scope_level] == :member end
def using_match_shorthand?(path, options)
def using_match_shorthand?(path, options) path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$} end
def with_exclusive_scope
def with_exclusive_scope begin old_name_prefix, old_path = @scope[:as], @scope[:path] @scope[:as], @scope[:path] = nil, nil with_scope_level(:exclusive) do yield end ensure @scope[:as], @scope[:path] = old_name_prefix, old_path end end
def with_scope_level(kind, resource = parent_resource)
def with_scope_level(kind, resource = parent_resource) old, @scope[:scope_level] = @scope[:scope_level], kind old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource yield ensure @scope[:scope_level] = old @scope[:scope_level_resource] = old_resource end