Rails源代码分析(32):ActionController::Routing(3) RouteSet

1 类结构
RouteSet
RouteSet::Mapper 
RouteSet:: NamedRouteCollection 


2 代码分析

RouteSet::Mapper
这个就是在config/routes.rb中用来创建route的方法
  1.       # Mapper instances are used to build routes. The object passed to the draw
  2.       # block in config/routes.rb is a Mapper instance.
  3.       #
  4.       # Mapper instances have relatively few instance methods, in order to avoid
  5.       # clashes with named routes.
  6.       class Mapper #:doc:
  7.         def initialize(set) #:nodoc:
  8.           @set = set
  9.         end
  10.         # Create an unnamed route with the provided +path+ and +options+. See
  11.         # ActionController::Routing for an introduction to routes.
  12.         def connect(path, options = {}) # 添加route
  13.           @set.add_route(path, options)
  14.         end
  15.         # Creates a named route called "root" for matching the root level request.
  16.         def root(options = {}) # 添加root route
  17.           if options.is_a?(Symbol)
  18.             if source_route = @set.named_routes.routes[options]
  19.               options = source_route.defaults.merge({ :conditions => source_route.conditions })
  20.             end
  21.           end
  22.           named_route("root"'', options)
  23.         end
  24.         def named_route(name, path, options = {}) # 添加命名route
  25.           @set.add_named_route(name, path, options)
  26.         end
  27.         # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
  28.         # Example:
  29.         #
  30.         #   map.namespace(:admin) do |admin|
  31.         #     admin.resources :products,
  32.         #       :has_many => [ :tags, :images, :variants ]
  33.         #   end
  34.         #
  35.         # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
  36.         # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
  37.         # Admin::TagsController.
  38.         def namespace(name, options = {}, &block) # 在一个命名空间下添加route
  39.           if options[:namespace]
  40.             with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}":name_prefix => "#{options.delete(:name_prefix)}#{name}_":namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
  41.           else
  42.             with_options({:path_prefix => name, :name_prefix => "#{name}_":namespace => "#{name}/" }.merge(options), &block)
  43.           end
  44.         end
  45.         # 采用复写method_missing的模式来来添加命名route
  46.        def method_missing(route_name, *args, &proc) #:nodoc:
  47.           super unless args.length >= 1 && proc.nil?
  48.           @set.add_named_route(route_name, *args)
  49.         end
  50.       end

