Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Extract Mapping class from monster match method
  • Loading branch information
dhh committed Dec 24, 2009
1 parent 74b2e00 commit 2b7256a
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 91 deletions.
227 changes: 137 additions & 90 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Expand Up @@ -19,104 +19,148 @@ def call(env)

@constraints.each { |constraint|
if constraint.respond_to?(:matches?) && !constraint.matches?(req)
return [417, {}, []]
return [ 417, {}, [] ]
elsif constraint.respond_to?(:call) && !constraint.call(req)
return [417, {}, []]
return [ 417, {}, [] ]
end
}

@app.call(env)
end
end

module Base
def initialize(set)
@set = set
class Mapping
def initialize(set, scope, args)
@set, @scope = set, scope
@path, @options = extract_path_and_options(args)
end

def root(options = {})
match '/', options.merge(:as => :root)
def to_route
[ app, conditions, requirements, defaults, @options[:as] ]
end

def match(*args)
if args.one? && args.first.is_a?(Hash)
path = args.first.keys.first
options = { :to => args.first.values.first }
else
path = args.first

private
def extract_path_and_options(args)
options = args.extract_options!
end

conditions, defaults = {}, {}

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

constraints = options[:constraints] || {}
unless constraints.is_a?(Hash)
block, constraints = constraints, {}
if args.empty?
path, to = options.find { |name, value| name.is_a?(String) }
options.merge!(:to => to).delete(path) if path
else
path = args.first
end

[ normalize_path(path), options ]
end
blocks = ((@scope[:blocks] || []) + [block]).compact
constraints = (@scope[:constraints] || {}).merge(constraints)
options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) }

conditions[:path_info] = path
requirements = constraints.dup
def normalize_path(path)
path = nil if path == ""
path = "#{@scope[:path]}#{path}" if @scope[:path]
path = Rack::Mount::Utils.normalize_path(path) if path

path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS)
segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names
constraints.reject! { |k, v| segment_keys.include?(k.to_s) }
conditions.merge!(constraints)
raise ArgumentError, "path is required" unless path

path
end

requirements[:controller] ||= @set.controller_constraints

if via = options[:via]
via = Array(via).map { |m| m.to_s.upcase }
conditions[:request_method] = Regexp.union(*via)
def app
Constraints.new(
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
blocks
)
end

defaults[:controller] ||= @scope[:controller].to_s if @scope[:controller]

app = initialize_app_endpoint(options, defaults)
validate_defaults!(app, defaults, segment_keys)
app = Constraints.new(app, blocks)
def conditions
{ :path_info => @path }.merge(constraints).merge(request_method_condition)
end

def requirements
@requirements ||= returning(@options[:constraints] || {}) do |requirements|
requirements.reverse_merge!(@scope[:constraints]) if @scope[:constraints]
@options.each { |k, v| requirements[k] = v if v.is_a?(Regexp) }
requirements[:controller] ||= @set.controller_constraints
end
end

@set.add_route(app, conditions, requirements, defaults, options[:as])
def defaults
@defaults ||= if to.respond_to?(:call)
{ }
else
defaults = case to
when String
controller, action = to.split('#')
{ :controller => controller, :action => action }
when Symbol
{ :controller => default_controller, :action => to.to_s }
else
{ :controller => default_controller }
end

if defaults[:controller].blank? && segment_keys.exclude?("controller")
raise ArgumentError, "missing :controller"
end

if defaults[:action].blank? && segment_keys.exclude?("action")
raise ArgumentError, "missing :action"
end

defaults
end
end

self
end

def blocks
if @options[:constraints].present? && !@options[:constraints].is_a?(Hash)
block = @options[:constraints]
else
block = nil
end

((@scope[:blocks] || []) + [ block ]).compact
end

def constraints
@constraints ||= requirements.reject { |k, v| segment_keys.include?(k.to_s) || k == :controller }
end

private
def initialize_app_endpoint(options, defaults)
app = nil

if options[:to].respond_to?(:call)
app = options[:to]
defaults.delete(:controller)
defaults.delete(:action)
elsif options[:to].is_a?(String)
defaults[:controller], defaults[:action] = options[:to].split('#')
elsif options[:to].is_a?(Symbol)
defaults[:action] = options[:to].to_s
def request_method_condition
if via = @options[:via]
via = Array(via).map { |m| m.to_s.upcase }
{ :request_method => Regexp.union(*via) }
else
{ }
end
end

