module ActionDispatch::Routing::Mapper::Scoping
def constraints(constraints = {}, &block)
resources :iphones
constraints(Iphone) do
This class is then used like this:
An expected place for this code would be +lib/constraints+.
end
end
/iPhone/.match?(request.env["HTTP_USER_AGENT"])
def self.matches?(request)
class Iphone
if the user should be given access to that route, or +false+ if the user should not.
This class must have a +matches?+ method defined on it which either returns +true+
You are able to move this logic out into a class if it is too complex for routes.
end
resources :iphones
constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
Requests to routes can be constrained based on specific criteria:
=== Dynamic request matching
where as any user connecting outside of this range will be told there is no such route.
Any user connecting from the 192.168.* range will be able to see this resource,
end
resources :posts
constraints(ip: /192\.168\.\d+\.\d+/) do
Routes can also be constrained to an IP or a certain range of IP addresses:
=== Restricting based on IP
end
end
resources :comments
constraints(post_id: /\d+\.\d+/) do
resources :posts do
You may use this to also restrict other parameters:
The +id+ parameter must match the constraint passed in for this example.
Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be.
end
resources :posts
constraints(id: /\d+\.\d+/) do
For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
Allows you to constrain the nested routes based on a set of rules.
=== Parameter Restriction
def constraints(constraints = {}, &block) scope(constraints: constraints, &block) end
def controller(controller)
match "bacon", action: :bacon, via: :get
controller "food" do
Scopes routes to a specific controller
def controller(controller) @scope = @scope.new(controller: controller) yield ensure @scope = @scope.parent end
def defaults(defaults = {})
end
match 'scoped_pages/(:id)', to: 'pages#show'
defaults id: 'home' do
Allows you to set default parameters for a route, such as this:
def defaults(defaults = {}) @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults)) yield ensure @scope = @scope.parent end
def merge_action_scope(parent, child)
def merge_action_scope(parent, child) child end
def merge_as_scope(parent, child)
def merge_as_scope(parent, child) parent ? "#{parent}_#{child}" : child end
def merge_blocks_scope(parent, child)
def merge_blocks_scope(parent, child) merged = parent ? parent.dup : [] merged << child if child merged end
def merge_constraints_scope(parent, child)
def merge_constraints_scope(parent, child) merge_options_scope(parent, child) end
def merge_controller_scope(parent, child)
def merge_controller_scope(parent, child) child end
def merge_defaults_scope(parent, child)
def merge_defaults_scope(parent, child) merge_options_scope(parent, child) end
def merge_format_scope(parent, child)
def merge_format_scope(parent, child) child end
def merge_module_scope(parent, child)
def merge_module_scope(parent, child) parent ? "#{parent}/#{child}" : child end
def merge_options_scope(parent, child)
def merge_options_scope(parent, child) (parent || {}).merge(child) end
def merge_path_names_scope(parent, child)
def merge_path_names_scope(parent, child) merge_options_scope(parent, child) end
def merge_path_scope(parent, child)
def merge_path_scope(parent, child) Mapper.normalize_path("#{parent}/#{child}") end
def merge_shallow_path_scope(parent, child)
def merge_shallow_path_scope(parent, child) Mapper.normalize_path("#{parent}/#{child}") end
def merge_shallow_prefix_scope(parent, child)
def merge_shallow_prefix_scope(parent, child) parent ? "#{parent}_#{child}" : child end
def merge_shallow_scope(parent, child)
def merge_shallow_scope(parent, child) child ? true : false end
def merge_to_scope(parent, child)
def merge_to_scope(parent, child) child end
def merge_via_scope(parent, child)
def merge_via_scope(parent, child) child end
def namespace(path, options = {}, &block)
resources :posts
namespace :admin, as: "sekret" do
# generates +sekret_posts_path+ rather than +admin_posts_path+
end
resources :posts
namespace :admin, module: "sekret" do
# maps to Sekret::PostsController rather than Admin::PostsController
end
resources :posts
namespace :admin, path: "sekret" do
# accessible through /sekret/posts rather than /admin/posts
Resources#resources.
For options, see Base#match. For +:shallow_path+ option, see
options all default to the name of the namespace.
The +:path+, +:as+, +:module+, +:shallow_path+, and +:shallow_prefix+
=== Options
admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
admin_post GET /admin/posts/:id(.:format) admin/posts#show
edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
new_admin_post GET /admin/posts/new(.:format) admin/posts#new
admin_posts POST /admin/posts(.:format) admin/posts#create
admin_posts GET /admin/posts(.:format) admin/posts#index
This generates the following routes:
end
resources :posts
namespace :admin do
Scopes routes to a specific namespace. For example:
def namespace(path, options = {}, &block) path = path.to_s defaults = { module: path, as: options.fetch(:as, path), shallow_path: options.fetch(:path, path), shallow_prefix: options.fetch(:as, path) } path_scope(options.delete(:path) { path }) do scope(defaults.merge!(options), &block) end end
def scope(*args)
resources :posts
scope as: "sekret" do
# prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
end
resources :posts
scope path: "/admin" do
# prefix the posts resource's requests with '/admin'
end
resources :posts
scope module: "admin" do
# route /posts (without the prefix /admin) to Admin::PostsController
Takes same options as Base#match and Resources#resources.
=== Options
rather than /accounts/:account_id/projects.
The difference here being that the routes generated are like /:account_id/projects,
This generates helpers such as +account_projects_path+, just like +resources+ does.
end
resources :projects
scope path: ":account_id", as: "account" do
Take the following route definition as an example:
Scopes a set of routes to the given default options.
def scope(*args) options = args.extract_options!.dup scope = {} options[:path] = args.flatten.join("/") if args.any? options[:constraints] ||= {} unless nested_scope? options[:shallow_path] ||= options[:path] if options.key?(:path) options[:shallow_prefix] ||= options[:as] if options.key?(:as) end if options[:constraints].is_a?(Hash) defaults = options[:constraints].select do |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer)) end options[:defaults] = defaults.merge(options[:defaults] || {}) else block, options[:constraints] = options[:constraints], {} end if options.key?(:only) || options.key?(:except) scope[:action_options] = { only: options.delete(:only), except: options.delete(:except) } end if options.key? :anchor raise ArgumentError, "anchor is ignored unless passed to `match`" end @scope.options.each do |option| if option == :blocks value = block elsif option == :options value = options else value = options.delete(option) { POISON } end unless POISON == value scope[option] = send("merge_#{option}_scope", @scope[option], value) end end @scope = @scope.new scope yield self ensure @scope = @scope.parent end