RouteSet:: NamedRouteCollection 
  1.       # A NamedRouteCollection instance is a collection of named routes, and also
  2.       # maintains an anonymous module that can be used to install helpers for the
  3.       # named routes.
  4.       class NamedRouteCollection #:nodoc:
  5.         include Enumerable
  6.         include ActionController::Routing::Optimisation
  7.         attr_reader :routes:helpers
  8.         def initialize
  9.           clear!
  10.         end
  11.         def clear!
  12.           @routes = {}
  13.           @helpers = []
  14.           @module ||= Module.new
  15.           @module.instance_methods.each do |selector|
  16.             @module.class_eval { remove_method selector }
  17.           end
  18.         end
  19.         # 增加一个route
  20.        def add(name, route)
  21.           routes[name.to_sym] = route
  22.           define_named_route_methods(name, route)
  23.         end
  24.         # 获取一个route
  25.        def get(name)
  26.           routes[name.to_sym]
  27.         end
  28.         alias []=   add
  29.         alias []    get
  30.         alias clear clear!
  31.         def each
  32.           routes.each { |name, route| yield name, route }
  33.           self
  34.         end
  35.         def names
  36.           routes.keys
  37.         end
  38.         def length
  39.           routes.length
  40.         end
  41.         def reset!
  42.           old_routes = routes.dup
  43.           clear!
  44.           old_routes.each do |name, route|
  45.             add(name, route)
  46.           end
  47.         end
  48.         # 把这些Module include在ActionController和ActionView
  49.        def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false)
  50.           reset! if regenerate
  51.           Array(destinations).each do |dest|
  52.             dest.send! :include@module
  53.           end
  54.         end
  55.         private
  56.           def url_helper_name(name, kind = :url)
  57.             :"#{name}_#{kind}"
  58.           end
  59.           def hash_access_name(name, kind = :url)
  60.             :"hash_for_#{name}_#{kind}"
  61.           end
  62.           def define_named_route_methods(name, route)
  63.             {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts|
  64.               hash = route.defaults.merge(:use_route => name).merge(opts)
  65.               define_hash_access route, name, kind, hash
  66.               define_url_helper route, name, kind, hash
  67.             end
  68.           end
  69.           def define_hash_access(route, name, kind, options)
  70.             selector = hash_access_name(name, kind)
  71.             @module.module_eval <<-end_eval # We use module_eval to avoid leaks
  72.               def #{selector}(options = nil)
  73.                 options ? #{options.inspect}.merge(options) : #{options.inspect}
  74.               end
  75.               protected :#{selector}
  76.             end_eval
  77.             helpers << selector
  78.           end
  79.           def define_url_helper(route, name, kind, options)
  80.             selector = url_helper_name(name, kind)
  81.             # The segment keys used for positional paramters
  82.             hash_access_method = hash_access_name(name, kind)
  83.             # allow ordered parameters to be associated with corresponding
  84.             # dynamic segments, so you can do
  85.             #
  86.             #   foo_url(bar, baz, bang)
  87.             #
  88.             # instead of
  89.             #
  90.             #   foo_url(:bar => bar, :baz => baz, :bang => bang)
  91.             #
  92.             # Also allow options hash, so you can do
  93.             #
  94.             #   foo_url(bar, baz, bang, :sort_by => 'baz')
  95.             #
  96.             @module.module_eval <<-end_eval # We use module_eval to avoid leaks
  97.               def #{selector}(*args)
  98.                 #{generate_optimisation_block(route, kind)}
  99.                 opts = if args.empty? || Hash === args.first
  100.                   args.first || {}
  101.                 else
  102.                   options = args.extract_options!
  103.                   args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)|
  104.                     h[k] = v
  105.                     h
  106.                   end
  107.                   options.merge(args)
  108.                 end
  109.                 url_for(#{hash_access_method}(opts))
  110.               end
  111.               protected :#{selector}
  112.             end_eval
  113.             helpers << selector
  114.           end
  115.       end
RouteSet:
  1.       attr_accessor :routes:named_routes:configuration_file
  2.       def initialize
  3.         self.routes = []
  4.         self.named_routes = NamedRouteCollection.new
  5.       end
  6.       # Subclasses and plugins may override this method to specify a different
  7.       # RouteBuilder instance, so that other route DSL's can be created.
  8.       def builder
  9.         @builder ||= RouteBuilder.new
  10.       end
  11.       def draw
  12.         clear!
  13.         yield Mapper.new(self)
  14.         install_helpers
  15.       end
  16.       def clear!
  17.         routes.clear
  18.         named_routes.clear
  19.         @combined_regexp = nil
  20.         @routes_by_controller = nil
  21.         # This will force routing/recognition_optimization.rb
  22.         # to refresh optimisations.
  23.         @compiled_recognize_optimized = nil
  24.       end
  25.       def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
  26.         Array(destinations).each { |d| d.module_eval { include Helpers } }
  27.         # A helper module to hold URL related helpers.
  28.         # module Helpers include PolymorphicRoutes end        
  29.         named_routes.install(destinations, regenerate_code)
  30.       end
  31.       def empty?
  32.         routes.empty?
  33.       end
  34.       def load!
  35.         Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
  36.         clear!
  37.         load_routes!
  38.         install_helpers
  39.       end
  40.       # reload! will always force a reload whereas load checks the timestamp first
  41.       alias reload! load!
  42.       def reload
  43.         if @routes_last_modified && configuration_file
  44.           mtime = File.stat(configuration_file).mtime
  45.           # if it hasn't been changed, then just return
  46.           return if mtime == @routes_last_modified
  47.           # if it has changed then record the new time and fall to the load! below
  48.           @routes_last_modified = mtime
  49.         end
  50.         load!
  51.       end
  52.       def load_routes!
  53.         if configuration_file
  54.           load configuration_file
  55.           @routes_last_modified = File.stat(configuration_file).mtime
  56.         else
  57.           add_route ":controller/:action/:id"
  58.         end
  59.       end
  60.       def add_route(path, options = {})
  61.         route = builder.build(path, options)
  62.         routes << route
  63.         route
  64.       end
  65.       def add_named_route(name, path, options = {})
  66.         # TODO - is options EVER used?
  67.         name = options[:name_prefix] + name.to_s if options[:name_prefix]
  68.         named_routes[name.to_sym] = add_route(path, options)
  69.       end
  70.       def options_as_params(options)
  71.         # If an explicit :controller was given, always make :action explicit
  72.         # too, so that action expiry works as expected for things like
  73.         #
  74.         #   generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
  75.         #
  76.         # (the above is from the unit tests). In the above case, because the
  77.         # controller was explicitly given, but no action, the action is implied to
  78.         # be "index", not the recalled action of "show".
  79.         #
  80.         # great fun, eh?
  81.         options_as_params = options.clone
  82.         options_as_params[:action] ||= 'index' if options[:controller]
  83.         options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
  84.         options_as_params
  85.       end
  86.       def build_expiry(options, recall)
  87.         recall.inject({}) do |expiry, (key, recalled_value)|
  88.           expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
  89.           expiry
  90.         end
  91.       end     

  1.       # Generate the path indicated by the arguments, and return an array of
  2.       # the keys that were not used to generate it.
  3.       def extra_keys(options, recall={})
  4.         generate_extras(options, recall).last
  5.       end
  6.       def generate_extras(options, recall={})
  7.         generate(options, recall, :generate_extras)
  8.       end
  9.       def generate(options, recall = {}, method=:generate)
  10.         named_route_name = options.delete(:use_route)
  11.         generate_all = options.delete(:generate_all)
  12.         if named_route_name
  13.           named_route = named_routes[named_route_name]
  14.           options = named_route.parameter_shell.merge(options)
  15.         end
  16.         options = options_as_params(options)
  17.         expire_on = build_expiry(options, recall)
  18.         if options[:controller]
  19.           options[:controller] = options[:controller].to_s
  20.         end
  21.         # if the controller has changed, make sure it changes relative to the
  22.         # current controller module, if any. In other words, if we're currently
  23.         # on admin/get, and the new controller is 'set', the new controller
  24.         # should really be admin/set.
  25.         if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
  26.           old_parts = recall[:controller].split('/')
  27.           new_parts = options[:controller].split('/')
  28.           parts = old_parts[0..-(new_parts.length + 1)] + new_parts
  29.           options[:controller] = parts.join('/')
  30.         end
  31.         # drop the leading '/' on the controller name
  32.         options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
  33.         merged = recall.merge(options)
  34.         if named_route
  35.           path = named_route.generate(options, merged, expire_on)
  36.           if path.nil?
  37.             raise_named_route_error(options, named_route, named_route_name)
  38.           else
  39.             return path
  40.           end
  41.         else
  42.           merged[:action] ||= 'index'
  43.           options[:action] ||= 'index'
  44.           controller = merged[:controller]
  45.           action = merged[:action]
  46.           raise RoutingError, "Need controller and action!" unless controller && action
  47.           if generate_all
  48.             # Used by caching to expire all paths for a resource
  49.             return routes.collect do |route|
  50.               route.send!(method, options, merged, expire_on)
  51.             end.compact
  52.           end
  53.           # don't use the recalled keys when determining which routes to check
  54.           routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
  55.           routes.each do |route|
  56.             results = route.send!(method, options, merged, expire_on)
  57.             return results if results && (!results.is_a?(Array) || results.first)
  58.           end
  59.         end
  60.         raise RoutingError, "No route matches #{options.inspect}"
  61.       end
  62.       # try to give a helpful error message when named route generation fails
  63.       def raise_named_route_error(options, named_route, named_route_name)
  64.         diff = named_route.requirements.diff(options)
  65.         unless diff.empty?
  66.           raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
  67.         else
  68.           required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
  69.           required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
  70.           raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route.  content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
  71.         end
  72.       end




  根据request分解params
  1.  def recognize(request)
  2.         params = recognize_path(request.path, extract_request_environment(request))
  3.         request.path_parameters = params.with_indifferent_access
  4.         "#{params[:controller].camelize}Controller".constantize
  5.  end
  6.  # 这个方法被recognition_optimisation重写后面再分析
  7.  def recognize_path(path, environment={})
  8.         raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
  9.  end
  10.  # Subclasses and plugins may override this method to extract further attributes
  11.  # from the request, for use by route conditions and such.
  12.  def extract_request_environment(request)
  13.      :method => request.method }
  14.  end
 根据controller获得routes
  1.       def routes_by_controller
  2.         @routes_by_controller ||= Hash.new do |controller_hash, controller|
  3.           controller_hash[controller] = Hash.new do |action_hash, action|
  4.             action_hash[action] = Hash.new do |key_hash, keys|
  5.               key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
  6.             end
  7.           end
  8.         end
  9.       end
  10.       def routes_for(options, merged, expire_on)
  11.         raise "Need controller and action!" unless controller && action
  12.         controller = merged[:controller]
  13.         merged = options if expire_on[:controller]
  14.         action = merged[:action] || 'index'
  15.         routes_by_controller[controller][action][merged.keys]
  16.       end
  17.       def routes_for_controller_and_action(controller, action)
  18.         selected = routes.select do |route|
  19.           route.matches_controller_and_action? controller, action
  20.         end
  21.         (selected.length == routes.length) ? routes : selected
  22.       end
  23.       def routes_for_controller_and_action_and_keys(controller, action, keys)
  24.         selected = routes.select do |route|
  25.           route.matches_controller_and_action? controller, action
  26.         end
  27.         selected.sort_by do |route|
  28.           (keys - route.significant_keys).length
  29.         end
  30.       end





















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值