def segment_keys
@segment_keys ||= Rack::Mount::RegexpWithNamedGroups.new(
Rack::Mount::Strexp.compile(@path, requirements, SEPARATORS)
).names
end

app || Routing::RouteSet::Dispatcher.new(:defaults => defaults)
def to
@options[:to]
end

def default_controller
@scope[:controller].to_s if @scope[:controller]
end
end

def validate_defaults!(app, defaults, segment_keys)
return unless app.is_a?(Routing::RouteSet::Dispatcher)
module Base
def initialize(set)
@set = set
end

unless defaults.include?(:controller) || segment_keys.include?("controller")
raise ArgumentError, "missing :controller"
end
def root(options = {})
match '/', options.reverse_merge(:as => :root)
end

unless defaults.include?(:action) || segment_keys.include?("action")
raise ArgumentError, "missing :action"
end
end
def match(*args)
@set.add_route(*Mapping.new(@set, @scope, args).to_route)
self
end
end

module HttpHelpers
Expand All @@ -139,15 +183,16 @@ def delete(*args, &block)
def redirect(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}

path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc {|params| path % params }
status = options[:status] || 301
path = args.shift || block
path_proc = path.is_a?(Proc) ? path : proc { |params| path % params }
status = options[:status] || 301

lambda do |env|
req = Rack::Request.new(env)
req = Rack::Request.new(env)
params = path_proc.call(env["action_dispatch.request.path_parameters"])
url = req.scheme + '://' + req.host + params
[status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']]
url = req.scheme + '://' + req.host + params

[ status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently'] ]
end
end

Expand Down Expand Up @@ -211,11 +256,11 @@ def scope(*args)

self
ensure
@scope[:path] = path if path_set
@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[:controller] = controller if controller_set
@scope[:options] = options
@scope[:blocks] = blocks
@scope[:constraints] = constraints
end

Expand Down Expand Up @@ -311,12 +356,12 @@ def resource(*resources, &block)
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 "(.: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}"
end
end

Expand Down Expand Up @@ -346,18 +391,20 @@ def resources(*resources, &block)
yield if block_given?

with_scope_level(:collection) do
get "(.:format)", :to => :index, :as => resource.collection_name
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
end

with_scope_level(:member) do
scope("/:id") do
get "(.:format)", :to => :show, :as => resource.member_name
put "(.:format)", :to => :update
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
Expand Down Expand Up @@ -473,4 +520,4 @@ def with_scope_level(kind, resource = parent_resource)
include Resources
end
end
end
end
13 changes: 12 additions & 1 deletion actionpack/test/dispatch/routing_test.rb
Expand Up @@ -22,7 +22,7 @@ def self.matches?(request)
delete 'logout', :to => :destroy, :as => :logout
end

match 'account/logout' => redirect("/logout")
match 'account/logout' => redirect("/logout"), :as => :logout_redirect
match 'account/login', :to => redirect("/login")

match 'account/modulo/:name', :to => redirect("/%{name}s")
Expand Down Expand Up @@ -110,6 +110,7 @@ def self.matches?(request)
resources :rooms
end

match '/info' => 'projects#info', :as => 'info'
root :to => 'projects#index'
end
end
Expand Down Expand Up @@ -153,6 +154,7 @@ def test_login_redirect

def test_logout_redirect_without_to
with_test_routes do
assert_equal '/account/logout', logout_redirect_path
get '/account/logout'
assert_equal 301, @response.status
assert_equal 'http://www.example.com/logout', @response.headers['Location']
Expand Down Expand Up @@ -462,10 +464,19 @@ def test_access_token_rooms

def test_root
with_test_routes do
assert_equal '/', root_path
get '/'
assert_equal 'projects#index', @response.body
end
end

def test_index
with_test_routes do
assert_equal '/info', info_path
get '/info'
assert_equal 'projects#info', @response.body
end
end

private
def with_test_routes
Expand Down

0 comments on commit 2b7256a

Please sign in to comment.