1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/lib/action_dispatch/journey/route.rb
Ben Hughes f1525dac11 Optimize Journey::Route#score
Scoring routes based on constraints repeated many type conversions that
could be performed in the outer loop.  Determinations of score and
fitness also used Array operations that required allocations.  Against
my benchmark with a large routeset, this reduced object allocations by
over 30x and wall time by over 3x.
2016-12-28 17:19:15 -08:00

186 lines
4.7 KiB
Ruby

module ActionDispatch
# :stopdoc:
module Journey
class Route
attr_reader :app, :path, :defaults, :name, :precedence
attr_reader :constraints, :internal
alias :conditions :constraints
module VerbMatchers
VERBS = %w{ DELETE GET HEAD OPTIONS LINK PATCH POST PUT TRACE UNLINK }
VERBS.each do |v|
class_eval <<-eoc
class #{v}
def self.verb; name.split("::").last; end
def self.call(req); req.#{v.downcase}?; end
end
eoc
end
class Unknown
attr_reader :verb
def initialize(verb)
@verb = verb
end
def call(request); @verb === request.request_method; end
end
class All
def self.call(_); true; end
def self.verb; ""; end
end
VERB_TO_CLASS = VERBS.each_with_object(all: All) do |verb, hash|
klass = const_get verb
hash[verb] = klass
hash[verb.downcase] = klass
hash[verb.downcase.to_sym] = klass
end
end
def self.verb_matcher(verb)
VerbMatchers::VERB_TO_CLASS.fetch(verb) do
VerbMatchers::Unknown.new verb.to_s.dasherize.upcase
end
end
def self.build(name, app, path, constraints, required_defaults, defaults)
request_method_match = verb_matcher(constraints.delete(:request_method))
new name, app, path, constraints, required_defaults, defaults, request_method_match, 0
end
##
# +path+ is a path constraint.
# +constraints+ is a hash of constraints to be applied to this route.
def initialize(name, app, path, constraints, required_defaults, defaults, request_method_match, precedence, internal = false)
@name = name
@app = app
@path = path
@request_method_match = request_method_match
@constraints = constraints
@defaults = defaults
@required_defaults = nil
@_required_defaults = required_defaults
@required_parts = nil
@parts = nil
@decorated_ast = nil
@precedence = precedence
@path_formatter = @path.build_formatter
@internal = internal
end
def ast
@decorated_ast ||= begin
decorated_ast = path.ast
decorated_ast.find_all(&:terminal?).each { |n| n.memo = self }
decorated_ast
end
end
def requirements
# needed for rails `rails routes`
@defaults.merge(path.requirements).delete_if { |_, v|
/.+?/ == v
}
end
def segments
path.names
end
def required_keys
required_parts + required_defaults.keys
end
def score(supplied_keys)
required_keys = path.required_names
required_keys.each do |k|
return -1 unless supplied_keys.include?(k)
end
score = 0
path.names.each do |k|
score += 1 if supplied_keys.include?(k)
end
score + (required_defaults.length * 2)
end
def parts
@parts ||= segments.map(&:to_sym)
end
alias :segment_keys :parts
def format(path_options)
@path_formatter.evaluate path_options
end
def required_parts
@required_parts ||= path.required_names.map(&:to_sym)
end
def required_default?(key)
@_required_defaults.include?(key)
end
def required_defaults
@required_defaults ||= @defaults.dup.delete_if do |k, _|
parts.include?(k) || !required_default?(k)
end
end
def glob?
!path.spec.grep(Nodes::Star).empty?
end
def dispatcher?
@app.dispatcher?
end
def matches?(request)
match_verb(request) &&
constraints.all? { |method, value|
case value
when Regexp, String
value === request.send(method).to_s
when Array
value.include?(request.send(method))
when TrueClass
request.send(method).present?
when FalseClass
request.send(method).blank?
else
value === request.send(method)
end
}
end
def ip
constraints[:ip] || //
end
def requires_matching_verb?
!@request_method_match.all? { |x| x == VerbMatchers::All }
end
def verb
verbs.join("|")
end
private
def verbs
@request_method_match.map(&:verb)
end
def match_verb(request)
@request_method_match.any? { |m| m.call request }
end
end
end
# :startdoc:
end