diff options
Diffstat (limited to 'actionpack/lib/action_dispatch/routing/mapper.rb')
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 245 |
1 files changed, 169 insertions, 76 deletions
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8f33346a4f..9aaa4355f2 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -68,16 +68,11 @@ module ActionDispatch end def normalize_path(path) - path = nil if path == "" - path = "#{@scope[:path]}#{path}" if @scope[:path] - path = Rack::Mount::Utils.normalize_path(path) if path - - raise ArgumentError, "path is required" unless path - - path + path = "#{@scope[:path]}/#{path}" + raise ArgumentError, "path is required" if path.empty? + Mapper.normalize_path(path) end - def app Constraints.new( to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults), @@ -123,7 +118,6 @@ module ActionDispatch end end - def blocks if @options[:constraints].present? && !@options[:constraints].is_a?(Hash) block = @options[:constraints] @@ -162,6 +156,14 @@ module ActionDispatch end end + # Invokes Rack::Mount::Utils.normalize path and ensure that + # (:locale) becomes (/:locale) instead of /(:locale). + def self.normalize_path(path) + path = Rack::Mount::Utils.normalize_path(path) + path.sub!(%r{/\(+/?:}, '(/:') + path + end + module Base def initialize(set) @set = set @@ -200,13 +202,22 @@ module ActionDispatch path = args.shift || block path_proc = path.is_a?(Proc) ? path : proc { |params| path % params } status = options[:status] || 301 + body = 'Moved Permanently' lambda do |env| - req = Rack::Request.new(env) - params = path_proc.call(env["action_dispatch.request.path_parameters"]) - url = req.scheme + '://' + req.host + params + req = Request.new(env) + + uri = URI.parse(path_proc.call(req.params.symbolize_keys)) + uri.scheme ||= req.scheme + uri.host ||= req.host + uri.port ||= req.port unless req.port == 80 - [ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ] + headers = { + 'Location' => uri.to_s, + 'Content-Type' => 'text/html', + 'Content-Length' => body.length.to_s + } + [ status, headers, [body] ] end end @@ -236,46 +247,35 @@ module ActionDispatch options[:controller] = args.first end - if path = options.delete(:path) - path_set = true - path, @scope[:path] = @scope[:path], Rack::Mount::Utils.normalize_path(@scope[:path].to_s + path.to_s) - else - path_set = false - end + recover = {} - if name_prefix = options.delete(:name_prefix) - name_prefix_set = true - name_prefix, @scope[:name_prefix] = @scope[:name_prefix], (@scope[:name_prefix] ? "#{@scope[:name_prefix]}_#{name_prefix}" : name_prefix) - else - name_prefix_set = false + options[:constraints] ||= {} + unless options[:constraints].is_a?(Hash) + block, options[:constraints] = options[:constraints], {} end - if controller = options.delete(:controller) - controller_set = true - controller, @scope[:controller] = @scope[:controller], controller - else - controller_set = false + scope_options.each do |option| + if value = options.delete(option) + recover[option] = @scope[option] + @scope[option] = send("merge_#{option}_scope", @scope[option], value) + end end - constraints = options.delete(:constraints) || {} - unless constraints.is_a?(Hash) - block, constraints = constraints, {} - end - constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints) - blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block] + recover[:block] = @scope[:blocks] + @scope[:blocks] = merge_blocks_scope(@scope[:blocks], block) - options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) + recover[:options] = @scope[:options] + @scope[:options] = merge_options_scope(@scope[:options], options) yield - self ensure - @scope[:path] = path if path_set - @scope[:name_prefix] = name_prefix if name_prefix_set - @scope[:controller] = controller if controller_set - @scope[:options] = options - @scope[:blocks] = blocks - @scope[:constraints] = constraints + scope_options.each do |option| + @scope[option] = recover[option] if recover.has_key?(option) + end + + @scope[:options] = recover[:options] + @scope[:blocks] = recover[:block] end def controller(controller) @@ -283,7 +283,7 @@ module ActionDispatch end def namespace(path) - scope("/#{path}") { yield } + scope(path.to_s, :name_prefix => path.to_s, :namespace => path.to_s) { yield } end def constraints(constraints = {}) @@ -304,25 +304,83 @@ module ActionDispatch args.push(options) super(*args) end + + private + def scope_options + @scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym } + end + + def merge_path_scope(parent, child) + Mapper.normalize_path("#{parent}/#{child}") + end + + def merge_name_prefix_scope(parent, child) + parent ? "#{parent}_#{child}" : child + end + + def merge_namespace_scope(parent, child) + parent ? "#{parent}/#{child}" : child + end + + def merge_controller_scope(parent, child) + @scope[:namespace] ? "#{@scope[:namespace]}/#{child}" : child + end + + def merge_resources_path_names_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_constraints_scope(parent, child) + merge_options_scope(parent, child) + end + + def merge_blocks_scope(parent, child) + (parent || []) + [child] + end + + def merge_options_scope(parent, child) + (parent || {}).merge(child) + end end module Resources + CRUD_ACTIONS = [:index, :show, :create, :update, :destroy] + class Resource #:nodoc: - attr_reader :plural, :singular + def self.default_actions + [:index, :create, :new, :show, :update, :destroy, :edit] + end + + attr_reader :plural, :singular, :options def initialize(entities, options = {}) entities = entities.to_s + @options = options @plural = entities.pluralize @singular = entities.singularize end + def default_actions + self.class.default_actions + end + + def actions + if only = options[:only] + only.map(&:to_sym) + elsif except = options[:except] + default_actions - except.map(&:to_sym) + else + default_actions + end + end + def name - plural + options[:as] || plural end def controller - plural + options[:controller] || plural end def member_name @@ -339,15 +397,24 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: + def self.default_actions + [:show, :create, :update, :destroy, :new, :edit] + end + def initialize(entity, options = {}) super end def name - singular + options[:as] || singular end end + def initialize(*args) + super + @scope[:resources_path_names] = @set.resources_path_names + end + def resource(*resources, &block) options = resources.extract_options! @@ -357,7 +424,14 @@ module ActionDispatch return self end - resource = SingletonResource.new(resources.pop) + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + resource(resources, options) + end + return self + end + + resource = SingletonResource.new(resources.pop, options) if @scope[:scope_level] == :resources nested do @@ -366,16 +440,16 @@ module ActionDispatch return self end - scope(:path => "/#{resource.name}", :controller => resource.controller) do + scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resource, resource) do yield if block_given? - get "(.:format)", :to => :show, :as => resource.member_name - post "(.:format)", :to => :create - put "(.:format)", :to => :update - delete "(.:format)", :to => :destroy - get "/new(.:format)", :to => :new, :as => "new_#{resource.singular}" - get "/edit(.:format)", :to => :edit, :as => "edit_#{resource.singular}" + get :show, :as => resource.member_name if resource.actions.include?(:show) + post :create if resource.actions.include?(:create) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :new, :as => resource.singular if resource.actions.include?(:new) + get :edit, :as => resource.singular if resource.actions.include?(:edit) end end @@ -391,7 +465,14 @@ module ActionDispatch return self end - resource = Resource.new(resources.pop) + if path_names = options.delete(:path_names) + scope(:resources_path_names => path_names) do + resources(resources, options) + end + return self + end + + resource = Resource.new(resources.pop, options) if @scope[:scope_level] == :resources nested do @@ -400,28 +481,22 @@ module ActionDispatch return self end - scope(:path => "/#{resource.name}", :controller => resource.controller) do + scope(:path => resource.name.to_s, :controller => resource.controller) do with_scope_level(:resources, resource) do yield if block_given? with_scope_level(:collection) do - get "(.:format)", :to => :index, :as => resource.collection_name - post "(.:format)", :to => :create - - with_exclusive_name_prefix :new do - get "/new(.:format)", :to => :new, :as => resource.singular - end + get :index, :as => resource.collection_name if resource.actions.include?(:index) + post :create if resource.actions.include?(:create) + get :new, :as => resource.singular if resource.actions.include?(:new) end with_scope_level(:member) do - scope("/:id") do - get "(.:format)", :to => :show, :as => resource.member_name - put "(.:format)", :to => :update - delete "(.:format)", :to => :destroy - - with_exclusive_name_prefix :edit do - get "/edit(.:format)", :to => :edit, :as => resource.singular - end + scope(':id') do + get :show, :as => resource.member_name if resource.actions.include?(:show) + put :update if resource.actions.include?(:update) + delete :destroy if resource.actions.include?(:destroy) + get :edit, :as => resource.singular if resource.actions.include?(:edit) end end end @@ -448,7 +523,7 @@ module ActionDispatch end with_scope_level(:member) do - scope("/:id", :name_prefix => parent_resource.member_name, :as => "") do + scope(':id', :name_prefix => parent_resource.member_name, :as => "") do yield end end @@ -460,7 +535,7 @@ module ActionDispatch end with_scope_level(:nested) do - scope("/#{parent_resource.id_segment}", :name_prefix => parent_resource.member_name) do + scope(parent_resource.id_segment, :name_prefix => parent_resource.member_name) do yield end end @@ -474,9 +549,22 @@ module ActionDispatch return self end + resources_path_names = options.delete(:path_names) + if args.first.is_a?(Symbol) - with_exclusive_name_prefix(args.first) do - return match("/#{args.first}(.:format)", options.merge(:to => args.first.to_sym)) + action = args.first + if CRUD_ACTIONS.include?(action) + begin + old_path = @scope[:path] + @scope[:path] = "#{@scope[:path]}(.:format)" + return match(options.reverse_merge(:to => action)) + ensure + @scope[:path] = old_path + end + else + with_exclusive_name_prefix(action) do + return match("#{action_path(action, resources_path_names)}(.:format)", options.reverse_merge(:to => action)) + end end end @@ -502,6 +590,11 @@ module ActionDispatch end private + def action_path(name, path_names = nil) + path_names ||= @scope[:resources_path_names] + path_names[name.to_sym] || name.to_s + end + def with_exclusive_name_prefix(prefix) begin old_name_prefix = @scope[:name_prefix] |