Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 4394

Show
Ignore:
Timestamp:
06/01/06 15:42:08 (2 years ago)
Author:
minam
Message:

New routes implementation. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r4388 r4394  
    11*SVN* 
     2 
     3* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck] 
     4 
     5  map.connect '/foo/:id', :controller => '...', :action => '...' 
     6  map.connect '/foo/:id.:format', :controller => '...', :action => '...' 
     7  map.connect '/foo/:id', ..., :conditions => { :method => :get } 
    28 
    39* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper] 
     
    3844  All this relies on the fact that you have a route that includes .:format. 
    3945   
    40  
    4146* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH] 
    4247 
  • trunk/actionpack/lib/action_controller/assertions.rb

    r4261 r4394  
    190190          ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?  
    191191       
    192           generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras) 
     192          generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, extras) 
    193193          found_extras = options.reject {|k, v| ! extra_keys.include? k} 
    194194 
     
    366366          request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method 
    367367          request.path   = path 
    368           ActionController::Routing::Routes.recognize!(request) 
     368 
     369          ActionController::Routing::Routes.recognize(request) 
    369370          request 
    370371        end 
  • trunk/actionpack/lib/action_controller/base.rb

    r4347 r4394  
    33require 'action_controller/response' 
    44require 'action_controller/routing' 
    5 require 'action_controller/code_generation' 
    65require 'action_controller/url_rewriter' 
    76require 'drb' 
  • trunk/actionpack/lib/action_controller/routing.rb

    r4381 r4394  
     1require 'cgi' 
     2require 'pathname' 
     3 
     4class Object 
     5  def to_param 
     6    to_s 
     7  end 
     8end 
     9 
     10class TrueClass 
     11  def to_param 
     12    self 
     13  end 
     14end 
     15 
     16class FalseClass 
     17  def to_param 
     18    self 
     19  end 
     20end 
     21 
     22class NilClass 
     23  def to_param 
     24    self 
     25  end 
     26end 
     27 
     28class Regexp 
     29  def number_of_captures 
     30    Regexp.new("|#{source}").match('').captures.length 
     31  end 
     32   
     33  class << self 
     34    def optionalize(pattern) 
     35      case unoptionalize(pattern) 
     36        when /\A(.|\(.*\))\Z/ then "#{pattern}?" 
     37        else "(?:#{pattern})?" 
     38      end 
     39    end 
     40     
     41    def unoptionalize(pattern) 
     42      [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp| 
     43        return $1 if regexp =~ pattern 
     44      end 
     45      return pattern 
     46    end 
     47  end 
     48end 
     49 
    150module ActionController 
    2   module Routing #:nodoc: 
     51  module Routing 
     52    SEPARATORS = %w( / ; . , ? ) 
     53 
    354    class << self 
    4       def expiry_hash(options, recall) 
    5         k = v = nil 
    6         expire_on = {} 
    7         options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))} 
    8         expire_on 
    9       end 
    10  
    11       def extract_parameter_value(parameter) #:nodoc: 
    12         CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)  
    13       end 
     55      def with_controllers(names) 
     56        use_controllers! names 
     57        yield 
     58      ensure 
     59        use_controllers! nil 
     60      end 
     61     
     62      def possible_controllers 
     63        unless @possible_controllers 
     64          @possible_controllers = [] 
     65         
     66          paths = $LOAD_PATH.select { |path| File.directory? path } 
     67          paths.collect! { |path| Pathname.new(path).realpath.to_s } 
     68          paths = paths.sort_by { |path| - path.length } 
     69         
     70          seen_paths = Hash.new {|h, k| h[k] = true; false} 
     71          paths.each do |load_path| 
     72            Dir["#{load_path}/**/*_controller.rb"].collect do |path| 
     73              next if seen_paths[path] 
     74             
     75              controller_name = path[(load_path.length + 1)..-1] 
     76              controller_name.gsub!(/_controller\.rb\Z/, '') 
     77              @possible_controllers << controller_name 
     78            end 
     79          end 
     80        end 
     81        @possible_controllers 
     82      end 
     83 
     84      def use_controllers!(controller_names) 
     85        @possible_controllers = controller_names 
     86      end 
     87 
    1488      def controller_relative_to(controller, previous) 
    1589        if controller.nil?           then previous 
     
    1791        elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}" 
    1892        else controller 
    19         end 
    20       end 
    21  
    22       def treat_hash(hash, keys_to_delete = []) 
    23         k = v = nil 
    24         hash.each do |k, v| 
    25           if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s 
     93        end      
     94      end      
     95    end 
     96   
     97    class Route 
     98      attr_accessor :segments, :requirements, :conditions 
     99   
     100      def initialize 
     101        @segments = [] 
     102        @requirements = {} 
     103        @conditions = {} 
     104      end 
     105   
     106      # Write and compile a +generate+ method for this Route. 
     107      def write_generation 
     108        # Build the main body of the generation 
     109        body = "not_expired = true\n#{generation_extraction}\n#{generation_structure}" 
     110     
     111        # If we have conditions that must be tested first, nest the body inside an if 
     112        body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements 
     113        args = "options, hash, expire_on = {}" 
     114 
     115        # Nest the body inside of a def block, and then compile it. 
     116        method_decl = "def generate_raw(#{args})\path = begin\n#{body}\nend\n[path, hash]\nend" 
     117# puts "\n======================" 
     118# puts 
     119# p self 
     120# puts 
     121# puts method_decl 
     122# puts 
     123        instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" 
     124 
     125        method_decl = "def generate(#{args})\nappend_query_string(*generate_raw(options, hash, expire_on))\nend" 
     126        instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" 
     127 
     128        method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, hash.keys.map(&:to_sym) - significant_keys]\nend" 
     129        instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" 
     130      end 
     131   
     132      # Build several lines of code that extract values from the options hash. If any 
     133      # of the values are missing or rejected then a return will be executed. 
     134      def generation_extraction 
     135        segments.collect do |segment| 
     136          segment.extraction_code 
     137        end.compact * "\n" 
     138      end 
     139   
     140      # Produce a condition expression that will check the requirements of this route 
     141      # upon generation. 
     142      def generation_requirements 
     143        requirement_conditions = requirements.collect do |key, req| 
     144          if req.is_a? Regexp 
     145            value_regexp = Regexp.new "\\A#{req.source}\\Z" 
     146            "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" 
    26147          else 
    27             hash.delete k 
    28             keys_to_delete << k 
    29           end 
    30         end 
    31         hash 
    32       end 
    33        
    34       def test_condition(expression, condition) 
    35         case condition 
    36           when String then "(#{expression} == #{condition.inspect})" 
    37           when Regexp then 
    38             condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source  
    39             "(#{condition.inspect} =~ #{expression})" 
    40           when Array then 
    41             conds = condition.collect do |condition| 
    42               cond = test_condition(expression, condition) 
    43               (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})" 
    44             end 
    45             "(#{conds.join(' || ')})" 
    46           when true then expression 
    47           when nil then "! #{expression}" 
    48           else 
    49             raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil" 
    50         end 
    51       end 
    52     end 
    53  
    54     class Component #:nodoc: 
    55       def dynamic?()  false end 
    56       def optional?() false end 
    57  
    58       def key() nil end 
    59    
    60       def self.new(string, *args) 
    61         return super(string, *args) unless self == Component 
    62         case string 
    63           when /.*;.*/       then SubpathComponent.new(string.split(/;/), *args) 
    64           when ':controller' then ControllerComponent.new(:controller, *args) 
    65           when /^:(\w+)$/    then DynamicComponent.new($1, *args) 
    66           when /^\*(\w+)$/   then PathComponent.new($1, *args) 
    67           else StaticComponent.new(string, *args) 
    68         end 
    69       end  
    70     end 
    71  
    72     class SubpathComponent < Component #:nodoc: 
    73       attr_reader :parts 
    74  
    75       def initialize(parts, *args) 
    76         @parts = parts.map { |part| Component.new(part, *args) } 
    77       end 
    78  
    79       def write_recognition(g) 
    80         raise RoutingError, "Subpath components must occur last" unless g.after.empty? 
    81         g.if("#{g.next_segment(true)} && #{g.next_segment}.include?(';')") do |gp| 
    82           gp.line "subindex, subpath = 0, #{gp.next_segment}.split(/;/)" 
    83           tweak_recognizer(gp).go 
    84           gp.move_forward { |gpp| gpp.continue } 
    85         end 
    86       end 
    87  
    88       def write_generation(g) 
    89         raise RoutingError, "Subpath components must occur last" unless g.after.empty? 
    90         tweak_generator(g).go 
    91       end 
    92  
    93       def key 
    94         parts.map { |p| p.key } 
    95       end 
    96  
    97       private 
    98  
    99         def tweak_recognizer(g) 
    100           gg = g.dup 
    101  
    102           gg.path_name = :subpath 
    103           gg.base_segment_name = :subsegment 
    104           gg.base_index_name = :subindex 
    105           gg.depth = 0 
    106  
    107           gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || []) 
    108  
    109           gg 
    110         end 
    111  
    112         def tweak_generator(g) 
    113           gg = g.dup 
    114           gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || []) 
    115           gg.start_subpath! 
    116           gg 
    117         end 
    118     end 
    119  
    120     class StaticComponent < Component #:nodoc: 
    121       attr_reader :value 
    122    
    123       def initialize(value) 
    124         @value = value 
    125       end 
    126  
    127       def write_recognition(g) 
    128         g.if_next_matches(value) do |gp| 
    129           gp.move_forward {|gpp| gpp.continue} 
    130         end 
    131       end 
    132  
    133       def write_generation(g) 
    134         g.add_segment(value) {|gp| gp.continue } 
    135       end 
    136     end 
    137  
    138     class DynamicComponent < Component #:nodoc: 
    139       attr_reader :key, :default 
    140       attr_accessor :condition 
    141    
    142       def dynamic?()  true      end 
    143       def optional?() @optional end 
    144  
    145       def default=(default) 
    146         @optional = true 
    147         @default = default 
    148       end 
    149      
    150       def initialize(key, options = {}) 
    151         @key = key.to_sym 
    152         @optional = false 
    153         default, @condition = options[:default], options[:condition] 
    154         self.default = default if options.key?(:default) 
    155       end 
    156  
    157       def default_check(g) 
    158         presence = "#{g.hash_value(key, !! default)}" 
    159         if default 
    160            "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})" 
     148            "hash[:#{key}] == #{req.inspect}" 
     149          end 
     150        end 
     151        requirement_conditions * ' && ' unless requirement_conditions.empty? 
     152      end 
     153      def generation_structure 
     154        segments.last.string_structure segments[0..-2] 
     155      end 
     156   
     157      # Write and compile a +recognize+ method for this Route. 
     158      def write_recognition 
     159        # Create an if structure to extract the params from a match if it occurs. 
     160        body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" 
     161        body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" 
     162     
     163        # Build the method declaration and compile it 
     164        method_decl = "def recognize(path, env={})\n#{body}\nend" 
     165# puts "\n======================" 
     166# puts 
     167# p self 
     168# puts 
     169# puts method_decl 
     170# puts 
     171        instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" 
     172        method_decl 
     173      end 
     174 
     175      # Plugins may override this method to add other conditions, like checks on 
     176      # host, subdomain, and so forth. Note that changes here only affect route 
     177      # recognition, not generation. 
     178      def recognition_conditions 
     179        result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] 
     180        result << "conditions[:method] === env[:method]" if conditions[:method] 
     181        result 
     182      end 
     183 
     184      # Build the regular expression pattern that will match this route. 
     185      def recognition_pattern(wrap = true) 
     186        pattern = '' 
     187        segments.reverse_each do |segment| 
     188          pattern = segment.build_pattern pattern 
     189        end 
     190        wrap ? ("\\A" + pattern + "\\Z") : pattern 
     191      end 
     192   
     193      # Write the code to extract the parameters from a matched route. 
     194      def recognition_extraction 
     195        next_capture = 1 
     196        extraction = segments.collect do |segment| 
     197          x = segment.match_extraction next_capture 
     198          next_capture += Regexp.new(segment.regexp_chunk).number_of_captures 
     199          x 
     200        end 
     201        extraction.compact 
     202      end 
     203   
     204      # Write the real generation implementation and then resend the message. 
     205      def generate(options, hash, expire_on = {}) 
     206        write_generation 
     207        generate options, hash, expire_on 
     208      end 
     209 
     210      def generate_extras(options, hash, expire_on = {}) 
     211        write_generation 
     212        generate_extras options, hash, expire_on 
     213      end 
     214 
     215      # Generate the query string with any extra keys in the hash and append 
     216      # it to the given path, returning the new path. 
     217      def append_query_string(path, hash) 
     218        return nil unless path 
     219        query = hash.keys.map(&:to_sym) - significant_keys 
     220        "#{path}#{build_query_string(hash, query)}" 
     221      end 
     222 
     223      # Build a query string from the keys of the given hash. If +only_keys+ 
     224      # is given (as an array), only the keys indicated will be used to build 
     225      # the query string. The query string will correctly build array parameter 
     226      # values. 
     227      def build_query_string(hash, only_keys=nil) 
     228        elements = [] 
     229 
     230        only_keys ||= hash.keys 
     231         
     232        only_keys.each do |key| 
     233          value = hash[key]  
     234          key = CGI.escape key.to_s 
     235          if value.class == Array 
     236            key <<  '[]' 
     237          else     
     238            value = [ value ]  
     239          end      
     240          value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" } 
     241        end      
     242         
     243        query_string = "?#{elements.join("&")}" unless elements.empty? 
     244        query_string || "" 
     245      end 
     246   
     247      # Write the real recognition implementation and then resend the message. 
     248      def recognize(path, environment={}) 
     249        write_recognition 
     250        recognize path, environment 
     251      end 
     252   
     253      # A route's parameter shell contains parameter values that are not in the 
     254      # route's path, but should be placed in the recognized hash. 
     255      #  
     256      # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route: 
     257      #  
     258      #   map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ 
     259      #  
     260      def parameter_shell 
     261        @parameter_shell ||= returning({}) do |shell| 
     262          requirements.each do |key, requirement| 
     263            shell[key] = requirement unless requirement.is_a? Regexp 
     264          end 
     265        end 
     266      end 
     267   
     268      # Return an array containing all the keys that are used in this route. This 
     269      # includes keys that appear inside the path, and keys that have requirements 
     270      # placed upon them. 
     271      def significant_keys 
     272        @significant_keys ||= returning [] do |sk| 
     273          segments.each { |segment| sk << segment.key if segment.respond_to? :key } 
     274          sk.concat requirements.keys 
     275          sk.uniq! 
     276        end 
     277      end 
     278 
     279      # Return a hash of key/value pairs representing the keys in the route that 
     280      # have defaults, or which are specified by non-regexp requirements. 
     281      def defaults 
     282        @defaults ||= returning({}) do |hash| 
     283          segments.each do |segment| 
     284            next unless segment.respond_to? :default 
     285            hash[segment.key] = segment.default unless segment.default.nil? 
     286          end 
     287          requirements.each do |key,req| 
     288            next if Regexp === req || req.nil? 
     289            hash[key] = req 
     290          end 
     291        end 
     292      end 
     293   
     294      def matches_controller_and_action?(controller, action) 
     295        unless @matching_prepared 
     296          @controller_requirement = requirement_for(:controller) 
     297          @action_requirement = requirement_for(:action) 
     298          @matching_prepared = true 
     299        end 
     300 
     301        (@controller_requirement.nil? || @controller_requirement === controller) && 
     302        (@action_requirement.nil? || @action_requirement === action) 
     303      end 
     304 
     305      def to_s 
     306        @to_s ||= segments.inject("") { |str,s| str << s.to_s } 
     307      end 
     308   
     309    protected 
     310   
     311      def requirement_for(key) 
     312        return requirements[key] if requirements.key? key 
     313        segments.each do |segment| 
     314          return segment.regexp if segment.respond_to?(:key) && segment.key == key 
     315        end 
     316        nil 
     317      end 
     318   
     319    end 
     320 
     321    class Segment 
     322      attr_accessor :is_optional 
     323      alias_method :optional?, :is_optional 
     324 
     325      def initialize 
     326        self.is_optional = false 
     327      end 
     328 
     329      def extraction_code 
     330        nil 
     331      end 
     332   
     333      # Continue generating string for the prior segments. 
     334      def continue_string_structure(prior_segments) 
     335        if prior_segments.empty? 
     336          interpolation_statement(prior_segments) 
    161337        else 
    162           "! #{presence}" 
    163         end 
    164       end 
    165    
    166       def write_generation(g) 
    167         wrote_dropout = write_dropout_generation(g) 
    168         write_continue_generation(g, wrote_dropout) 
    169       end 
    170  
    171       def write_dropout_generation(g) 
    172         return false unless optional? && g.after.all? {|c| c.optional?} 
    173      
    174         check = [default_check(g)] 
    175         gp = g.dup # Use another generator to write the conditions after the first && 
    176         # We do this to ensure that the generator will not assume x_value is set. It will 
    177         # not be set if it follows a false condition -- for example, false && (x = 2) 
    178          
    179         check += gp.after.map {|c| c.default_check gp} 
    180         gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here 
    181         true  
    182       end 
    183  
    184       def write_continue_generation(g, use_else) 
    185         test  = Routing.test_condition(g.hash_value(key, true, default), condition || true) 
    186         check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test] 
    187      
    188         g.send(*check) do |gp| 
    189           gp.expire_for_keys(key) unless gp.after.empty? 
    190           add_segments_to(gp) {|gpp| gpp.continue} 
    191         end 
    192       end 
    193  
    194       def add_segments_to(g) 
    195         g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp} 
    196       end 
    197    
    198       def recognition_check(g) 
    199         test_type = [true, nil].include?(condition) ? :presence : :constraint 
    200      
    201         prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : '' 
    202         check = prefix + Routing.test_condition(g.next_segment(true), condition || true) 
    203      
    204         g.if(check) {|gp| yield gp, test_type} 
    205       end 
    206    
    207       def write_recognition(g) 
    208         test_type = nil 
    209         recognition_check(g) do |gp, test_type| 
    210           assign_result(gp) {|gpp| gpp.continue} 
    211         end 
    212      
    213         if optional? && g.after.all? {|c| c.optional?} 
    214           call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"] 
    215         
    216           g.send(*call) do |gp| 
    217             assign_default(gp) 
    218             gp.after.each {|c| c.assign_default(gp)} 
    219             gp.finish(false) 
    220           end 
    221         end 
    222       end 
    223  
    224       def assign_result(g, with_default = false) 
    225         g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})" 
    226         g.move_forward {|gp| yield gp} 
    227       end 
    228  
    229       def assign_default(g) 
    230         g.constant_result key, default unless default.nil? 
    231       end 
    232     end 
    233  
    234     class ControllerComponent < DynamicComponent #:nodoc: 
    235       def key() :controller end 
    236  
    237       def add_segments_to(g) 
    238         g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp} 
    239       end 
    240      
    241       def recognition_check(g) 
    242         g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"  
    243         g.if('controller_result') do |gp| 
    244           gp << 'controller_value, segments_to_controller = controller_result' 
    245           if condition 
    246             gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')" 
    247             gp.if(Routing.test_condition("controller_path", condition)) do |gpp| 
    248               gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint} 
    249             end 
    250           else 
    251             gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint} 
    252           end 
    253         end 
    254       end 
    255  
    256       def assign_result(g) 
    257         g.result key, 'controller_value' 
    258         yield g 
    259       end 
    260  
    261       def assign_default(g) 
    262         ControllerComponent.assign_controller(g, default) 
    263       end 
    264    
    265       class << self 
    266         def assign_controller(g, controller) 
    267           expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller" 
    268           g.result :controller, expr, true 
    269         end 
    270  
    271         def traverse_to_controller(segments, start_at = 0) 
    272           mod = ::Object 
    273           length = segments.length 
    274           index = start_at 
    275           mod_name = controller_name = segment = nil 
    276            
    277           while index < length 
    278             return nil unless /^[A-Za-z][A-Za-z\d_]*$/ =~ (segment = segments[index]) 
    279             index += 1 
    280              
    281             mod_name = segment.camelize 
    282             controller_name = "#{mod_name}Controller" 
    283              
    284             begin 
    285               # We use eval instead of const_get to avoid obtaining values from parent modules. 
    286               controller = eval("mod::#{controller_name}", nil, __FILE__, __LINE__) 
    287               expected_name = "#{mod.name}::#{controller_name}" 
    288                
    289               # Detect the case when const_get returns an object from a parent namespace. 
    290               if controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) && (mod == Object || controller.name == expected_name) 
    291                 return controller, (index - start_at) 
    292               end 
    293             rescue NameError => e 
    294               raise unless /^uninitialized constant .*#{controller_name}$/ =~ e.message                             
    295             end 
    296              
    297             begin 
    298               next_mod = eval("mod::#{mod_name}", nil, __FILE__, __LINE__) 
    299               # Check that we didn't get a module from a parent namespace 
    300               mod = (mod == Object || next_mod.name == "#{mod.name}::#{mod_name}") ? next_mod : nil 
    301             rescue NameError => e 
    302               raise unless /^uninitialized constant .*#{mod_name}$/ =~ e.message 
    303             end 
    304              
    305             return nil unless mod 
    306           end 
    307         end 
    308       end 
    309     end 
    310  
    311     class PathComponent < DynamicComponent #:nodoc: 
    312       def optional?() true end 
    313       def default()   []  end 
    314       def condition() nil  end 
    315  
    316       def default=(value) 
    317         raise RoutingError, "All path components have an implicit default of []" unless value == [] 
    318       end 
    319    
    320       def write_generation(g) 
    321         raise RoutingError, 'Path components must occur last' unless g.after.empty? 
    322         g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do 
    323           g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)" 
    324           g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish } 
    325         end 
    326         g.else { g.finish } 
    327       end 
    328    
    329       def write_recognition(g) 
    330         raise RoutingError, "Path components must occur last" unless g.after.empty? 
    331      
    332         start = g.index_name.to_s 
    333         start = "(#{start})" unless /^\w+$/ =~ start.to_s 
    334      
    335         value_expr = "#{g.path_name}[#{start}..-1] || []" 
    336         g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})" 
    337         g.finish(false) 
    338       end 
    339    
     338          new_priors = prior_segments[0..-2] 
     339          prior_segments.last.string_structure(new_priors) 
     340        end 
     341      end 
     342   
     343      # Return a string interpolation statement for this segment and those before it. 
     344      def interpolation_statement(prior_segments) 
     345        chunks = prior_segments.collect { |s| s.interpolation_chunk } 
     346        chunks << interpolation_chunk 
     347        "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}" 
     348      end 
     349   
     350      def string_structure(prior_segments) 
     351        optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments) 
     352      end 
     353   
     354      # Return an if condition that is true if all the prior segments can be generated. 
     355      # If there are no optional segments before this one, then nil is returned. 
     356      def all_optionals_available_condition(prior_segments) 
     357        optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact 
     358        optional_locals.empty? ? nil : " if #{optional_locals * ' && '}" 
     359      end 
     360   
     361      # Recognition 
     362   
     363      def match_extraction(next_capture) 
     364        nil 
     365      end 
     366   
     367      # Warning 
     368   
     369      # Returns true if this segment is optional? because of a default. If so, then 
     370      # no warning will be emitted regarding this segment. 
     371      def optionality_implied? 
     372        false 
     373      end 
     374   
     375    end 
     376 
     377    class StaticSegment < Segment 
     378      attr_accessor :value, :raw 
     379      alias_method :raw?, :raw 
     380   
     381      def initialize(value = nil) 
     382        super() 
     383        self.value = value 
     384      end 
     385   
     386      def interpolation_chunk 
     387        raw? ? value : CGI.escape(value) 
     388      end 
     389   
     390      def regexp_chunk 
     391        chunk = Regexp.escape value 
     392        optional? ? Regexp.optionalize(chunk) : chunk 
     393      end 
     394   
     395      def build_pattern(pattern) 
     396        escaped = Regexp.escape(value) 
     397        if optional? && ! pattern.empty? 
     398          "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" 
     399        elsif optional? 
     400          Regexp.optionalize escaped 
     401        else 
     402          escaped + pattern 
     403        end 
     404      end 
     405   
     406      def to_s 
     407        value 
     408      end 
     409   
     410    end 
     411 
     412    class DividerSegment < StaticSegment 
     413   
     414      def initialize(value = nil) 
     415        super(value) 
     416        self.raw = true 
     417        self.is_optional = true 
     418      end 
     419   
     420      def optionality_implied? 
     421        true 
     422      end 
     423   
     424    end 
     425 
     426    class DynamicSegment < Segment 
     427      attr_accessor :key, :default, :regexp 
     428   
     429      def initialize(key = nil, options = {}) 
     430        super() 
     431        self.key = key 
     432        self.default = options[:default] if options.key? :default 
     433        self.is_optional = true if options[:optional] || options.key?(:default) 
     434      end 
     435   
     436      def to_s 
     437        ":#{key}" 
     438      end 
     439   
     440      # The local variable name that the value of this segment will be extracted to. 
     441      def local_name 
     442        "#{key}_value" 
     443      end 
     444   
     445      def extract_value 
     446        "#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}" 
     447      end 
     448      def value_check 
     449        if default # Then we know it won't be nil 
     450          "#{value_regexp.inspect} =~ #{local_name}" if regexp 
     451        elsif optional? 
     452          # If we have a regexp check that the value is not given, or that it matches. 
     453          # If we have no regexp, return nil since we do not require a condition. 
     454          "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp 
     455        else # Then it must be present, and if we have a regexp, it must match too. 
     456          "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" 
     457        end 
     458      end 
     459      def expiry_statement 
     460        "not_expired, hash = false, options if not_expired && expire_on[:#{key}]" 
     461      end 
     462   
     463      def extraction_code 
     464        s = extract_value 
     465        vc = value_check 
     466        s << "\nreturn [nil,nil] unless #{vc}" if vc 
     467        s << "\n#{expiry_statement}" 
     468      end 
     469   
     470      def interpolation_chunk 
     471        "\#{CGI.escape(#{local_name}.to_s)}" 
     472      end 
     473   
     474      def string_structure(prior_segments) 
     475        if optional? # We have a conditional to do... 
     476          # If we should not appear in the url, just write the code for the prior 
     477          # segments. This occurs if our value is the default value, or, if we are 
     478          # optional, if we have nil as our value. 
     479          "if #{local_name} == #{default.inspect}\n" +  
     480            continue_string_structure(prior_segments) +  
     481          "\nelse\n" + # Otherwise, write the code up to here 
     482            "#{interpolation_statement(prior_segments)}\nend" 
     483        else 
     484          interpolation_statement(prior_segments) 
     485        end 
     486      end 
     487   
     488      def value_regexp 
     489        Regexp.new "\\A#{regexp.source}\\Z" if regexp 
     490      end 
     491      def regexp_chunk 
     492        regexp ? regexp.source : "([^#{Routing::SEPARATORS.join}]+)" 
     493      end 
     494   
     495      def build_pattern(pattern) 
     496        chunk = regexp_chunk 
     497        chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0 
     498        pattern = "#{chunk}#{pattern}" 
     499        optional? ? Regexp.optionalize(pattern) : pattern 
     500      end 
     501      def match_extraction(next_capture) 
     502        hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]") 
     503        "params[:#{key}] = match[#{next_capture}] #{hangon}" 
     504      end 
     505   
     506      def optionality_implied? 
     507        [:action, :id].include? key 
     508      end 
     509   
     510    end 
     511 
     512    class ControllerSegment < DynamicSegment 
     513      def regexp_chunk 
     514        possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } 
     515        "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" 
     516      end 
     517 
     518      # Don't CGI.escape the controller name, since it may have slashes in it, 
     519      # like admin/foo. 
     520      def interpolation_chunk 
     521        "\#{#{local_name}.to_s}" 
     522      end 
     523 
     524      # Make sure controller names like Admin/Content are correctly normalized to 
     525      # admin/content 
     526      def extract_value 
     527        "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase" 
     528      end 
     529 
     530      def match_extraction(next_capture) 
     531        hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]") 
     532        "params[:#{key}] = match[#{next_capture}].downcase #{hangon}" 
     533      end 
     534    end 
     535 
     536    class PathSegment < DynamicSegment 
     537      EscapedSlash = CGI.escape("/") 
     538      def interpolation_chunk 
     539        "\#{CGI.escape(#{local_name}).gsub(#{EscapedSlash.inspect}, '/')}" 
     540      end 
     541 
     542      def default 
     543        '' 
     544      end 
     545 
     546      def default=(path) 
     547        raise RoutingError, "paths cannot have non-empty default values" unless path.blank? 
     548      end 
     549 
     550      def match_extraction(next_capture) 
     551        "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" 
     552      end 
     553 
     554      def regexp_chunk 
     555        regexp || "(.*)" 
     556      end 
     557 
    340558      class Result < ::Array #:nodoc: 
    341         def to_s() join '/' end 
     559        def to_s() join '/' end  
    342560        def self.new_escaped(strings) 
    343561          new strings.collect {|str| CGI.unescape str} 
    344         end 
    345       end 
    346     end 
    347  
    348     class Route #:nodoc: 
    349       attr_accessor :components, :known 
    350       attr_reader :path, :options, :keys, :defaults 
    351    
    352       def initialize(path, options = {}) 
    353         @path, @options = path, options 
    354      
    355         initialize_components path 
    356         defaults, conditions = initialize_hashes options.dup 
    357         @defaults = defaults.dup 
    358         @request_method = conditions.delete(:method) 
    359         configure_components(defaults, conditions) 
    360         add_default_requirements 
    361         initialize_keys 
    362       end 
    363    
    364       def inspect 
    365         "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>" 
    366       end 
    367    
    368       def write_generation(generator = CodeGeneration::GenerationGenerator.new) 
    369         generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || []) 
    370  
    371         if known.empty? then generator.go 
    372         else 
    373           # Alter the conditions to allow :action => 'index' to also catch :action => nil 
    374           altered_known = known.collect do |k, v| 
    375             if k == :action && v== 'index' then [k, [nil, 'index']] 
    376             else [k, v] 
     562        end      
     563      end      
     564    end 
     565 
     566    class RouteBuilder 
     567      attr_accessor :separators, :optional_separators 
     568   
     569      def initialize 
     570        self.separators = Routing::SEPARATORS 
     571        self.optional_separators = %w( / ) 
     572      end 
     573   
     574      def separator_pattern(inverted = false) 
     575        "[#{'^' if inverted}#{Regexp.escape(separators.join)}]" 
     576      end 
     577   
     578      def interval_regexp 
     579        Regexp.new "(.*?)(#{separators.source}|$)" 
     580      end 
     581   
     582      # Accepts a "route path" (a string defining a route), and returns the array 
     583      # of segments that corresponds to it. Note that the segment array is only 
     584      # partially initialized--the defaults and requirements, for instance, need 
     585      # to be set separately, via the #assign_route_options method, and the 
     586      # #optional? method for each segment will not be reliable until after 
     587      # #assign_route_options is called, as well. 
     588      def segments_for_route_path(path) 
     589        rest, segments = path, [] 
     590     
     591        until rest.empty? 
     592          segment, rest = segment_for rest 
     593          segments << segment 
     594        end 
     595        segments 
     596      end 
     597 
     598      # A factory method that returns a new segment instance appropriate for the 
     599      # format of the given string. 
     600      def segment_for(string) 
     601        segment = case string 
     602          when /\A:(\w+)/ 
     603            key = $1.to_sym 
     604            case key 
     605              when :action then DynamicSegment.new(key, :default => 'index') 
     606              when :id then DynamicSegment.new(key, :optional => true) 
     607              when :controller then ControllerSegment.new(key) 
     608              else DynamicSegment.new key 
    377609            end 
    378           end 
    379           generator.if(generator.check_conditions(altered_known)) {|gp| gp.go } 
    380         end 
    381          
    382         generator 
    383       end 
    384    
    385       def write_recognition(generator = CodeGeneration::RecognitionGenerator.new) 
    386         g = generator.dup 
    387         g.share_locals_with generator 
    388         g.before, g.current, g.after = [], components.first, (components[1..-1] || []) 
    389  
    390         known.each do |key, value| 
    391           if key == :controller then ControllerComponent.assign_controller(g, value) 
    392           else g.constant_result(key, value) 
    393           end&