原创  Rails源代码分析(33):ActionController::Routing(4) Builder 收藏

1 说明
  这个类用于根据path来创建Route对象

2 代码分析
  核心:
  1.       def build(path, options)
  2.         # Wrap the path with slashes
  3.         path = "/#{path}" unless path[0] == ?/
  4.         path = "#{path}/" unless path[-1] == ?/
  5.         path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix]
  6.         segments = segments_for_route_path(path)
  7.         defaults, requirements, conditions = divide_route_options(segments, options)
  8.         requirements = assign_route_options(segments, defaults, requirements)
  9.         route = Route.new
  10.         route.segments = segments
  11.         route.requirements = requirements
  12.         route.conditions = conditions
  13.         if !route.significant_keys.include?(:action) && !route.requirements[:action]
  14.           route.requirements[:action] = "index"
  15.           route.significant_keys << :action
  16.         end
  17.         # Routes cannot use the current string interpolation method
  18.         # if there are user-supplied <tt>:requirements</tt> as the interpolation
  19.         # code won't raise RoutingErrors when generating
  20.         if options.key?(:requirements) || route.requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
  21.           route.optimise = false
  22.         end
  23.         if !route.significant_keys.include?(:controller)
  24.           raise ArgumentError, "Illegal route: the :controller must be specified!"
  25.         end
  26.         route
  27.       end

  1.       def segments_for_route_path(path)
  2.         rest, segments = path, []
  3.         until rest.empty? # 循环将path分割为segment
  4.           segment, rest = segment_for rest
  5.           segments << segment
  6.         end
  7.         segments
  8.       end
  9.       # Split the given hash of options into requirement and default hashes. The
  10.       # segments are passed alongside in order to distinguish between default values
  11.       # and requirements.
  12.       def divide_route_options(segments, options)
  13.         options = options.dup
  14.         if options[:namespace]
  15.           options[:controller] = "#{options[:path_prefix]}/#{options[:controller]}"
  16.           options.delete(:path_prefix)
  17.           options.delete(:name_prefix)
  18.           options.delete(:namespace)
  19.         end
  20.         requirements = (options.delete(:requirements) || {}).dup
  21.         defaults     = (options.delete(:defaults)     || {}).dup
  22.         conditions   = (options.delete(:conditions)   || {}).dup
  23.         path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
  24.         options.each do |key, value|
  25.           hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
  26.           hash[key] = value
  27.         end
  28.         [defaults, requirements, conditions]
  29.       end
  30.       # Takes a hash of defaults and a hash of requirements, and assigns them to
  31.       # the segments. Any unused requirements (which do not correspond to a segment)
  32.       # are returned as a hash.
  33.       def assign_route_options(segments, defaults, requirements)
  34.         route_requirements = {} # Requirements that do not belong to a segment
  35.         segment_named = Proc.new do |key|
  36.           segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
  37.         end
  38.         requirements.each do |key, requirement|
  39.           segment = segment_named[key]
  40.           if segment
  41.             raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
  42.             if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
  43.               raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
  44.             end
  45.             if multiline_regexp?(requirement)
  46.               raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
  47.             end
  48.             segment.regexp = requirement
  49.           else
  50.             route_requirements[key] = requirement
  51.           end
  52.         end
  53.         defaults.each do |key, default|
  54.           segment = segment_named[key]
  55.           raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
  56.           segment.is_optional = true
  57.           segment.default = default.to_param if default
  58.         end
  59.         assign_default_route_options(segments)
  60.         ensure_required_segments(segments)
  61.         route_requirements
  62.       end
  1.       # A factory method that returns a new segment instance appropriate for the
  2.       # format of the given string.
  3.       def segment_for(string)
  4.         segment = case string
  5.           when /\A:(\w+)/ # match :controller
  6.             key = $1.to_sym
  7.             case key
  8.               when :controller then ControllerSegment.new(key)
  9.               else DynamicSegment.new key
  10.             end
  11.           when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) # match path
  12.           when /\A\?(.*?)\?/ # match static
  13.             returning segment = StaticSegment.new($1do
  14.               segment.is_optional = true
  15.             end
  16.           when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) # match static
  17.           when Regexp.new(separator_pattern) then # match seperator
  18.             returning segment = DividerSegment.new($&) do
  19.               segment.is_optional = (optional_separators.include? $&)
  20.             end
  21.         end
  22.         [segment, $~.post_match]
  23.       end

That returning statement may look odd; it’s a method defined in ActiveSupport that makes it really easy to return a value, but only after performing some operations on it. You’ll find it used all over in Rails, so it’s worth getting familiar with it.)

If you are trying to extend Routes, this is where yourRouteBuilder subclass would extend segment_for to add it’s own custom string processing. As you can see, routing currently supports five different segment types:

  • DynamicSegment. This represents parts of the route that begin with a colon, like :action:permalink or :id.
  • ControllerSegment. This is actually a subclass ofDynamicSegment. It represents to special string :controller, because it does some special recognition on those strings. (We’ll cover that more in the next article).
  • PathSegment. This is for segments that start with an asterisk, and which represent the remainder of the path. Routes like "/file/*path" use a PathSegment.
  • StaticSegment. This is any static text in your route that must be matched (or generated) verbatim. If you have a path like "/one/two", the strings "one" and "two" are both static segments.
  • DividerSegment. This is any segment that is used to delimit the other segments. Generally, this will be the forward slash character, but also includes commas, periods, semicolons, and question marks.








发表于 @ 2008年12月22日 12:52:00 | 评论( loading... ) | 编辑| 举报| 收藏

旧一篇:Rails源代码分析(32):ActionController::Routing(3) RouteSet | 新一篇:Rails源代码分析(34):ActionController::Routing(5) Route

  • 发表评论
  • 评论内容:
  •  
Copyright © jlaky
Powered by CSDN Blog