class Rack::Mount::RouteSet
Constants
- PASS
- PATH_INFO
- X_CASCADE
Public Class Methods
Basic RouteSet initializer.
If a block is given, the set is yielded and finalized.
See other aspects for other valid options:
-
Generation::RouteSet.new -
Recognition::RouteSet.new
# File lib/rack/mount/route_set.rb, line 21 def initialize(options = {}, &block) @parameters_key = options.delete(:parameters_key) || 'rack.routing_args' @parameters_key.freeze @named_routes = {} @recognition_key_analyzer = Analysis::Splitting.new @generation_keys = [:controller, :action] @generation_route_keys = [] @request_class = options.delete(:request_class) || Rack::Request @valid_conditions = @request_class.public_instance_methods.map! { |m| m.to_sym } extend CodeGeneration unless options[:_optimize] == false @optimized_recognize_defined = false @routes = [] expire! if block_given? yield self rehash end end
Initialize a new RouteSet without optimizations
# File lib/rack/mount/route_set.rb, line 10 def self.new_without_optimizations(options = {}, &block) new(options.merge(:_optimize => false), &block) end
Public Instance Methods
Builder method to add a route to the set
app-
A valid Rack app to call if the conditions are met.
conditions-
A hash of conditions to match against. Conditions may be expressed as strings or regexps to match against.
defaults-
A hash of values that always gets merged in
name-
Symbol identifier for the route used with named route generations
# File lib/rack/mount/route_set.rb, line 56 def add_route(app, conditions = {}, defaults = {}, name = nil) unless conditions.is_a?(Hash) raise ArgumentError, 'conditions must be a Hash' end unless conditions.all? { |method, pattern| @valid_conditions.include?(method) } raise ArgumentError, 'conditions may only include ' + @valid_conditions.inspect end route = Route.new(app, conditions, defaults, name) @routes << route @recognition_key_analyzer << route.conditions @named_routes[route.name] = route if route.name @generation_route_keys << route.generation_keys expire! route end
Rack compatible recognition and dispatching method. Routes are tried until one returns a non-catch status code. If no routes match, then catch status code is returned.
This method can only be invoked after the RouteSet has been finalized.
# File lib/rack/mount/route_set.rb, line 134 def call(env) raise 'route set not finalized' unless @recognition_graph env[PATH_INFO] = Utils.normalize_path(env[PATH_INFO]) request = nil req = @request_class.new(env) recognize(req) do |route, matches, params| # TODO: We only want to unescape params from uri related methods params.each { |k, v| params[k] = Utils.unescape_uri(v) if v.is_a?(String) } if route.prefix? env[Prefix::KEY] = matches[:path_info].to_s end old_params = env[@parameters_key] env[@parameters_key] = (old_params || {}).merge(params) result = route.app.call(env) if result[1][X_CASCADE] == PASS env[@parameters_key] = old_params else return result end end request || [404, {'Content-Type' => 'text/html', 'X-Cascade' => 'pass'}, ['Not Found']] end
Finalizes the set and builds optimized data structures. You
must freeze the set before you can use call
and url. So remember to call freeze after you are done adding
routes.
# File lib/rack/mount/route_set.rb, line 269 def freeze unless frozen? rehash stubbed_request_class @recognition_key_analyzer = nil @generation_route_keys = nil @valid_conditions = nil @routes.each { |route| route.freeze } @routes.freeze end super end
Number of routes in the set
# File lib/rack/mount/route_set.rb, line 252 def length @routes.length end
# File lib/rack/mount/route_set.rb, line 80 def recognize(obj) raise 'route set not finalized' unless @recognition_graph cache = {} keys = @recognition_keys.map { |key| if key.respond_to?(:call) key.call(cache, obj) else obj.send(key) end } @recognition_graph[*keys].each do |route| matches = {} params = route.defaults.dup if route.conditions.all? { |method, condition| value = obj.send(method) if condition.is_a?(Regexp) && (m = value.match(condition)) matches[method] = m captures = m.captures route.named_captures[method].each do |k, i| if v = captures[i] params[k] = v end end true elsif value == condition true else false end } if block_given? yield route, matches, params else return route, matches, params end end end nil end
Generates a url from Rack env and identifiers or significant keys.
To generate a url by named route, pass the name in as a
Symbol.
url(env, :dashboard) # => "/dashboard"
Additional parameters can be passed in as a hash
url(env, :people, :id => "1") # => "/people/1"
If no named route is given, it will fall back to a slower generation search.
url(env, :controller => "people", :action => "show", :id => "1") # => "/people/1"
# File lib/rack/mount/route_set.rb, line 176 def url(env, *args) named_route, params = nil, {} case args.length when 2 named_route, params = args[0], args[1].dup when 1 if args[0].is_a?(Hash) params = args[0].dup else named_route = args[0] end else raise ArgumentError end only_path = params.delete(:only_path) recall = env[@parameters_key] || {} unless result = generate(:all, named_route, params, recall, :parameterize => lambda { |name, param| Utils.escape_uri(param) }) return end parts, params = result return unless parts params.each do |k, v| if v params[k] = v else params.delete(k) end end req = stubbed_request_class.new(env) req._stubbed_values = parts.merge(:query_string => Utils.build_nested_query(params)) only_path ? req.fullpath : req.url end
Protected Instance Methods
# File lib/rack/mount/route_set.rb, line 286 def recognition_stats { :keys => @recognition_keys, :keys_size => @recognition_keys.size, :graph_size => @recognition_graph.size, :graph_height => @recognition_graph.height, :graph_average_height => @recognition_graph.average_height } end
Private Instance Methods
# File lib/rack/mount/route_set.rb, line 333 def build_generation_graph build_nested_route_set(@generation_keys) { |k, i| throw :skip unless @routes[i].significant_params? if k = @generation_route_keys[i][k] k.to_s else nil end } end
An internal helper method for constructing a nested set from the linear route set.
#build_nested_route_set([:request_method, :path_info]) { |route, method|
route.send(method)
}
# File lib/rack/mount/route_set.rb, line 308 def build_nested_route_set(keys, &block) graph = Multimap.new @routes.each_with_index do |route, index| catch(:skip) do k = keys.map { |key| block.call(key, index) } Utils.pop_trailing_blanks!(k) k.map! { |key| key || /.*/ } graph[*k] = route end end graph end
# File lib/rack/mount/route_set.rb, line 321 def build_recognition_graph build_nested_route_set(@recognition_keys) { |k, i| @recognition_key_analyzer.possible_keys[i][k] } end
# File lib/rack/mount/route_set.rb, line 327 def build_recognition_keys keys = @recognition_key_analyzer.report Utils.debug "recognition keys - #{keys.inspect}" keys end
# File lib/rack/mount/route_set.rb, line 345 def extract_params!(*args) case args.length when 4 named_route, params, recall, options = args when 3 if args[0].is_a?(Hash) params, recall, options = args else named_route, params, recall = args end when 2 if args[0].is_a?(Hash) params, recall = args else named_route, params = args end when 1 if args[0].is_a?(Hash) params = args[0] else named_route = args[0] end else raise ArgumentError end named_route ||= nil params ||= {} recall ||= {} options ||= {} [named_route, params.dup, recall.dup, options.dup] end
# File lib/rack/mount/route_set.rb, line 379 def stubbed_request_class @stubbed_request_class ||= begin klass = Class.new(@request_class) klass.public_instance_methods.each do |method| next if method =~ /^__|object_id/ klass.class_eval <<-RUBY def #{method}(*args, &block) @_stubbed_values[:#{method}] || super end RUBY end klass.class_eval { attr_accessor :_stubbed_values } klass end end