diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/routing')
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 279 |
1 files changed, 145 insertions, 134 deletions
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index ab13fb14ce..a12f60e20d 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -61,7 +61,9 @@ module ActionDispatch attr_reader :requirements, :conditions, :defaults attr_reader :to, :default_controller, :default_action, :as, :anchor - def self.build(scope, set, path, as, options) + def self.build(scope, set, path, as, controller, default_action, to, options) + formatted = options.delete(:format) { scope[:options] && scope[:options][:format] } + options = scope[:options].merge(options) if scope[:options] options.delete :only @@ -69,50 +71,50 @@ module ActionDispatch options.delete :shallow_path options.delete :shallow_prefix options.delete :shallow + options.delete :format - defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {} + defaults = (scope[:defaults] || {}).dup + scope_constraints = scope[:constraints] || {} - new scope, set, path, defaults, as, options + new set, path, defaults, as, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], options end - def initialize(scope, set, path, defaults, as, options) + def initialize(set, path, defaults, as, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, options) @requirements, @conditions = {}, {} @defaults = defaults @set = set - @to = options.delete :to - @default_controller = options.delete(:controller) || scope[:controller] - @default_action = options.delete(:action) || scope[:action] + @to = to + @default_controller = controller + @default_action = default_action @as = as @anchor = options.delete :anchor - formatted = options.delete :format via = Array(options.delete(:via) { [] }) - options_constraints = options.delete :constraints + options_constraints = options.delete(:constraints) || {} path = normalize_path! path, formatted ast = path_ast path path_params = path_params ast - options = normalize_options!(options, formatted, path_params, ast, scope[:module]) - - - split_constraints(path_params, scope[:constraints]) if scope[:constraints] - constraints = constraints(options, path_params) - - split_constraints path_params, constraints + options = normalize_options!(options, formatted, path_params, ast, modyoule) - @blocks = blocks(options_constraints, scope[:blocks]) + constraints = scope_constraints.merge constraints(options, path_params) if options_constraints.is_a?(Hash) - split_constraints path_params, options_constraints options_constraints.each do |key, default| if URL_OPTIONS.include?(key) && (String === default || Fixnum === default) @defaults[key] ||= default end end + @blocks = blocks + constraints.merge! options_constraints + else + @blocks = blocks(options_constraints) end + split_constraints path_params, constraints + normalize_format!(formatted) @conditions[:path_info] = path @@ -217,12 +219,6 @@ module ActionDispatch end end - def verify_callable_constraint(callable_constraint) - unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?) - raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?" - end - end - def add_request_method(via, conditions) return if via == [:all] @@ -302,13 +298,11 @@ module ActionDispatch yield end - def blocks(options_constraints, scope_blocks) - if options_constraints && !options_constraints.is_a?(Hash) - verify_callable_constraint(options_constraints) - [options_constraints] - else - scope_blocks || [] + def blocks(callable_constraint) + unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?) + raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?" end + [callable_constraint] end def constraints(options, path_params) @@ -447,10 +441,10 @@ module ActionDispatch # resources :user, param: :name # # You can override <tt>ActiveRecord::Base#to_param</tt> of a related - # model to constructe an URL. + # model to construct an URL: # # class User < ActiveRecord::Base - # def to_param # overridden + # def to_param # name # end # end @@ -684,7 +678,11 @@ module ActionDispatch def map_method(method, args, &block) options = args.extract_options! options[:via] = method - match(*args, options, &block) + if options.key?(:defaults) + defaults(options.delete(:defaults)) { match(*args, options, &block) } + else + match(*args, options, &block) + end self end end @@ -787,8 +785,8 @@ module ActionDispatch 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?(Fixnum)) + defaults = options[:constraints].select do |k, v| + URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum)) end (options[:defaults] ||= {}).reverse_merge!(defaults) @@ -822,9 +820,11 @@ module ActionDispatch # controller "food" do # match "bacon", action: :bacon, via: :get # end - def controller(controller, options={}) - options[:controller] = controller - scope(options) { yield } + def controller(controller) + @scope = @scope.new(controller: controller) + yield + ensure + @scope = @scope.parent end # Scopes routes to a specific namespace. For example: @@ -870,13 +870,14 @@ module ActionDispatch defaults = { module: path, - path: options.fetch(:path, path), as: options.fetch(:as, path), shallow_path: options.fetch(:path, path), shallow_prefix: options.fetch(:as, path) } - scope(defaults.merge!(options)) { yield } + path_scope(options.delete(:path) { path }) do + scope(defaults.merge!(options)) { yield } + end end # === Parameter Restriction @@ -944,7 +945,10 @@ module ActionDispatch # end # Using this, the +:id+ parameter here will default to 'home'. def defaults(defaults = {}) - scope(:defaults => defaults) { yield } + @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults)) + yield + ensure + @scope = @scope.parent end private @@ -1056,14 +1060,14 @@ module ActionDispatch class Resource #:nodoc: attr_reader :controller, :path, :options, :param - def initialize(entities, api_only = false, options = {}) + def initialize(entities, api_only, shallow, options = {}) @name = entities.to_s @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @as = options[:as] @param = (options[:param] || :id).to_sym @options = options - @shallow = false + @shallow = shallow @api_only = api_only end @@ -1129,17 +1133,15 @@ module ActionDispatch "#{path}/:#{nested_param}" end - def shallow=(value) - @shallow = value - end - def shallow? @shallow end + + def singleton?; false; end end class SingletonResource < Resource #:nodoc: - def initialize(entities, api_only, options) + def initialize(entities, api_only, shallow, options) super @as = nil @controller = (options[:controller] || plural).to_s @@ -1167,6 +1169,8 @@ module ActionDispatch alias :member_scope :path alias :nested_scope :path + + def singleton?; true; end end def resources_path_names(options) @@ -1201,20 +1205,22 @@ module ActionDispatch return self end - resource_scope(:resource, SingletonResource.new(resources.pop, api_only?, options)) do - yield if block_given? + with_scope_level(:resource) do + resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? - concerns(options[:concerns]) if options[:concerns] + concerns(options[:concerns]) if options[:concerns] - collection do - post :create - end if parent_resource.actions.include?(:create) + collection do + post :create + end if parent_resource.actions.include?(:create) - new do - get :new - end if parent_resource.actions.include?(:new) + new do + get :new + end if parent_resource.actions.include?(:new) - set_member_mappings_for_resource + set_member_mappings_for_resource + end end self @@ -1359,21 +1365,23 @@ module ActionDispatch return self end - resource_scope(:resources, Resource.new(resources.pop, api_only?, options)) do - yield if block_given? + with_scope_level(:resources) do + resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do + yield if block_given? - concerns(options[:concerns]) if options[:concerns] + 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 + 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) + new do + get :new + end if parent_resource.actions.include?(:new) - set_member_mappings_for_resource + set_member_mappings_for_resource + end end self @@ -1479,7 +1487,7 @@ module ActionDispatch end def shallow? - parent_resource.instance_of?(Resource) && @scope[:shallow] + !parent_resource.singleton? && @scope[:shallow] end # Matches a url pattern to one or more routes. @@ -1523,48 +1531,65 @@ module ActionDispatch options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" end - paths.each do |_path| - route_options = options.dup - route_options[:path] ||= _path if _path.is_a?(String) + controller = options.delete(:controller) || @scope[:controller] + option_path = options.delete :path + to = options.delete :to - 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 + path_types = paths.group_by(&:class) + path_types.fetch(String, []).each do |_path| + route_options = options.dup + process_path(route_options, controller, _path, option_path || _path, to) + end - decomposed_match(_path, route_options) + path_types.fetch(Symbol, []).each do |action| + route_options = options.dup + decomposed_match(action, controller, route_options, option_path, to) end + self end - def using_match_shorthand?(path, options) - path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$} + def process_path(options, controller, path, option_path, to) + path_without_format = path.sub(/\(\.:format\)$/, '') + if using_match_shorthand?(path_without_format, to, options[:action]) + to ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1') + to.tr!("-", "_") + end + + decomposed_match(path, controller, options, option_path, to) end - def decomposed_match(path, options) # :nodoc: + def using_match_shorthand?(path, to, action) + return false if to || action + + path =~ %r{^/?[-\w]+/[-\w/]+$} + end + + def decomposed_match(path, controller, options, _path, to) # :nodoc: if on = options.delete(:on) - send(on) { decomposed_match(path, options) } + send(on) { decomposed_match(path, controller, options, _path, to) } else case @scope.scope_level when :resources - nested { decomposed_match(path, options) } + nested { decomposed_match(path, controller, options, _path, to) } when :resource - member { decomposed_match(path, options) } + member { decomposed_match(path, controller, options, _path, to) } else - add_route(path, options) + add_route(path, controller, options, _path, to) end end end - def add_route(action, options) # :nodoc: - path = path_for_action(action, options.delete(:path)) + def add_route(action, controller, options, _path, to) # :nodoc: + path = path_for_action(action, _path) raise ArgumentError, "path is required" if path.blank? action = action.to_s + default_action = options.delete(:action) || @scope[:action] + if action =~ /^[\w\-\/]+$/ - options[:action] ||= action.tr('-', '_') unless action.include?("/") + default_action ||= action.tr('-', '_') unless action.include?("/") else action = nil end @@ -1575,7 +1600,7 @@ module ActionDispatch name_for_action(options.delete(:as), action) end - mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options) + mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, controller, default_action, to, options) app, conditions, requirements, defaults, as, anchor = mapping.to_route @set.add_route(app, conditions, requirements, defaults, as, anchor) end @@ -1591,7 +1616,7 @@ module ActionDispatch if @scope.resources? with_scope_level(:root) do - scope(parent_resource.path) do + path_scope(parent_resource.path) do super(options) end end @@ -1667,18 +1692,6 @@ module ActionDispatch @scope.nested? end - def with_exclusive_scope - begin - @scope = @scope.new(:as => nil, :path => nil) - - with_scope_level(:exclusive) do - yield - end - ensure - @scope = @scope.parent - end - end - def with_scope_level(kind) @scope = @scope.new_level(kind) yield @@ -1686,16 +1699,11 @@ module ActionDispatch @scope = @scope.parent end - def resource_scope(kind, resource) #:nodoc: - resource.shallow = @scope[:shallow] + def resource_scope(resource) #:nodoc: @scope = @scope.new(:scope_level_resource => resource) - @nesting.push(resource) - with_scope_level(kind) do - controller_scope(parent_resource.resource_scope) { yield } - end + controller(resource.resource_scope) { yield } ensure - @nesting.pop @scope = @scope.parent end @@ -1708,12 +1716,10 @@ module ActionDispatch options end - def nesting_depth #:nodoc: - @nesting.size - end - def shallow_nesting_depth #:nodoc: - @nesting.count(&:shallow?) + @scope.find_all { |node| + node.frame[:scope_level_resource] + }.count { |node| node.frame[:scope_level_resource].shallow? } end def param_constraint? #:nodoc: @@ -1739,16 +1745,17 @@ module ActionDispatch end def path_for_action(action, path) #:nodoc: - if path.blank? && canonical_action?(action) + return "#{@scope[:path]}/#{path}" if path + + if canonical_action?(action) @scope[:path].to_s else - "#{@scope[:path]}/#{action_path(action, path)}" + "#{@scope[:path]}/#{action_path(action)}" end end - def action_path(name, path = nil) #:nodoc: - name = name.to_sym if name.is_a?(String) - path || @scope[:path_names][name] || name.to_s + def action_path(name) #:nodoc: + @scope[:path_names][name.to_sym] || name end def prefix_name_for_action(as, action) #:nodoc: @@ -1806,13 +1813,6 @@ module ActionDispatch end private - def controller_scope(controller) - @scope = @scope.new(controller: controller) - yield - ensure - @scope = @scope.parent - end - def path_scope(path) @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) yield @@ -1936,7 +1936,7 @@ module ActionDispatch attr_reader :parent, :scope_level - def initialize(hash, parent = {}, scope_level = nil) + def initialize(hash, parent = NULL, scope_level = nil) @hash = hash @parent = parent @scope_level = scope_level @@ -1984,23 +1984,34 @@ module ActionDispatch end def new_level(level) - self.class.new(self, self, level) + self.class.new(frame, self, level) end - def fetch(key, &block) - @hash.fetch(key, &block) + def [](key) + scope = find { |node| node.frame.key? key } + scope && scope.frame[key] end - def [](key) - @hash.fetch(key) { @parent[key] } + include Enumerable + + def each + node = self + loop do + break if node.equal? NULL + yield node + node = node.parent + end end + + def frame; @hash; end + + NULL = Scope.new(nil, nil) end def initialize(set) #:nodoc: @set = set @scope = Scope.new({ :path_names => @set.resources_path_names }) @concerns = {} - @nesting = [] end include Base |