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

Changeset 4394

Show
Ignore:
Timestamp:
06/01/06 15:42:08 (3 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 
    394         end 
    395  
    396         if @request_method 
    397           g.if("@request.method == :#{@request_method}") { |gp| gp.go } 
    398         else 
    399           g.go 
    400         end 
    401      
    402         generator 
    403       end 
    404  
    405       def initialize_keys 
    406         @keys = (components.collect {|c| c.key} + known.keys).flatten.compact 
    407         @keys.freeze 
    408       end 
    409    
    410       def extra_keys(options) 
    411         options.keys - @keys 
    412       end 
    413      
    414       def matches_controller?(controller) 
    415         if known[:controller] then known[:controller] == controller 
    416         else 
    417           c = components.find {|c| c.key == :controller} 
    418           return false unless c 
    419           return c.condition.nil? || eval(Routing.test_condition('controller', c.condition)) 
    420         end 
    421       end 
    422    
    423       protected 
    424         def initialize_components(path) 
    425           path = path.split('/') if path.is_a? String 
    426           path.shift if path.first.blank? 
    427           self.components = path.collect {|str| Component.new str} 
    428         end 
    429      
    430         def initialize_hashes(options) 
    431           path_keys = components.collect {|c| c.key }.flatten.compact 
    432           self.known = {} 
    433           defaults = options.delete(:defaults) || {} 
    434           conditions = options.delete(:require) || {} 
    435           conditions.update(options.delete(:requirements) || {}) 
    436  
    437           options.each do |k, v| 
    438             if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v 
    439             else known[k] = v 
     610          when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true) 
     611          when /\A\?(.*?)\?/ 
     612            returning segment = StaticSegment.new($1) do 
     613              segment.is_optional = true 
    440614            end 
    441           end 
    442           [defaults, conditions] 
    443         end 
    444      
    445         def configure_components(defaults, conditions) 
    446           all_components = components.map { |c| SubpathComponent === c ? c.parts : c }.flatten 
    447           all_components.each do |component| 
    448             if defaults.key?(component.key) then component.default = defaults[component.key] 
    449             elsif component.key == :action  then component.default = 'index' 
    450             elsif component.key == :id      then component.default = nil 
     615          when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1) 
     616          when Regexp.new(separator_pattern) then 
     617            returning segment = DividerSegment.new($&) do 
     618              segment.is_optional = (optional_separators.include? $&) 
    451619            end 
    452          
    453             component.condition = conditions[component.key] if conditions.key?(component.key) 
    454           end 
    455         end 
    456          
    457         def add_default_requirements 
    458           component_keys = components.collect {|c| c.key}.flatten 
    459           known[:action] ||= 'index' unless component_keys.include? :action 
    460         end 
    461     end 
    462  
    463     class RouteSet #:nodoc: 
    464       attr_reader :routes, :categories, :controller_to_selector 
    465       def initialize 
    466         @routes = [] 
    467         @generation_methods = Hash.new(:generate_default_path) 
    468       end 
    469        
    470       def generate(options, request_or_recall_hash = {}) 
    471         recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters 
    472         use_recall = true 
    473          
    474         controller = options[:controller] 
    475         options[:action] ||= 'index' if controller 
    476         recall_controller = recall[:controller] 
    477         if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/))  
    478           recall = {} if controller && controller[0] == ?/ 
    479           options[:controller] = Routing.controller_relative_to(controller, recall_controller) 
    480         end 
    481         options = recall.dup if options.empty? # XXX move to url_rewriter? 
    482          
    483         keys_to_delete = [] 
    484         Routing.treat_hash(options, keys_to_delete) 
    485          
    486         merged = recall.merge(options) 
    487         keys_to_delete.each {|key| merged.delete key} 
    488         expire_on = Routing.expiry_hash(options, recall) 
    489      
    490         generate_path(merged, options, expire_on) 
    491       end 
    492        
    493       def generate_path(merged, options, expire_on) 
    494         send @generation_methods[merged[:controller]], merged, options, expire_on 
    495       end 
    496       def generate_default_path(*args) 
    497         write_generation 
    498         generate_default_path(*args) 
    499       end 
    500    
    501       def write_generation 
    502         method_sources = [] 
    503         @generation_methods = Hash.new(:generate_default_path) 
    504         categorize_routes.each do |controller, routes| 
    505           next unless routes.length < @routes.length 
    506        
    507           ivar = controller.gsub('/', '__') 
    508           method_name = "generate_path_for_#{ivar}".to_sym 
    509           instance_variable_set "@#{ivar}", routes 
    510           code = generation_code_for(ivar, method_name).to_s 
    511           method_sources << code 
    512            
    513           filename = "generated_code/routing/generation_for_controller_#{controller}.rb" 
    514           eval(code, nil, filename) 
    515        
    516           @generation_methods[controller.to_s]   = method_name 
    517           @generation_methods[controller.to_sym] = method_name 
    518         end 
    519          
    520         code = generation_code_for('routes', 'generate_default_path').to_s 
    521         eval(code, nil, 'generated_code/routing/generation.rb') 
    522          
    523         return (method_sources << code) 
    524       end 
    525  
    526       def recognize(request) 
    527         @request = request 
    528  
    529         string_path = @request.path 
    530         string_path.chomp! if string_path[0] == ?/   
    531         path = string_path.split '/'   
    532         path.shift   
    533     
    534         hash = recognize_path(path)   
    535         return recognition_failed(@request) unless hash && hash['controller']   
    536     
    537         controller = hash['controller']   
    538         hash['controller'] = controller.controller_path   
    539         @request.path_parameters = hash   
    540         controller.new  
    541       end 
    542       alias :recognize! :recognize 
    543    
    544       def recognition_failed(request) 
    545         raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}" 
    546       end 
    547  
    548       def write_recognition 
    549         g = generator = CodeGeneration::RecognitionGenerator.new 
    550         g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"} 
    551      
    552         g.def "self.recognize_path(path)" do 
    553           each do |route| 
    554             g << 'index = 0' 
    555             route.write_recognition(g) 
    556           end 
    557         end 
    558  
    559         eval g.to_s, nil, 'generated/routing/recognition.rb' 
    560         return g.to_s 
    561       end 
    562          
    563       def generation_code_for(ivar = 'routes', method_name = nil) 
    564         routes = instance_variable_get('@' + ivar) 
    565         key_ivar = "@keys_for_#{ivar}" 
    566         instance_variable_set(key_ivar, routes.collect {|route| route.keys}) 
    567      
    568         g = generator = CodeGeneration::GenerationGenerator.new 
    569         g.def "self.#{method_name}(merged, options, expire_on)" do 
    570           g << 'unused_count = options.length + 1' 
    571           g << "unused_keys = keys = options.keys" 
    572           g << 'path = nil' 
    573        
    574           routes.each_with_index do |route, index| 
    575             g << "new_unused_keys = keys - #{key_ivar}[#{index}]" 
    576             g << 'new_path = (' 
    577             g.source.indent do 
    578               if index.zero? 
    579                 g << "new_unused_count = new_unused_keys.length" 
    580                 g << "hash = merged; not_expired = true" 
    581                 route.write_generation(g.dup) 
     620        end 
     621        [segment, $~.post_match] 
     622      end 
     623   
     624      # Split the given hash of options into requirement and default hashes. The 
     625      # segments are passed alongside in order to distinguish between default values 
     626      # and requirements. 
     627      def divide_route_options(segments, options) 
     628        requirements = options.delete(:requirements) || {} 
     629        defaults     = options.delete(:defaults)     || {} 
     630        conditions   = options.delete(:conditions)   || {} 
     631 
     632        path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact 
     633        options.each do |key, value| 
     634          hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements 
     635          hash[key] = value 
     636        end 
     637     
     638        [defaults, requirements, conditions] 
     639      end 
     640   
     641      # Takes a hash of defaults and a hash of requirements, and assigns them to 
     642      # the segments. Any unused requirements (which do not correspond to a segment) 
     643      # are returned as a hash. 
     644      def assign_route_options(segments, defaults, requirements) 
     645        route_requirements = {} # Requirements that do not belong to a segment 
     646     
     647        segment_named = Proc.new do |key| 
     648          segments.detect { |segment| segment.key == key if segment.respond_to?(:key) } 
     649        end 
     650     
     651        requirements.each do |key, requirement| 
     652          segment = segment_named[key] 
     653          if segment 
     654            raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp) 
     655            segment.regexp = requirement 
     656          else 
     657            route_requirements[key] = requirement 
     658          end 
     659        end 
     660     
     661        defaults.each do |key, default| 
     662          segment = segment_named[key] 
     663          raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment 
     664          segment.is_optional = true 
     665          segment.default = default.to_param if default 
     666        end 
     667 
     668        ensure_required_segments(segments) 
     669        route_requirements 
     670      end 
     671   
     672      # Makes sure that there are no optional segments that precede a required 
     673      # segment. If any are found that precede a required segment, they are 
     674      # made required. 
     675      def ensure_required_segments(segments) 
     676        allow_optional = true 
     677        segments.reverse_each do |segment| 
     678          allow_optional &&= segment.optional? 
     679          if !allow_optional && segment.optional? 
     680            unless segment.optionality_implied? 
     681              warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required." 
     682            end 
     683            segment.is_optional = false 
     684          elsif allow_optional & segment.respond_to?(:default) && segment.default 
     685            # if a segment has a default, then it is optional 
     686            segment.is_optional = true 
     687          end 
     688        end 
     689      end 
     690 
     691      # Construct and return a route with the given path and options. 
     692      def build(path, options) 
     693        # Wrap the path with slashes 
     694        path = "/#{path}" unless path[0] == ?/ 
     695        path = "#{path}/" unless path[-1] == ?/ 
     696     
     697        segments = segments_for_route_path(path) 
     698        defaults, requirements, conditions = divide_route_options(segments, options) 
     699        requirements = assign_route_options(segments, defaults, requirements) 
     700 
     701        route = Route.new 
     702        route.segments = segments 
     703        route.requirements = requirements 
     704        route.conditions = conditions 
     705 
     706        if !route.significant_keys.include?(:action) && !route.requirements[:action] 
     707          route.requirements[:action] = "index" 
     708          route.significant_keys << :action 
     709        end 
     710 
     711        route 
     712      end 
     713    end 
     714 
     715    class RouteSet 
     716   
     717      # Mapper instances are used to build routes. The object passed to the draw 
     718      # block in config/routes.rb is a Mapper instance. 
     719      #  
     720      # Mapper instances have relatively few instance methods, in order to avoid 
     721      # clashes with named routes. 
     722      class Mapper 
     723        def initialize(set) 
     724          @set = set 
     725        end 
     726     
     727        # Create an unnamed route with the provided +path+ and +options+. See  
     728        # SomeHelpfulUrl for an introduction to routes. 
     729        def connect(path, options = {}) 
     730          @set.add_route(path, options) 
     731        end 
     732     
     733        def method_missing(route_name, *args, &proc) 
     734          super unless args.length >= 1 && proc.nil? 
     735          @set.add_named_route(route_name, *args) 
     736        end 
     737      end 
     738 
     739      # A NamedRouteCollection instance is a collection of named routes, and also 
     740      # maintains an anonymous module that can be used to install helpers for the 
     741      # named routes. 
     742      class NamedRouteCollection 
     743        include Enumerable 
     744 
     745        attr_reader :routes, :helpers 
     746 
     747        def initialize 
     748          clear! 
     749        end 
     750 
     751        def clear! 
     752          @routes = {} 
     753          @helpers = [] 
     754          @module = Module.new 
     755        end 
     756 
     757        def add(name, route) 
     758          routes[name.to_sym] = route 
     759          define_hash_access_method(name, route) 
     760          define_url_helper_method(name, route) 
     761        end 
     762 
     763        def get(name) 
     764          routes[name.to_sym] 
     765        end 
     766 
     767        alias []=   add 
     768        alias []    get 
     769        alias clear clear! 
     770 
     771        def each 
     772          routes.each { |name, route| yield name, route } 
     773          self 
     774        end 
     775 
     776        def names 
     777          routes.keys 
     778        end 
     779 
     780        def length 
     781          routes.length 
     782        end 
     783 
     784        def install(dest = ActionController::Base) 
     785          dest.send :include, @module 
     786          if dest.respond_to? :helper_method 
     787            helpers.each { |name| dest.send :helper_method, name } 
     788          end 
     789        end 
     790 
     791        private 
     792 
     793          def url_helper_name(name) 
     794            :"#{name}_url" 
     795          end 
     796 
     797          def hash_access_name(name) 
     798            :"hash_for_#{name}_url" 
     799          end 
     800 
     801          def define_hash_access_method(name, route) 
     802            method_name = hash_access_name(name) 
     803 
     804            @module.send(:define_method, method_name) do |*args| 
     805              hash = route.defaults.merge(:use_route => name) 
     806              args.first ? hash.merge(args.first) : hash 
     807            end 
     808 
     809            @module.send(:protected, method_name) 
     810            helpers << method_name 
     811          end 
     812 
     813          def define_url_helper_method(name, route) 
     814            hash_access_method = hash_access_name(name) 
     815            method_name = url_helper_name(name) 
     816 
     817            @module.send(:define_method, method_name) do |*args| 
     818              opts = if args.empty? || Hash === args.first 
     819                args.first || {} 
    582820              else 
    583                 g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp| 
    584                   gp << "hash = merged; not_expired = true" 
    585                   route.write_generation(gp) 
     821                # allow ordered parameters to be associated with corresponding 
     822                # dynamic segments, so you can do 
     823                # 
     824                #   foo_url(bar, baz, bang) 
     825                # 
     826                # instead of 
     827                # 
     828                #   foo_url(:bar => bar, :baz => baz, :bang => bang) 
     829                route.segments.inject({}) do |opts, seg| 
     830                  next opts unless seg.respond_to?(:key) && seg.key 
     831                  opts[seg.key] = args.shift 
     832                  break opts if args.empty? 
     833                  opts 
    586834                end 
    587835              end 
     836 
     837              url_for(send(hash_access_method, opts)) 
    588838            end 
    589             g.source.lines.last << ' )' # Add the closing brace to the end line 
    590             g.if 'new_path' do 
    591               g << 'return new_path, [] if new_unused_count.zero?' 
    592               g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count' 
     839 
     840            @module.send(:protected, method_name) 
     841            helpers << method_name 
     842          end 
     843      end 
     844   
     845      attr_accessor :routes, :named_routes 
     846   
     847      def initialize 
     848        self.routes = [] 
     849        self.named_routes = NamedRouteCollection.new 
     850      end 
     851 
     852      # Subclasses and plugins may override this method to specify a different 
     853      # RouteBuilder instance, so that other route DSL's can be created. 
     854      def builder 
     855        @builder ||= RouteBuilder.new 
     856      end 
     857 
     858      def draw 
     859        clear! 
     860        yield Mapper.new(self) 
     861        named_routes.install 
     862      end 
     863   
     864      def clear! 
     865        routes.clear 
     866        named_routes.clear 
     867        @combined_regexp = nil 
     868        @routes_by_controller = nil 
     869      end 
     870 
     871      def empty? 
     872        routes.empty? 
     873      end 
     874   
     875      def load! 
     876        clear! 
     877        load_routes! 
     878        named_routes.install 
     879      end 
     880 
     881      alias reload load! 
     882 
     883      def load_routes! 
     884        if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes 
     885          load File.join("#{RAILS_ROOT}/config/routes.rb") 
     886        else 
     887          add_route ":controller/:action/:id" 
     888        end 
     889      end 
     890   
     891      def add_route(path, options = {}) 
     892        route = builder.build(path, options) 
     893        routes << route 
     894        route 
     895      end 
     896   
     897      def add_named_route(name, path, options = {}) 
     898        named_routes[name] = add_route(path, options) 
     899      end 
     900   
     901      def options_as_params(options) 
     902        # If an explicit :controller was given, always make :action explicit 
     903        # too, so that action expiry works as expected for things like 
     904        # 
     905        #   generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) 
     906        # 
     907        # (the above is from the unit tests). In the above case, because the 
     908        # controller was explicitly given, but no action, the action is implied to 
     909        # be "index", not the recalled action of "show". 
     910        # 
     911        # great fun, eh? 
     912 
     913        options_as_params = options[:controller] ? { :action => "index" } : {} 
     914        options.each do |k, value| 
     915          options_as_params[k] = value.to_param 
     916        end 
     917        options_as_params 
     918      end 
     919   
     920      def build_expiry(options, recall) 
     921        recall.inject({}) do |expiry, (key, recalled_value)| 
     922          expiry[key] = (options.key?(key) && options[key] != recalled_value) 
     923          expiry 
     924        end 
     925      end 
     926 
     927      # Generate the path indicated by the arguments, and return an array of 
     928      # the keys that were not used to generate it. 
     929      def extra_keys(options, recall={}) 
     930        generate_extras(options, recall).last 
     931      end 
     932 
     933      def generate_extras(options, recall={}) 
     934        generate(options, recall, :generate_extras) 
     935      end 
     936 
     937      def generate(options, recall = {}, method=:generate) 
     938        if options[:use_route] 
     939          options = options.dup 
     940          named_route = named_routes[options.delete(:use_route)] 
     941          options = named_route.parameter_shell.merge(options) 
     942        end 
     943 
     944        options = options_as_params(options) 
     945        expire_on = build_expiry(options, recall) 
     946 
     947        # if the controller has changed, make sure it changes relative to the 
     948        # current controller module, if any. In other words, if we're currently 
     949        # on admin/get, and the new controller is 'set', the new controller 
     950        # should really be admin/set. 
     951        if expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ 
     952          parts = recall[:controller].split('/')[0..-2] + [options[:controller]] 
     953          options[:controller] = parts.join('/') 
     954        end 
     955 
     956        # drop the leading '/' on the controller name 
     957        options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ 
     958        merged = recall.merge(options) 
     959     
     960        if named_route 
     961          return named_route.generate(options, merged, expire_on) 
     962        else 
     963          merged[:action] ||= 'index' 
     964          options[:action] ||= 'index' 
     965   
     966          controller = merged[:controller] 
     967          action = merged[:action] 
     968 
     969          raise "Need controller and action!" unless controller && action 
     970          routes = routes_by_controller[controller][action][merged.keys.sort_by { |x| x.object_id }] 
     971 
     972          routes.each do |route| 
     973            results = route.send(method, options, merged, expire_on) 
     974            return results if results 
     975          end 
     976        end 
     977     
     978        raise RoutingError, "No route matches #{options.inspect}" 
     979      end 
     980   
     981      def recognize(request) 
     982        params = recognize_path(request.path, extract_request_environment(request)) 
     983        request.path_parameters = params.with_indifferent_access 
     984        "#{params[:controller].camelize}Controller".constantize 
     985      end 
     986   
     987      def recognize_path(path, environment={}) 
     988        routes.each do |route| 
     989          result = route.recognize(path, environment) and return result 
     990        end 
     991        raise RoutingError, "no route found to match #{path.inspect} with #{environment.inspect}" 
     992      end 
     993   
     994      def routes_by_controller 
     995        @routes_by_controller ||= Hash.new do |controller_hash, controller| 
     996          controller_hash[controller] = Hash.new do |action_hash, action| 
     997            action_hash[action] = Hash.new do |key_hash, keys| 
     998              key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) 
    593999            end 
    5941000          end 
    595          
    596           g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path" 
    597           g << "return path, unused_keys" 
    598         end 
    599          
    600         return g 
    601       end 
    602        
    603       def categorize_routes 
    604         @categorized_routes = by_controller = Hash.new(self) 
    605        
    606         known_controllers.each do |name| 
    607           set = by_controller[name] = [] 
    608           each do |route| 
    609             set << route if route.matches_controller? name 
    610           end 
    611         end 
    612      
    613         @categorized_routes 
    614       end 
    615        
    616       def known_controllers 
    617         @routes.inject([]) do |known, route| 
    618           if (controller = route.known[:controller]) 
    619             if controller.is_a?(Regexp) 
    620               known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word}  
    621             else known << controller 
    622             end 
    623           end 
    624           known 
    625         end.uniq 
    626       end 
    627  
    628       def reload 
    629         NamedRoutes.clear 
    630          
    631         if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb')) 
    632         else connect(':controller/:action/:id', :action => 'index', :id => nil) 
    633         end 
    634  
    635         NamedRoutes.install 
    636       end 
    637  
    638       def connect(*args) 
    639         new_route = Route.new(*args) 
    640         @routes << new_route 
    641         return new_route 
    642       end 
    643  
    644       def draw 
    645         old_routes = @routes 
    646         @routes = [] 
    647          
    648         begin yield self 
    649         rescue 
    650           @routes = old_routes 
    651           raise 
    652         end 
    653         write_generation 
    654         write_recognition 
    655       end 
    656        
    657       def empty?() @routes.empty? end 
    658    
    659       def each(&block) @routes.each(&block) end 
    660        
    661       # Defines a new named route with the provided name and arguments. 
    662       # This method need only be used when you wish to use a name that a RouteSet instance 
    663       # method exists for, such as categories. 
    664       # 
    665       # For example, map.categories '/categories', :controller => 'categories' will not work 
    666       # due to RouteSet#categories. 
    667       def named_route(name, path, hash = {}) 
    668         route = connect(path, hash) 
    669         NamedRoutes.name_route(route, name) 
    670         route 
    671       end 
    672        
    673       def method_missing(name, *args) 
    674         (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args) 
    675       end 
    676  
    677       def extra_keys(options, recall = {}) 
    678         generate(options.dup, recall).last 
    679       end 
    680     end 
    681  
    682     module NamedRoutes #:nodoc: 
    683       Helpers = [] 
    684       class << self 
    685         def clear() Helpers.clear end 
    686    
    687         def hash_access_name(name) 
    688           "hash_for_#{name}_url" 
    689         end 
    690  
    691         def url_helper_name(name) 
    692           "#{name}_url" 
    693         end 
    694          
    695         def known_hash_for_route(route) 
    696           hash = route.known.symbolize_keys 
    697           route.defaults.each do |key, value| 
    698             hash[key.to_sym] ||= value if value 
    699           end 
    700           hash[:controller] = "/#{hash[:controller]}" 
    701            
    702           hash 
    703         end 
    704          
    705         def define_hash_access_method(route, name) 
    706           hash = known_hash_for_route(route) 
    707           define_method(hash_access_name(name)) do |*args| 
    708             args.first ? hash.merge(args.first) : hash 
    709           end 
    710         end 
    711          
    712         def name_route(route, name) 
    713           define_hash_access_method(route, name) 
    714            
    715           module_eval(%{def #{url_helper_name name}(options = {}) 
    716             url_for(#{hash_access_name(name)}.merge(options.is_a?(Hash) ? options : { :id => options })) 
    717           end}, "generated/routing/named_routes/#{name}.rb") 
    718        
    719           protected url_helper_name(name), hash_access_name(name) 
    720        
    721           Helpers << url_helper_name(name).to_sym 
    722           Helpers << hash_access_name(name).to_sym 
    723           Helpers.uniq! 
    724         end 
    725      
    726         def install(cls = ActionController::Base) 
    727           cls.send :include, self 
    728           if cls.respond_to? :helper_method 
    729             Helpers.each do |helper_name| 
    730               cls.send :helper_method, helper_name 
    731             end 
    732           end 
    733         end 
     1001        end 
     1002      end 
     1003   
     1004      def routes_for(options, merged, expire_on) 
     1005        raise "Need controller and action!" unless controller && action 
     1006        controller = merged[:controller] 
     1007        merged = options if expire_on[:controller] 
     1008        action = merged[:action] || 'index' 
     1009     
     1010        routes_by_controller[controller][action][merged.keys] 
     1011      end 
     1012   
     1013      def routes_for_controller_and_action(controller, action) 
     1014        selected = routes.select do |route| 
     1015          route.matches_controller_and_action? controller, action 
     1016        end 
     1017        (selected.length == routes.length) ? routes : selected 
     1018      end 
     1019   
     1020      def routes_for_controller_and_action_and_keys(controller, action, keys) 
     1021        selected = routes.select do |route| 
     1022          route.matches_controller_and_action? controller, action 
     1023        end 
     1024        selected.sort_by do |route| 
     1025          (keys - route.significant_keys).length 
     1026        end 
     1027      end 
     1028 
     1029      # Subclasses and plugins may override this method to extract further attributes 
     1030      # from the request, for use by route conditions and such. 
     1031      def extract_request_environment(request) 
     1032        { :method => request.method } 
    7341033      end 
    7351034    end 
  • trunk/actionpack/lib/action_controller/test_process.rb

    r4382 r4394  
    107107          value = value.to_s 
    108108        elsif value.is_a? Array 
    109           value = ActionController::Routing::PathComponent::Result.new(value) 
     109          value = ActionController::Routing::PathSegment::Result.new(value) 
    110110        end 
    111111 
     
    434434 
    435435    def method_missing(selector, *args) 
    436       return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector) 
     436      return @controller.send(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) 
    437437      return super 
    438438    end 
  • trunk/actionpack/lib/action_controller/url_rewriter.rb

    r2147 r4394  
    4242        end 
    4343        RESERVED_OPTIONS.each {|k| options.delete k} 
    44         path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash 
    4544 
    46         path << build_query_string(options, extra_keys) unless extra_keys.empty? 
    47          
    48         path 
    49       end 
    50  
    51       # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll 
    52       # be added as a path element instead of a regular parameter pair. 
    53       def build_query_string(hash, only_keys = nil) 
    54         elements = [] 
    55         query_string = "" 
    56  
    57         only_keys ||= hash.keys 
    58          
    59         only_keys.each do |key| 
    60           value = hash[key]  
    61           key = CGI.escape key.to_s 
    62           if value.class == Array 
    63             key <<  '[]' 
    64           else 
    65             value = [ value ] 
    66           end 
    67           value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" } 
    68         end 
    69          
    70         query_string << ("?" + elements.join("&")) unless elements.empty? 
    71         query_string 
     45        # Generates the query string, too 
     46        Routing::Routes.generate(options, @request.parameters) 
    7247      end 
    7348  end 
  • trunk/actionpack/test/controller/action_pack_assertions_test.rb

    r4261 r4394  
    228228  def test_assert_redirect_to_named_route 
    229229    with_routing do |set| 
    230       set.draw do 
    231         set.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing' 
    232         set.connect   ':controller/:action/:id' 
    233       end 
     230      set.draw do |map| 
     231        map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing' 
     232        map.connect   ':controller/:action/:id' 
     233      end 
     234      set.named_routes.install 
     235 
    234236      process :redirect_to_named_route 
    235237      assert_redirected_to 'http://test.host/route_one' 
     
    241243  def test_assert_redirect_to_named_route_failure 
    242244    with_routing do |set| 
    243       set.draw do 
    244         set.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one' 
    245         set.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' 
    246         set.connect   ':controller/:action/:id' 
     245      set.draw do |map| 
     246        map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one' 
     247        map.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two' 
     248        map.connect   ':controller/:action/:id' 
    247249      end 
    248250      process :redirect_to_named_route 
  • trunk/actionpack/test/controller/mime_type_test.rb

    r3917 r4394  
    66 
    77  def test_parse_single 
     8p Mime::LOOKUP.keys.sort 
    89    Mime::LOOKUP.keys.each do |mime_type| 
    910      assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) 
  • trunk/actionpack/test/controller/routing_test.rb

    r4319 r4394  
    22require 'test/unit' 
    33require File.dirname(__FILE__) + '/fake_controllers' 
    4 require 'stringio
     4require 'action_controller/routing
    55 
    66RunTimeTests = ARGV.include? 'time' 
    7  
    8 module ActionController::CodeGeneration 
    9  
    10 class SourceTests < Test::Unit::TestCase 
    11   attr_accessor :source 
    12   def setup 
    13     @source = Source.new 
    14   end 
    15      
    16   def test_initial_state 
    17     assert_equal [], source.lines 
    18     assert_equal 0, source.indentation_level 
    19   end 
    20    
    21   def test_trivial_operations 
    22     source << "puts 'Hello World'" 
    23     assert_equal ["puts 'Hello World'"], source.lines 
    24     assert_equal "puts 'Hello World'", source.to_s 
    25      
    26     source.line "puts 'Goodbye World'" 
    27     assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], source.lines 
    28     assert_equal "puts 'Hello World'\nputs 'Goodbye World'", source.to_s 
    29   end 
    30  
    31   def test_indentation 
    32     source << "x = gets.to_i" 
    33     source << 'if x.odd?' 
    34     source.indent { source << "puts 'x is odd!'" } 
    35     source << 'else' 
    36     source.indent { source << "puts 'x is even!'" } 
    37     source << 'end' 
    38      
    39     assert_equal ["x = gets.to_i", "if x.odd?", "  puts 'x is odd!'", 'else', "  puts 'x is even!'", 'end'], source.lines 
    40      
    41     text = "x = gets.to_i 
    42 if x.odd? 
    43   puts 'x is odd!' 
    44 else 
    45   puts 'x is even!' 
    46 end" 
    47  
    48     assert_equal text, source.to_s 
    49   end  
     7ROUTING = ActionController::Routing 
     8 
     9class ROUTING::RouteBuilder 
     10  attr_reader :warn_output 
     11 
     12  def warn(msg) 
     13    (@warn_output ||= []) << msg 
     14  end 
    5015end 
    5116 
    52 class CodeGeneratorTests < Test::Unit::TestCase 
    53   attr_accessor :generator 
    54   def setup 
    55     @generator = CodeGenerator.new 
    56   end 
    57    
    58   def test_initial_state 
    59     assert_equal [], generator.source.lines 
    60     assert_equal [], generator.locals 
    61   end 
    62      
    63   def test_trivial_operations 
    64     ["puts 'Hello World'", "puts 'Goodbye World'"].each {|l| generator << l}  
    65     assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], generator.source.lines 
    66     assert_equal "puts 'Hello World'\nputs 'Goodbye World'", generator.to_s 
    67   end 
    68    
    69   def test_if 
    70     generator << "x = gets.to_i" 
    71     generator.if("x.odd?") { generator << "puts 'x is odd!'" } 
    72      
    73     assert_equal "x = gets.to_i\nif x.odd?\n  puts 'x is odd!'\nend", generator.to_s 
    74   end 
    75    
    76   def test_else 
    77     test_if 
    78     generator.else { generator << "puts 'x is even!'" } 
    79      
    80     assert_equal "x = gets.to_i\nif x.odd?\n  puts 'x is odd!'\nelse \n  puts 'x is even!'\nend", generator.to_s 
    81   end 
    82  
    83   def test_dup 
    84     generator << 'x = 2' 
    85     generator.locals << :x 
    86      
    87     g = generator.dup 
    88     assert_equal generator.source, g.source 
    89     assert_equal generator.locals, g.locals 
    90      
    91     g << 'y = 3' 
    92     g.locals << :y 
    93     assert_equal [:x, :y], g.locals # Make sure they don't share the same array. 
    94     assert_equal [:x], generator.locals 
    95   end 
    96 end  
    97  
    98 class RecognitionTests < Test::Unit::TestCase 
    99   attr_accessor :generator 
    100   alias :g :generator 
    101   def setup 
    102     @generator = RecognitionGenerator.new 
    103   end 
    104  
    105   def go(components) 
    106     g.current = components.first 
    107     g.after = components[1..-1] || [] 
    108     g.go 
    109   end 
    110    
    111   def execute(path, show = false) 
    112     path = path.split('/') if path.is_a? String 
    113     source = "index, path = 0, #{path.inspect}\n#{g.to_s}" 
    114     puts source if show 
    115     r = eval source 
    116     r ? r.symbolize_keys : nil 
    117   end 
    118    
    119   Static = ::ActionController::Routing::StaticComponent 
    120   Dynamic = ::ActionController::Routing::DynamicComponent 
    121   Path = ::ActionController::Routing::PathComponent 
    122   Controller = ::ActionController::Routing::ControllerComponent 
    123    
    124   def test_all_static 
    125     c = %w(hello world how are you).collect {|str| Static.new(str)} 
    126      
    127     g.result :controller, "::ContentController", true 
    128     g.constant_result :action, 'index'  
    129      
    130     go c 
    131      
    132     assert_nil execute('x') 
    133     assert_nil execute('hello/world/how') 
    134     assert_nil execute('hello/world/how/are') 
    135     assert_nil execute('hello/world/how/are/you/today') 
    136     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hello/world/how/are/you')) 
    137   end 
    138  
    139   def test_basic_dynamic 
    140     c = [Static.new("hi"), Dynamic.new(:action)] 
    141     g.result :controller, "::ContentController", true 
    142     go c 
    143      
    144     assert_nil execute('boo') 
    145     assert_nil execute('boo/blah') 
    146     assert_nil execute('hi') 
    147     assert_nil execute('hi/dude/what') 
    148     assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) 
    149   end  
    150  
    151   def test_basic_dynamic_backwards 
    152     c = [Dynamic.new(:action), Static.new("hi")] 
    153     go c 
    154  
    155     assert_nil execute('') 
    156     assert_nil execute('boo') 
    157     assert_nil execute('boo/blah') 
    158     assert_nil execute('hi') 
    159     assert_equal({:action => 'index'}, execute('index/hi')) 
    160     assert_equal({:action => 'show'}, execute('show/hi')) 
    161     assert_nil execute('hi/dude') 
    162   end 
    163  
    164   def test_dynamic_with_default 
    165     c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')] 
    166     g.result :controller, "::ContentController", true 
    167     go c 
    168      
    169     assert_nil execute('boo') 
    170     assert_nil execute('boo/blah') 
    171     assert_nil execute('hi/dude/what') 
    172     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi')) 
    173     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) 
    174     assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) 
    175   end  
    176  
    177   def test_dynamic_with_string_condition 
    178     c = [Static.new("hi"), Dynamic.new(:action, :condition => 'index')] 
    179     g.result :controller, "::ContentController", true 
    180     go c 
    181  
    182     assert_nil execute('boo') 
    183     assert_nil execute('boo/blah') 
    184     assert_nil execute('hi') 
    185     assert_nil execute('hi/dude/what') 
    186     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) 
    187     assert_nil execute('hi/dude') 
    188   end 
    189  
    190   def test_dynamic_with_string_condition_backwards 
    191     c = [Dynamic.new(:action, :condition => 'index'), Static.new("hi")] 
    192     g.result :controller, "::ContentController", true 
    193     go c 
    194  
    195     assert_nil execute('boo') 
    196     assert_nil execute('boo/blah') 
    197     assert_nil execute('hi') 
    198     assert_nil execute('dude/what/hi') 
    199     assert_nil execute('index/what') 
    200     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('index/hi')) 
    201     assert_nil execute('dude/hi') 
    202   end 
    203  
    204   def test_dynamic_with_regexp_condition 
    205     c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)] 
    206     g.result :controller, "::ContentController", true 
    207     go c 
    208      
    209     assert_nil execute('boo') 
    210     assert_nil execute('boo/blah') 
    211     assert_nil execute('hi') 
    212     assert_nil execute('hi/FOXY') 
    213     assert_nil execute('hi/138708jkhdf') 
    214     assert_nil execute('hi/dkjfl8792343dfsf') 
    215     assert_nil execute('hi/dude/what') 
    216     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) 
    217     assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) 
    218   end  
    219    
    220   def test_dynamic_with_regexp_and_default 
    221     c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/, :default => 'index')] 
    222     g.result :controller, "::ContentController", true 
    223     go c 
    224      
    225     assert_nil execute('boo') 
    226     assert_nil execute('boo/blah') 
    227     assert_nil execute('hi/FOXY') 
    228     assert_nil execute('hi/138708jkhdf') 
    229     assert_nil execute('hi/dkjfl8792343dfsf') 
    230     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi')) 
    231     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index')) 
    232     assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude')) 
    233     assert_nil execute('hi/dude/what') 
    234   end 
    235  
    236   def test_path 
    237     c = [Static.new("hi"), Path.new(:file)] 
    238     g.result :controller, "::ContentController", true 
    239     g.constant_result :action, "download" 
    240      
    241     go c 
    242      
    243     assert_nil execute('boo') 
    244     assert_nil execute('boo/blah') 
    245     assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('hi')) 
    246     assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)}, 
    247                  execute('hi/books/agile_rails_dev.pdf')) 
    248     assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('hi/dude')) 
    249     assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s 
    250   end 
    251  
    252   def test_path_with_dynamic 
    253     c = [Dynamic.new(:action), Path.new(:file)] 
    254     g.result :controller, "::ContentController", true 
    255  
    256     go c 
    257  
    258     assert_nil execute('') 
    259     assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('download')) 
    260     assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)}, 
    261                  execute('download/books/agile_rails_dev.pdf')) 
    262     assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('download/dude')) 
    263     assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s 
    264   end 
    265  
    266   def test_path_with_dynamic_and_default 
    267     c = [Dynamic.new(:action, :default => 'index'), Path.new(:file)] 
    268  
    269     go c 
    270  
    271     assert_equal({:action => 'index', :file => []}, execute('')) 
    272     assert_equal({:action => 'index', :file => []}, execute('index')) 
    273     assert_equal({:action => 'blarg', :file => []}, execute('blarg')) 
    274     assert_equal({:action => 'index', :file => ['content']}, execute('index/content')) 
    275     assert_equal({:action => 'show', :file => ['rails_dev.pdf']}, execute('show/rails_dev.pdf')) 
    276   end 
    277    
    278   def test_controller 
    279     c = [Static.new("hi"), Controller.new(:controller)] 
    280     g.constant_result :action, "hi" 
    281      
    282     go c 
    283      
    284     assert_nil execute('boo') 
    285     assert_nil execute('boo/blah') 
    286     assert_nil execute('hi/x') 
    287     assert_nil execute('hi/13870948') 
    288     assert_nil execute('hi/content/dog') 
    289     assert_nil execute('hi/admin/user/foo') 
    290     assert_equal({:controller => ::ContentController, :action => 'hi'}, execute('hi/content')) 
    291     assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user')) 
    292   end 
    293    
    294   def test_controller_with_regexp 
    295     c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)] 
    296     g.constant_result :action, "hi" 
    297      
    298     go c 
    299      
    300     assert_nil execute('hi') 
    301     assert_nil execute('hi/x') 
    302     assert_nil execute('hi/content') 
    303     assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user')) 
    304     assert_equal({:controller => ::Admin::NewsFeedController, :action => 'hi'}, execute('hi/admin/news_feed')) 
    305     assert_nil execute('hi/admin/user/foo') 
    306   end 
    307    
    308   def test_standard_route(time = ::RunTimeTests) 
    309     c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)] 
    310     go c 
    311      
    312     # Make sure we get the right answers 
    313     assert_equal({:controller => ::ContentController, :action => 'index'}, execute('content')) 
    314     assert_equal({:controller => ::ContentController, :action => 'list'}, execute('content/list')) 
    315     assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, execute('content/show/10')) 
    316      
    317     assert_equal({:controller => ::Admin::UserController, :action => 'index'}, execute('admin/user')) 
    318     assert_equal({:controller => ::Admin::UserController, :action => 'list'}, execute('admin/user/list')) 
    319     assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => 'nseckar'}, execute('admin/user/show/nseckar')) 
    320      
    321     assert_nil execute('content/show/10/20') 
    322     assert_nil execute('food') 
    323  
    324     if time 
    325       source = "def self.execute(path) 
    326         path = path.split('/') if path.is_a? String 
    327         index = 0 
    328         r = #{g.to_s} 
    329       end" 
    330       eval(source) 
    331  
    332       GC.start 
    333       n = 1000 
    334       time = Benchmark.realtime do n.times { 
    335         execute('content') 
    336         execute('content/list') 
    337         execute('content/show/10') 
    338          
    339         execute('admin/user') 
    340         execute('admin/user/list') 
    341         execute('admin/user/show/nseckar') 
    342          
    343         execute('admin/user/show/nseckar/dude') 
    344         execute('admin/why/show/nseckar') 
    345         execute('content/show/10/20') 
    346         execute('food') 
    347       } end 
    348       time -= Benchmark.realtime do n.times { } end 
    349      
    350        
    351       puts "\n\nRecognition:" 
    352       per_url = time / (n * 10) 
    353      
    354       puts "#{per_url * 1000} ms/url" 
    355       puts "#{1 / per_url} urls/s\n\n" 
    356     end 
    357   end 
    358  
    359   def test_default_route 
    360     g.result :controller, "::ContentController", true 
    361     g.constant_result :action, 'index'  
    362      
    363     go [] 
    364      
    365     assert_nil execute('x') 
    366     assert_nil execute('hello/world/how') 
    367     assert_nil execute('hello/world/how/are') 
    368     assert_nil execute('hello/world/how/are/you/today') 
    369     assert_equal({:controller => ::ContentController, :action => 'index'}, execute([])) 
    370   end 
    371 end 
    372  
    373 class GenerationTests < Test::Unit::TestCase 
    374   attr_accessor :generator 
    375   alias :g :generator 
    376   def setup 
    377     @generator = GenerationGenerator.new # ha! 
    378   end 
    379    
    380   def go(components) 
    381     g.current = components.first 
    382     g.after = components[1..-1] || [] 
    383     g.go 
    384   end 
    385    
    386   def execute(options, recall, show = false) 
    387     source = "\n 
    388 expire_on = ::ActionController::Routing.expiry_hash(options, recall) 
    389 hash = merged = recall.merge(options) 
    390 not_expired = true 
    391  
    392 #{g.to_s}\n\n" 
    393     puts source if show 
    394     eval(source) 
    395   end 
    396    
    397   Static = ::ActionController::Routing::StaticComponent 
    398   Dynamic = ::ActionController::Routing::DynamicComponent 
    399   Path = ::ActionController::Routing::PathComponent 
    400   Controller = ::ActionController::Routing::ControllerComponent 
    401    
    402   def test_all_static_no_requirements 
    403     c = [Static.new("hello"), Static.new("world")] 
    404     go c 
    405      
    406     assert_equal "/hello/world", execute({}, {}) 
    407   end 
    408    
    409   def test_basic_dynamic 
    410     c = [Static.new("hi"), Dynamic.new(:action)] 
    411     go c 
    412      
    413     assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'}) 
    414     assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'}) 
    415     assert_equal '/hi/list+people', execute({}, {:action => 'list people'}) 
    416     assert_nil execute({},{}) 
    417   end 
    418    
    419   def test_dynamic_with_default 
    420     c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')] 
    421     go c 
    422      
    423     assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'}) 
    424     assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'}) 
    425     assert_equal '/hi/list+people', execute({}, {:action => 'list people'}) 
    426     assert_equal '/hi', execute({}, {}) 
    427   end 
    428    
    429   def test_dynamic_with_regexp_condition 
    430     c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)] 
    431     go c 
    432      
    433     assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'}) 
    434     assert_nil execute({:action => 'fox5'}, {:action => 'index'}) 
    435     assert_nil execute({:action => 'something_is_up'}, {:action => 'index'}) 
    436     assert_nil execute({}, {:action => 'list people'}) 
    437     assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {}) 
    438     assert_nil execute({}, {}) 
    439   end 
    440    
    441   def test_dynamic_with_default_and_regexp_condition 
    442     c = [Static.new("hi"), Dynamic.new(:action, :default => 'index', :condition => /^[a-z]+$/)] 
    443     go c 
    444      
    445     assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'}) 
    446     assert_nil execute({:action => 'fox5'}, {:action => 'index'}) 
    447     assert_nil execute({:action => 'something_is_up'}, {:action => 'index'}) 
    448     assert_nil execute({}, {:action => 'list people'}) 
    449     assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {}) 
    450     assert_equal '/hi', execute({}, {}) 
    451   end 
    452  
    453   def test_path 
    454     c = [Static.new("hi"), Path.new(:file)] 
    455     go c 
    456      
    457     assert_equal '/hi', execute({:file => []}, {}) 
    458     assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => %w(books agile_rails_dev.pdf)}, {}) 
    459     assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => %w(books development&whatever agile_rails_dev.pdf)}, {}) 
    460      
    461     assert_equal '/hi', execute({:file => ''}, {}) 
    462     assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => 'books/agile_rails_dev.pdf'}, {}) 
    463     assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => 'books/development&whatever/agile_rails_dev.pdf'}, {}) 
    464   end 
    465    
    466   def test_controller 
    467     c = [Static.new("hi"), Controller.new(:controller)] 
    468     go c 
    469      
    470     assert_nil execute({}, {}) 
    471     assert_equal '/hi/content', execute({:controller => 'content'}, {}) 
    472     assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {}) 
    473     assert_equal '/hi/content', execute({}, {:controller => 'content'})  
    474     assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'}) 
    475   end 
    476    
    477   def test_controller_with_regexp 
    478     c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)] 
    479     go c 
    480      
    481     assert_nil execute({}, {}) 
    482     assert_nil execute({:controller => 'content'}, {}) 
    483     assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {}) 
    484     assert_nil execute({}, {:controller => 'content'})  
    485     assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'}) 
    486   end 
    487    
    488   def test_standard_route(time = ::RunTimeTests) 
    489     c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)] 
    490     go c 
    491      
    492     # Make sure we get the right answers 
    493     assert_equal('/content', execute({:action => 'index'}, {:controller => 'content', :action => 'list'})) 
    494     assert_equal('/content/list', execute({:action => 'list'}, {:controller => 'content', :action => 'index'})) 
    495     assert_equal('/content/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'})) 
    496  
    497     assert_equal('/admin/user', execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'})) 
    498     assert_equal('/admin/user/list', execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'})) 
    499     assert_equal('/admin/user/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'})) 
    500  
    501     if time 
    502       GC.start 
    503       n = 1000 
    504       time = Benchmark.realtime do n.times { 
    505         execute({:action => 'index'}, {:controller => 'content', :action => 'list'}) 
    506         execute({:action => 'list'}, {:controller => 'content', :action => 'index'}) 
    507         execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}) 
    508  
    509         execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'}) 
    510         execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'}) 
    511         execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}) 
    512       } end 
    513       time -= Benchmark.realtime do n.times { } end 
    514      
    515       puts "\n\nGeneration:" 
    516       per_url = time / (n * 6) 
    517      
    518       puts "#{per_url * 1000} ms/url" 
    519       puts "#{1 / per_url} urls/s\n\n" 
    520     end 
    521   end 
    522  
    523   def test_default_route 
    524     g.if(g.check_conditions(:controller => 'content', :action => 'welcome')) { go [] } 
    525      
    526     assert_nil execute({:controller => 'foo', :action => 'welcome'}, {}) 
    527     assert_nil execute({:controller => 'content', :action => 'elcome'}, {}) 
    528     assert_nil execute({:action => 'elcome'}, {:controller => 'content'}) 
    529  
    530     assert_equal '/', execute({:controller => 'content', :action => 'welcome'}, {}) 
    531     assert_equal '/', execute({:action => 'welcome'}, {:controller => 'content'}) 
    532     assert_equal '/', execute({:action => 'welcome', :id => '10'}, {:controller => 'content'}) 
    533   end 
    534 end 
    535  
    536 class RouteTests < Test::Unit::TestCase 
    537    
    538    
    539   def route(*args) 
    540     @route = ::ActionController::Routing::Route.new(*args) unless args.empty? 
    541     return @route 
    542   end 
    543    
    544   def rec(path, show = false) 
    545     path = path.split('/') if path.is_a? String 
    546     index = 0 
    547     source = route.write_recognition.to_s 
    548     puts "\n\n#{source}\n\n" if show 
    549     r = eval(source) 
    550     r ? r.symbolize_keys : r 
    551   end 
    552   def gen(options, recall = nil, show = false) 
    553     recall ||= options.dup 
    554      
    555     expire_on = ::ActionController::Routing.expiry_hash(options, recall) 
    556     hash = merged = recall.merge(options) 
    557     not_expired = true 
    558      
    559     source = route.write_generation.to_s 
    560     puts "\n\n#{source}\n\n" if show 
    561     eval(source) 
    562      
    563   end 
    564    
    565   def test_static 
    566     route 'hello/world', :known => 'known_value', :controller => 'content', :action => 'index' 
    567      
    568     assert_nil rec('hello/turn') 
    569     assert_nil rec('turn/world') 
    570     assert_equal( 
    571       {:known => 'known_value', :controller => ::ContentController, :action => 'index'}, 
    572       rec('hello/world') 
    573     ) 
    574      
    575     assert_nil gen(:known => 'foo') 
    576     assert_nil gen({}) 
    577     assert_equal '/hello/world', gen(:known => 'known_value', :controller => 'content', :action => 'index') 
    578     assert_equal '/hello/world', gen(:known => 'known_value', :extra => 'hi', :controller => 'content', :action => 'index') 
    579     assert_equal [:extra], route.extra_keys(:known => 'known_value', :extra => 'hi') 
    580   end 
    581    
    582   def test_dynamic 
    583     route 'hello/:name', :controller => 'content', :action => 'show_person' 
    584      
    585     assert_nil rec('hello') 
    586     assert_nil rec('foo/bar') 
    587     assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'rails'}, rec('hello/rails')) 
    588     assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'Nicholas Seckar'}, rec('hello/Nicholas+Seckar')) 
    589      
    590     assert_nil gen(:controller => 'content', :action => 'show_dude', :name => 'rails') 
    591     assert_nil gen(:controller => 'content', :action => 'show_person') 
    592     assert_nil gen(:controller => 'admin/user', :action => 'show_person', :name => 'rails') 
    593     assert_equal '/hello/rails', gen(:controller => 'content', :action => 'show_person', :name => 'rails') 
    594     assert_equal '/hello/Nicholas+Seckar', gen(:controller => 'content', :action => 'show_person', :name => 'Nicholas Seckar') 
    595   end 
    596    
    597   def test_typical 
    598     route ':controller/:action/:id', :action => 'index', :id => nil 
    599     assert_nil rec('hello') 
    600     assert_nil rec('foo bar') 
    601     assert_equal({:controller => ::ContentController, :action => 'index'}, rec('content')) 
    602     assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user')) 
    603      
    604     assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user/index')) 
    605     assert_equal({:controller => ::Admin::UserController, :action => 'list'}, rec('admin/user/list')) 
    606     assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}, rec('admin/user/show/10')) 
    607      
    608     assert_equal({:controller => ::ContentController, :action => 'list'}, rec('content/list')) 
    609     assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, rec('content/show/10')) 
    610      
    611      
    612     assert_equal '/content', gen(:controller => 'content', :action => 'index') 
    613     assert_equal '/content/list', gen(:controller => 'content', :action => 'list') 
    614     assert_equal '/content/show/10', gen(:controller => 'content', :action => 'show', :id => '10') 
    615      
    616     assert_equal '/admin/user', gen(:controller => 'admin/user', :action => 'index') 
    617     assert_equal '/admin/user', gen(:controller => 'admin/user') 
    618     assert_equal '/admin/user', gen({:controller => 'admin/user'}, {:controller => 'content', :action => 'list', :id => '10'}) 
    619     assert_equal '/admin/user/show/10', gen(:controller => 'admin/user', :action => 'show', :id => '10') 
    620   end 
    621 end 
    622  
    623 class RouteSetTests < Test::Unit::TestCase 
     17class LegacyRouteSetTests < Test::Unit::TestCase 
    62418  attr_reader :rs 
    62519  def setup 
    62620    @rs = ::ActionController::Routing::RouteSet.new 
     21    ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) 
    62722    @rs.draw {|m| m.connect ':controller/:action/:id' } 
    628     ::ActionController::Routing::NamedRoutes.clear 
    62923  end 
    63024   
    63125  def test_default_setup 
    632     assert_equal({:controller => ::ContentController, :action => 'index'}.stringify_keys, rs.recognize_path(%w(content))) 
    633     assert_equal({:controller => ::ContentController, :action => 'list'}.stringify_keys, rs.recognize_path(%w(content list))) 
    634     assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(content show 10))) 
    635      
    636     assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(admin user show 10))) 
    637      
    638     assert_equal ['/admin/user/show/10', []], rs.generate({:controller => 'admin/user', :action => 'show', :id => 10}
    639      
    640     assert_equal ['/admin/user/show', []], rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
    641     assert_equal ['/admin/user/list/10', []], rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
    642  
    643     assert_equal ['/admin/stuff', []], rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
    644     assert_equal ['/stuff', []], rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
     26    assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content")) 
     27    assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list")) 
     28    assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10")) 
     29     
     30    assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/admin/user/show/10")) 
     31     
     32    assert_equal '/admin/user/show/10', rs.generate(:controller => 'admin/user', :action => 'show', :id => 10
     33     
     34    assert_equal '/admin/user/show', rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
     35    assert_equal '/admin/user/list/10', rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
     36 
     37    assert_equal '/admin/stuff', rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
     38    assert_equal '/stuff', rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'}) 
    64539  end 
    64640   
     
    65650      rectime = Benchmark.realtime do 
    65751        n.times do 
    658           rs.recognize_path(%w(content)
    659           rs.recognize_path(%w(content list)
    660           rs.recognize_path(%w(content show 10)
    661           rs.recognize_path(%w(admin user)
    662           rs.recognize_path(%w(admin user list)
    663           rs.recognize_path(%w(admin user show 10)
     52          rs.recognize_path("content"
     53          rs.recognize_path("content/list"
     54          rs.recognize_path("content/show/10"
     55          rs.recognize_path("admin/user"
     56          rs.recognize_path("admin/user/list"
     57          rs.recognize_path("admin/user/show/10"
    66458        end 
    66559      end 
     
    70599  end 
    706100 
    707   def test_route_generating_string_literal_in_comparison_warning 
    708     old_stderr = $stderr 
    709     $stderr = StringIO.new 
    710     rs.draw do |map| 
    711       map.connect 'subscriptions/:action/:subscription_type', :controller => "subscriptions" 
    712     end 
    713     assert_equal "", $stderr.string 
    714   ensure 
    715     $stderr = old_stderr 
    716   end 
    717  
    718101  def test_route_with_regexp_for_controller 
    719102    rs.draw do |map| 
     
    721104      map.connect ':controller/:action/:id' 
    722105    end 
    723     assert_equal({:controller => ::Admin::UserController, :admintoken => "foo", :action => "index"}.stringify_keys, 
    724         rs.recognize_path(%w(admin user foo))) 
    725     assert_equal({:controller => ::ContentController, :action => "foo"}.stringify_keys, 
    726         rs.recognize_path(%w(content foo))) 
    727     assert_equal ['/admin/user/foo', []], rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index") 
    728     assert_equal ['/content/foo',[]], rs.generate(:controller => "content", :action => "foo") 
     106    assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"}, 
     107        rs.recognize_path("/admin/user/foo")) 
     108    assert_equal({:controller => "content", :action => "foo"}, rs.recognize_path("/content/foo")) 
     109    assert_equal '/admin/user/foo', rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index") 
     110    assert_equal '/content/foo', rs.generate(:controller => "content", :action => "foo") 
    729111  end 
    730112   
    731113  def test_basic_named_route 
    732     rs.home '', :controller => 'content', :action => 'list'  
    733     x = setup_for_named_route 
    734     assert_equal({:controller => '/content', :action => 'list'}, 
    735                  x.new.send(:home_url)) 
     114    rs.add_named_route :home, '', :controller => 'content', :action => 'list'  
     115    x = setup_for_named_route.new 
     116    assert_equal({:controller => 'content', :action => 'list', :use_route => :home}, 
     117                 x.send(:home_url)) 
    736118  end 
    737119 
    738120  def test_named_route_with_option 
    739     rs.page 'page/:title', :controller => 'content', :action => 'show_page' 
    740     x = setup_for_named_route 
    741     assert_equal({:controller => '/content', :action => 'show_page', :title => 'new stuff'}, 
    742                  x.new.send(:page_url, :title => 'new stuff')) 
     121    rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page' 
     122    x = setup_for_named_route.new 
     123    assert_equal({:controller => 'content', :action => 'show_page', :title => 'new stuff', :use_route => :page}, 
     124                 x.send(:page_url, :title => 'new stuff')) 
    743125  end 
    744126 
    745127  def test_named_route_with_default 
    746     rs.page 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage' 
    747     x = setup_for_named_route 
    748     assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutPage'}, 
    749                  x.new.send(:page_url)) 
    750     assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutRails'}, 
    751                  x.new.send(:page_url, :title => "AboutRails")) 
     128    rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage' 
     129    x = setup_for_named_route.new 
     130    assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutPage', :use_route => :page}, 
     131                 x.send(:page_url)) 
     132    assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutRails', :use_route => :page}, 
     133                 x.send(:page_url, :title => "AboutRails")) 
    752134 
    753135  end 
     
    756138    x = Class.new 
    757139    x.send(:define_method, :url_for) {|x| x} 
    758     x.send :include, ::ActionController::Routing::NamedRoutes 
     140    rs.named_routes.install(x) 
    759141    x 
    760142  end 
     
    762144  def test_named_route_without_hash 
    763145    rs.draw do |map| 
    764       rs.normal ':controller/:action/:id' 
     146      map.normal ':controller/:action/:id' 
    765147    end 
    766148  end 
     
    768150  def test_named_route_with_regexps 
    769151    rs.draw do |map| 
    770       rs.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show', 
     152      map.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show', 
    771153        :year => /^\d+$/, :month => /^\d+$/, :day => /^\d+$/ 
    772       rs.connect ':controller/:action/:id' 
    773     end 
    774     x = setup_for_named_route 
     154      map.connect ':controller/:action/:id' 
     155    end 
     156    x = setup_for_named_route.new 
    775157    assert_equal( 
    776       {:controller => '/page', :action => 'show', :title => 'hi'}, 
    777       x.new.send(:article_url, :title => 'hi') 
     158      {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article}, 
     159      x.send(:article_url, :title => 'hi') 
    778160    ) 
    779161    assert_equal( 
    780       {:controller => '/page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6}, 
    781       x.new.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) 
     162      {:controller => 'page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6, :use_route => :article}, 
     163      x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) 
    782164    ) 
    783165  end 
    784166 
    785167  def test_changing_controller 
    786     assert_equal ['/admin/stuff/show/10', []], rs.generate( 
     168    assert_equal '/admin/stuff/show/10', rs.generate( 
    787169      {:controller => 'stuff', :action => 'show', :id => 10}, 
    788170      {:controller => 'admin/user', :action => 'index'} 
     
    792174  def test_paths_escaped 
    793175    rs.draw do |map| 
    794       rs.path 'file/*path', :controller => 'content', :action => 'show_file' 
    795       rs.connect ':controller/:action/:id' 
    796     end 
    797     results = rs.recognize_path %w(file hello+world how+are+you%3F) 
     176      map.path 'file/*path', :controller => 'content', :action => 'show_file' 
     177      map.connect ':controller/:action/:id' 
     178    end 
     179    results = rs.recognize_path "/file/hello+world/how+are+you%3F" 
    798180    assert results, "Recognition should have succeeded" 
    799     assert_equal ['hello world', 'how are you?'], results['path'
    800  
    801     results = rs.recognize_path %w(file) 
     181    assert_equal ['hello world', 'how are you?'], results[:path
     182 
     183    results = rs.recognize_path "/file" 
    802184    assert results, "Recognition should have succeeded" 
    803     assert_equal [], results['path'
     185    assert_equal [], results[:path
    804186  end 
    805187   
    806188  def test_non_controllers_cannot_be_matched 
    807     rs.draw do 
    808       rs.connect ':controller/:action/:id' 
    809     end 
    810     assert_nil rs.recognize_path(%w(not_a show 10)), "Shouldn't recognize non-controllers as controllers!" 
     189    rs.draw do |map| 
     190      map.connect ':controller/:action/:id' 
     191    end 
     192    assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } 
    811193  end 
    812194 
     
    814196    assert_raises(ActionController::RoutingError) do 
    815197      rs.draw do |map| 
    816         rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) 
    817         rs.connect ':controller/:action/:id' 
     198        map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) 
     199        map.connect ':controller/:action/:id' 
    818200      end 
    819201    end 
    820202     
    821203    rs.draw do |map| 
    822       rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => [] 
    823       rs.connect ':controller/:action/:id' 
     204      map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => [] 
     205      map.connect ':controller/:action/:id' 
    824206    end 
    825207  end 
     
    827209  def test_dynamic_path_allowed 
    828210    rs.draw do |map| 
    829       rs.connect '*path', :controller => 'content', :action => 'show_file' 
    830     end 
    831  
    832     assert_equal ['/pages/boo', []], rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo)) 
     211      map.connect '*path', :controller => 'content', :action => 'show_file' 
     212    end 
     213 
     214    assert_equal '/pages/boo', rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo)) 
    833215  end 
    834216 
    835217  def test_backwards 
    836218    rs.draw do |map| 
    837       rs.connect 'page/:id/:action', :controller => 'pages', :action => 'show' 
    838       rs.connect ':controller/:action/:id' 
    839     end 
    840  
    841     assert_equal ['/page/20', []], rs.generate({:id => 20}, {:controller => 'pages'}) 
    842     assert_equal ['/page/20', []], rs.generate(:controller => 'pages', :id => 20, :action => 'show') 
    843     assert_equal ['/pages/boo', []], rs.generate(:controller => 'pages', :action => 'boo') 
     219      map.connect 'page/:id/:action', :controller => 'pages', :action => 'show' 
     220      map.connect ':controller/:action/:id' 
     221    end 
     222 
     223    assert_equal '/page/20', rs.generate({:id => 20}, {:controller => 'pages', :action => 'show'}) 
     224    assert_equal '/page/20', rs.generate(:controller => 'pages', :id => 20, :action => 'show') 
     225    assert_equal '/pages/boo', rs.generate(:controller => 'pages', :action => 'boo') 
    844226  end 
    845227 
    846228  def test_route_with_fixnum_default 
    847229    rs.draw do |map| 
    848       rs.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 
    849       rs.connect ':controller/:action/:id' 
    850     end 
    851  
    852     assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page') 
    853     assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 1) 
    854     assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => '1') 
    855     assert_equal ['/page/10', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 10) 
    856  
    857     ctrl = ::ContentController 
    858  
    859     assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => 1}, rs.recognize_path(%w(page))) 
    860     assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '1'}, rs.recognize_path(%w(page 1))) 
    861     assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '10'}, rs.recognize_path(%w(page 10))) 
     230      map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1 
     231      map.connect ':controller/:action/:id' 
     232    end 
     233 
     234    assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page') 
     235    assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => 1) 
     236    assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => '1') 
     237    assert_equal '/page/10', rs.generate(:controller => 'content', :action => 'show_page', :id => 10) 
     238 
     239    assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page")) 
     240    assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1")) 
     241    assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10")) 
    862242  end 
    863243 
    864244  def test_action_expiry 
    865     assert_equal ['/content', []], rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) 
     245    assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) 
    866246  end 
    867247 
    868248  def test_recognition_with_uppercase_controller_name 
    869     assert_equal({'controller' => ::ContentController, 'action' => 'index'}, rs.recognize_path(%w(Content))) 
    870     assert_equal({'controller' => ::ContentController, 'action' => 'list'}, rs.recognize_path(%w(Content list))) 
    871     assert_equal({'controller' => ::ContentController, 'action' => 'show', 'id' => '10'}, rs.recognize_path(%w(Content show 10))) 
    872  
    873     assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin NewsFeed))) 
    874     assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin News_Feed))) 
     249    assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content")) 
     250    assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) 
     251    assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) 
     252 
     253    # these used to work, before the routes rewrite, but support for this was pulled in the new version... 
     254    #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed")) 
     255    #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed")) 
    875256  end 
    876257 
    877258  def test_both_requirement_and_optional 
    878     rs.draw do 
    879       rs.blog('test/:year', :controller => 'post', :action => 'show', 
     259    rs.draw do |map| 
     260      map.blog('test/:year', :controller => 'post', :action => 'show', 
    880261        :defaults => { :year => nil }, 
    881262        :requirements => { :year => /\d{4}/ } 
    882263      ) 
    883       rs.connect ':controller/:action/:id' 
    884     end 
    885  
    886     assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show') 
    887     assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show', :year => nil) 
    888      
    889     x = setup_for_named_route 
    890     assert_equal({:controller => '/post', :action => 'show'}, 
    891                  x.new.send(:blog_url)) 
     264      map.connect ':controller/:action/:id' 
     265    end 
     266 
     267    assert_equal '/test', rs.generate(:controller => 'post', :action => 'show') 
     268    assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil) 
     269     
     270    x = setup_for_named_route.new 
     271    assert_equal({:controller => 'post', :action => 'show', :use_route => :blog}, 
     272                 x.send(:blog_url)) 
    892273  end 
    893274   
    894275  def test_set_to_nil_forgets 
    895     rs.draw do 
    896       rs.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil 
    897       rs.connect ':controller/:action/:id' 
    898     end 
    899      
    900     assert_equal ['/pages/2005', []]
     276    rs.draw do |map| 
     277      map.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil 
     278      map.connect ':controller/:action/:id' 
     279    end 
     280     
     281    assert_equal '/pages/2005'
    901282      rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005) 
    902     assert_equal ['/pages/2005/6', []]
     283    assert_equal '/pages/2005/6'
    903284      rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6) 
    904     assert_equal ['/pages/2005/6/12', []]
     285    assert_equal '/pages/2005/6/12'
    905286      rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12) 
    906287     
    907     assert_equal ['/pages/2005/6/4', []]
     288    assert_equal '/pages/2005/6/4'
    908289      rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) 
    909290 
    910     assert_equal ['/pages/2005/6', []]
     291    assert_equal '/pages/2005/6'
    911292      rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) 
    912293 
    913     assert_equal ['/pages/2005', []]
     294    assert_equal '/pages/2005'
    914295      rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) 
    915296  end 
    916297   
    917298  def test_url_with_no_action_specified 
    918     rs.draw do 
    919       rs.connect '', :controller => 'content' 
    920       rs.connect ':controller/:action/:id' 
    921     end 
    922      
    923     assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index') 
    924     assert_equal ['/', []], rs.generate(:controller => 'content') 
     299    rs.draw do |map| 
     300      map.connect '', :controller => 'content' 
     301      map.connect ':controller/:action/:id' 
     302    end 
     303     
     304    assert_equal '/', rs.generate(:controller => 'content', :action => 'index') 
     305    assert_equal '/', rs.generate(:controller => 'content') 
    925306  end 
    926307 
    927308  def test_named_url_with_no_action_specified 
    928     rs.draw do 
    929       rs.root '', :controller => 'content' 
    930       rs.connect ':controller/:action/:id' 
    931     end 
    932      
    933     assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index') 
    934     assert_equal ['/', []], rs.generate(:controller => 'content') 
    935      
    936     x = setup_for_named_route 
    937     assert_equal({:controller => '/content', :action => 'index'}, 
    938                  x.new.send(:root_url)) 
     309    rs.draw do |map| 
     310      map.root '', :controller => 'content' 
     311      map.connect ':controller/:action/:id' 
     312    end 
     313     
     314    assert_equal '/', rs.generate(:controller => 'content', :action => 'index') 
     315    assert_equal '/', rs.generate(:controller => 'content') 
     316     
     317    x = setup_for_named_route.new 
     318    assert_equal({:controller => 'content', :action => 'index', :use_route => :root}, 
     319                 x.send(:root_url)) 
    939320  end 
    940321   
    941322  def test_url_generated_when_forgetting_action 
    942323    [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash|  
    943       rs.draw do 
    944         rs.root '', hash 
    945         rs.connect ':controller/:action/:id' 
     324      rs.draw do |map| 
     325        map.root '', hash 
     326        map.connect ':controller/:action/:id' 
    946327      end 
    947       assert_equal ['/', []], rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'}) 
    948       assert_equal ['/', []], rs.generate({:controller => 'content'}) 
    949       assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'}) 
     328      assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'}) 
     329      assert_equal '/', rs.generate({:controller => 'content'}) 
     330      assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'}) 
    950331    end 
    951332  end 
    952333   
    953334  def test_named_route_method 
    954     rs.draw do 
    955       assert_raises(ArgumentError) { rs.categories 'categories', :controller => 'content', :action => 'categories' } 
    956        
    957       rs.named_route :categories, 'categories', :controller => 'content', :action => 'categories' 
    958       rs.connect ':controller/:action/:id' 
    959     end 
    960  
    961     assert_equal ['/categories', []], rs.generate(:controller => 'content', :action => 'categories') 
    962     assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'}) 
    963   end 
    964  
    965   def test_named_route_helper_array 
     335    rs.draw do |map| 
     336      map.categories 'categories', :controller => 'content', :action => 'categories' 
     337      map.connect ':controller/:action/:id' 
     338    end 
     339 
     340    assert_equal '/categories', rs.generate(:controller => 'content', :action => 'categories') 
     341    assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'}) 
     342  end 
     343 
     344  def test_named_routes_array 
    966345    test_named_route_method 
    967     assert_equal [:categories_url, :hash_for_categories_url], ::ActionController::Routing::NamedRoutes::Helper
     346    assert_equal [:categories], rs.named_routes.name
    968347  end 
    969348 
    970349  def test_nil_defaults 
    971     rs.draw do 
    972       rs.connect 'journal', 
     350    rs.draw do |map| 
     351      map.connect 'journal', 
    973352        :controller => 'content', 
    974353        :action => 'list_journal', 
    975354        :date => nil, :user_id => nil 
    976       rs.connect ':controller/:action/:id' 
    977     end 
    978  
    979     assert_equal ['/journal', []], rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil) 
     355      map.connect ':controller/:action/:id' 
     356    end 
     357 
     358    assert_equal '/journal', rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil) 
    980359  end 
    981360 
     
    986365 
    987366    rs.draw do |r| 
    988       r.connect '/match', :controller => 'books', :action => 'get', :require => { :method => :get } 
    989       r.connect '/match', :controller => 'books', :action => 'post', :require => { :method => :post } 
    990       r.connect '/match', :controller => 'books', :action => 'put', :require => { :method => :put } 
    991       r.connect '/match', :controller => 'books', :action => 'delete', :require => { :method => :delete } 
     367      r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get } 
     368      r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post } 
     369      r.connect '/match', :controller => 'books', :action => 'put', :conditions => { :method => :put } 
     370      r.connect '/match', :controller => 'books', :action => 'delete', :conditions => { :method => :delete } 
    992371    end 
    993372  end 
     
    1001380 
    1002381        assert_nothing_raised { rs.recognize(@request) } 
    1003         assert_equal request_method.downcase, @request.path_parameters["action"
     382        assert_equal request_method.downcase, @request.path_parameters[:action
    1004383      ensure 
    1005384        Object.send(:remove_const, :BooksController) rescue nil 
     
    1018397    end 
    1019398 
    1020     hash = rs.recognize_path %w(books 17;edit) 
     399    hash = rs.recognize_path "/books/17;edit" 
    1021400    assert_not_nil hash 
    1022     assert_equal %w(subpath_books 17 edit), [hash["controller"].controller_name, hash["id"], hash["action"]] 
    1023      
    1024     hash = rs.recognize_path %w(items 3;complete) 
     401    assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]] 
     402     
     403    hash = rs.recognize_path "/items/3;complete" 
    1025404    assert_not_nil hash 
    1026     assert_equal %w(subpath_books 3 complete), [hash["controller"].controller_name, hash["id"], hash["action"]] 
    1027      
    1028     hash = rs.recognize_path %w(posts new;preview) 
     405    assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]] 
     406     
     407    hash = rs.recognize_path "/posts/new;preview" 
    1029408    assert_not_nil hash 
    1030     assert_equal %w(subpath_books preview), [hash["controller"].controller_name, hash["action"]] 
    1031  
    1032     hash = rs.recognize_path %w(posts 7) 
     409    assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]] 
     410 
     411    hash = rs.recognize_path "/posts/7" 
    1033412    assert_not_nil hash 
    1034     assert_equal %w(subpath_books show 7), [hash["controller"].controller_name, hash["action"], hash["id"]] 
    1035  
    1036     # for now, low-hanging fruit only. We don't allow subpath components anywhere 
    1037     # except at the end of the path 
    1038     assert_raises(ActionController::RoutingError) do 
    1039       rs.draw do |r| 
    1040         r.connect '/books;german/new', :controller => 'subpath_books', :action => "new" 
    1041       end 
    1042     end 
     413    assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]] 
    1043414  ensure 
    1044415    Object.send(:remove_const, :SubpathBooksController) rescue nil 
     
    1054425    end 
    1055426 
    1056     assert_equal ["/books/7;edit", []], rs.generate(:controller => "subpath_books", :id => 7, :action => "edit") 
    1057     assert_equal ["/items/15;complete", []], rs.generate(:controller => "subpath_books", :id => 15, :action => "complete") 
    1058     assert_equal ["/posts/new;preview", []], rs.generate(:controller => "subpath_books", :action => "preview") 
     427    assert_equal "/books/7;edit", rs.generate(:controller => "subpath_books", :id => 7, :action => "edit") 
     428    assert_equal "/items/15;complete", rs.generate(:controller => "subpath_books", :id => 15, :action => "complete") 
     429    assert_equal "/posts/new;preview", rs.generate(:controller => "subpath_books", :action => "preview") 
    1059430  ensure 
    1060431    Object.send(:remove_const, :SubpathBooksController) rescue nil 
     
    1062433end 
    1063434 
     435class SegmentTest < Test::Unit::TestCase 
     436   
     437  def test_first_segment_should_interpolate_for_structure 
     438    s = ROUTING::Segment.new 
     439    def s.interpolation_statement(array) 'hello' end 
     440    assert_equal 'hello', s.continue_string_structure([]) 
     441  end 
     442   
     443  def test_interpolation_statement 
     444    s = ROUTING::StaticSegment.new 
     445    s.value = "Hello" 
     446    assert_equal "Hello", eval(s.interpolation_statement([])) 
     447    assert_equal "HelloHello", eval(s.interpolation_statement([s])) 
     448     
     449    s2 = ROUTING::StaticSegment.new 
     450    s2.value = "-" 
     451    assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2])) 
     452     
     453    s3 = ROUTING::StaticSegment.new 
     454    s3.value = "World" 
     455    assert_equal "Hello-World", eval(s3.interpolation_statement([s, s2])) 
     456  end 
     457   
    1064458end 
     459 
     460class StaticSegmentTest < Test::Unit::TestCase 
     461   
     462  def test_interpolation_chunk_should_respect_raw 
     463    s = ROUTING::StaticSegment.new 
     464    s.value = 'Hello/World' 
     465    assert ! s.raw? 
     466    assert_equal 'Hello/World', CGI.unescape(s.interpolation_chunk) 
     467     
     468    s.raw = true 
     469    assert s.raw? 
     470    assert_equal 'Hello/World', s.interpolation_chunk 
     471  end 
     472   
     473  def test_regexp_chunk_should_escape_specials 
     474    s = ROUTING::StaticSegment.new 
     475     
     476    s.value = 'Hello*World' 
     477    assert_equal 'Hello\*World', s.regexp_chunk 
     478     
     479    s.value = 'HelloWorld' 
     480    assert_equal 'HelloWorld', s.regexp_chunk 
     481  end 
     482   
     483  def test_regexp_chunk_should_add_question_mark_for_optionals 
     484    s = ROUTING::StaticSegment.new 
     485    s.value = "/" 
     486    s.is_optional = true 
     487    assert_equal "/?", s.regexp_chunk 
     488     
     489    s.value = "hello" 
     490    assert_equal "(?:hello)?", s.regexp_chunk 
     491  end 
     492   
     493end 
     494 
     495class DynamicSegmentTest < Test::Unit::TestCase 
     496   
     497  def segment 
     498    unless @segment 
     499      @segment = ROUTING::DynamicSegment.new 
     500      @segment.key = :a 
     501    end 
     502    @segment 
     503  end 
     504   
     505  def test_extract_value 
     506    s = ROUTING::DynamicSegment.new 
     507    s.key = :a 
     508     
     509    hash = {:a => '10', :b => '20'} 
     510    assert_equal '10', eval(s.extract_value) 
     511     
     512    hash = {:b => '20'} 
     513    assert_equal nil, eval(s.extract_value) 
     514     
     515    s.default = '20' 
     516    assert_equal '20', eval(s.extract_value) 
     517  end 
     518   
     519  def test_default_local_name 
     520    assert_equal 'a_value', segment.local_name, 
     521      "Unexpected name -- all value_check tests will fail!" 
     522  end 
     523   
     524  def test_presence_value_check 
     525    a_value = 10 
     526    assert eval(segment.value_check) 
     527  end 
     528   
     529  def test_regexp_value_check_rejects_nil 
     530    segment.regexp = /\d+/ 
     531    a_value = nil 
     532    assert ! eval(segment.value_check) 
     533  end 
     534   
     535  def test_optional_regexp_value_check_should_accept_nil 
     536    segment.regexp = /\d+/ 
     537    segment.is_optional = true 
     538    a_value = nil 
     539    assert eval(segment.value_check) 
     540  end 
     541   
     542  def test_regexp_value_check_rejects_no_match 
     543    segment.regexp = /\d+/ 
     544     
     545    a_value = "Hello20World" 
     546    assert ! eval(segment.value_check) 
     547     
     548    a_value = "20Hi" 
     549    assert ! eval(segment.value_check) 
     550  end 
     551   
     552  def test_regexp_value_check_accepts_match 
     553    segment.regexp = /\d+/ 
     554     
     555    a_value = "30" 
     556    assert eval(segment.value_check) 
     557  end 
     558   
     559  def test_value_check_fails_on_nil 
     560    a_value = nil 
     561    assert ! eval(segment.value_check) 
     562  end 
     563   
     564  def test_optional_value_needs_no_check 
     565    segment.is_optional = true 
     566    a_value = nil 
     567    assert_equal nil, segment.value_check 
     568  end 
     569   
     570  def test_regexp_value_check_should_accept_match_with_default 
     571    segment.regexp = /\d+/ 
     572    segment.default = '200' 
     573     
     574    a_value = '100' 
     575    assert eval(segment.value_check) 
     576  end 
     577   
     578  def test_expiry_should_not_trigger_once_expired 
     579    not_expired = false 
     580    hash = merged = {:a => 2, :b => 3} 
     581    options = {:b => 3} 
     582    expire_on = Hash.new { raise 'No!!!' } 
     583     
     584    eval(segment.expiry_statement) 
     585  rescue RuntimeError 
     586    flunk "Expiry check should not have occured!" 
     587  end 
     588   
     589  def test_expiry_should_occur_according_to_expire_on 
     590    not_expired = true 
     591    hash = merged = {:a => 2, :b => 3} 
     592    options = {:b => 3} 
     593     
     594    expire_on = {:b => true, :a => false} 
     595    eval(segment.expiry_statement) 
     596    assert not_expired 
     597    assert_equal({:a => 2, :b => 3}, hash) 
     598     
     599    expire_on = {:b => true, :a => true} 
     600    eval(segment.expiry_statement) 
     601    assert ! not_expired 
     602    assert_equal({:b => 3}, hash) 
     603  end 
     604   
     605  def test_extraction_code_should_return_on_nil 
     606    hash = merged = {:b => 3} 
     607    options = {:b => 3} 
     608    a_value = nil 
     609     
     610    # Local jump because of return inside eval. 
     611    assert_raises(LocalJumpError) { eval(segment.extraction_code) } 
     612  end 
     613   
     614  def test_extraction_code_should_return_on_mismatch 
     615    segment.regexp = /\d+/ 
     616    hash = merged = {:a => 'Hi', :b => '3'} 
     617    options = {:b => '3'} 
     618    a_value = nil 
     619     
     620    # Local jump because of return inside eval. 
     621    assert_raises(LocalJumpError) { eval(segment.extraction_code) } 
     622  end 
     623   
     624  def test_extraction_code_should_accept_value_and_set_local 
     625    hash = merged = {:a => 'Hi', :b => '3'} 
     626    options = {:b => '3'} 
     627    a_value = nil 
     628     
     629    eval(segment.extraction_code) 
     630    assert_equal 'Hi', a_value 
     631  end 
     632   
     633  def test_extraction_should_work_without_value_check 
     634    segment.default = 'hi' 
     635    hash = merged = {:b => '3'} 
     636    options = {:b => '3'} 
     637    a_value = nil 
     638     
     639    eval(segment.extraction_code) 
     640    assert_equal 'hi', a_value 
     641  end 
     642   
     643  def test_extraction_code_should_perform_expiry 
     644    not_expired = true 
     645    hash = merged = {:a => 'Hi', :b => '3'} 
     646    options = {:b => '3'} 
     647    expire_on = {:a => true} 
     648    a_value = nil 
     649     
     650    eval(segment.extraction_code) 
     651    assert_equal 'Hi', a_value 
     652    assert ! not_expired 
     653    assert_equal options, hash 
     654  end 
     655   
     656  def test_interpolation_chunk_should_replace_value 
     657    a_value = 'Hi' 
     658    assert_equal a_value, eval(%("#{segment.interpolation_chunk}")) 
     659  end 
     660   
     661  def test_value_regexp_should_be_nil_without_regexp 
     662    assert_equal nil, segment.value_regexp 
     663  end 
     664   
     665  def test_value_regexp_should_match_exacly 
     666    segment.regexp = /\d+/ 
     667    assert_no_match segment.value_regexp, "Hello 10 World" 
     668    assert_no_match segment.value_regexp, "Hello 10" 
     669    assert_no_match segment.value_regexp, "10 World" 
     670    assert_match segment.value_regexp, "10" 
     671  end 
     672   
     673  def test_regexp_chunk_should_return_string 
     674    segment.regexp = /\d+/ 
     675    assert_kind_of String, segment.regexp_chunk 
     676  end 
     677   
     678end 
     679 
     680class ControllerSegmentTest < Test::Unit::TestCase 
     681   
     682  def test_regexp_should_only_match_possible_controllers 
     683    ActionController::Routing.with_controllers %w(admin/accounts admin/users account pages) do 
     684      cs = ROUTING::ControllerSegment.new :controller 
     685      regexp = %r{\A#{cs.regexp_chunk}\Z} 
     686       
     687      ActionController::Routing.possible_controllers.each do |name| 
     688        assert_match regexp, name 
     689        assert_no_match regexp, "#{name}_fake" 
     690         
     691        match = regexp.match name 
     692        assert_equal name, match[1] 
     693      end 
     694    end 
     695  end 
     696   
     697end 
     698 
     699class RouteTest < Test::Unit::TestCase 
     700 
     701  def setup 
     702    @route = ROUTING::Route.new 
     703  end 
     704 
     705  def slash_segment(is_optional = false) 
     706    returning ROUTING::DividerSegment.new('/') do |s| 
     707      s.is_optional = is_optional 
     708    end 
     709  end 
     710   
     711  def default_route 
     712    unless @default_route 
     713      @default_route = ROUTING::Route.new 
     714       
     715      @default_route.segments << (s = ROUTING::StaticSegment.new) 
     716      s.value = '/' 
     717      s.raw = true 
     718       
     719      @default_route.segments << (s = ROUTING::DynamicSegment.new) 
     720      s.key = :controller 
     721       
     722      @default_route.segments << slash_segment(:optional) 
     723      @default_route.segments << (s = ROUTING::DynamicSegment.new) 
     724      s.key = :action 
     725      s.default = 'index' 
     726      s.is_optional = true 
     727       
     728      @default_route.segments << slash_segment(:optional) 
     729      @default_route.segments << (s = ROUTING::DynamicSegment.new) 
     730      s.key = :id 
     731      s.is_optional = true 
     732       
     733      @default_route.segments << slash_segment(:optional) 
     734    end 
     735    @default_route 
     736  end 
     737 
     738  def test_default_route_recognition 
     739    expected = {:controller => 'accounts', :action => 'show', :id => '10'} 
     740    assert_equal expected, default_route.recognize('/accounts/show/10') 
     741    assert_equal expected, default_route.recognize('/accounts/show/10/') 
     742     
     743    expected[:id] = 'jamis' 
     744    assert_equal expected, default_route.recognize('/accounts/show/jamis/') 
     745     
     746    expected.delete :id 
     747    assert_equal expected, default_route.recognize('/accounts/show') 
     748    assert_equal expected, default_route.recognize('/accounts/show/') 
     749     
     750    expected[:action] = 'index' 
     751    assert_equal expected, default_route.recognize('/accounts/') 
     752    assert_equal expected, default_route.recognize('/accounts') 
     753     
     754    assert_equal nil, default_route.recognize('/') 
     755    assert_equal nil, default_route.recognize('/accounts/how/goood/it/is/to/be/free') 
     756  end 
     757   
     758  def test_default_route_should_omit_default_action 
     759    o = {:controller => 'accounts', :action => 'index'} 
     760    assert_equal '/accounts', default_route.generate(o, o, {}) 
     761  end 
     762   
     763  def test_default_route_should_include_default_action_when_id_present 
     764    o = {:controller => 'accounts', :action => 'index', :id => '20'} 
     765    assert_equal '/accounts/index/20', default_route.generate(o, o, {}) 
     766  end 
     767   
     768  def test_default_route_should_work_with_action_but_no_id 
     769    o = {:controller => 'accounts', :action => 'list_all'} 
     770    assert_equal '/accounts/list_all', default_route.generate(o, o, {}) 
     771  end 
     772   
     773  def test_parameter_shell 
     774    page_url = ROUTING::Route.new 
     775    page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/} 
     776    assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell) 
     777  end 
     778 
     779  def test_defaults 
     780    route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html" 
     781    assert_equal( 
     782      { :controller => "users", :action => "show", :format => "html" }, 
     783      route.defaults) 
     784  end 
     785 
     786  def test_significant_keys_for_default_route 
     787    keys = default_route.significant_keys.sort_by {|k| k.to_s } 
     788    assert_equal [:action, :controller, :id], keys 
     789  end 
     790   
     791  def test_significant_keys 
     792    user_url = ROUTING::Route.new 
     793    user_url.segments << (s = ROUTING::StaticSegment.new) 
     794    s.value = '/' 
     795    s.raw = true 
     796     
     797    user_url.segments << (s = ROUTING::StaticSegment.new) 
     798    s.value = 'user' 
     799     
     800    user_url.segments << (s = ROUTING::StaticSegment.new) 
     801    s.value = '/' 
     802    s.raw = true 
     803    s.is_optional = true 
     804     
     805    user_url.segments << (s = ROUTING::DynamicSegment.new) 
     806    s.key = :user 
     807     
     808    user_url.segments << (s = ROUTING::StaticSegment.new) 
     809    s.value = '/' 
     810    s.raw = true 
     811    s.is_optional = true 
     812     
     813    user_url.requirements = {:controller => 'users', :action => 'show'} 
     814     
     815    keys = user_url.significant_keys.sort_by { |k| k.to_s } 
     816    assert_equal [:action, :controller, :user], keys 
     817  end 
     818 
     819  def test_build_empty_query_string 
     820    assert_equal '', @route.build_query_string({}) 
     821  end 
     822 
     823  def test_simple_build_query_string 
     824    assert_equal '?x=1&y=2', @route.build_query_string(:x => '1', :y => '2') 
     825  end 
     826 
     827  def test_convert_ints_build_query_string 
     828    assert_equal '?x=1&y=2', @route.build_query_string(:x => 1, :y => 2) 
     829  end 
     830 
     831  def test_escape_spaces_build_query_string 
     832    assert_equal '?x=hello+world&y=goodbye+world', @route.build_query_string(:x => 'hello world', :y => 'goodbye world') 
     833  end 
     834 
     835  def test_expand_array_build_query_string 
     836    assert_equal '?x[]=1&x[]=2', @route.build_query_string(:x => [1, 2]) 
     837  end 
     838 
     839  def test_escape_spaces_build_query_string_selected_keys 
     840    assert_equal '?x=hello+world', @route.build_query_string({:x => 'hello world', :y => 'goodbye world'}, [:x]) 
     841  end 
     842end 
     843 
     844class RouteBuilderTest < Test::Unit::TestCase 
     845   
     846  def builder 
     847    @bulider ||= ROUTING::RouteBuilder.new 
     848  end 
     849   
     850  def test_segment_for_static 
     851    segment, rest = builder.segment_for 'ulysses' 
     852    assert_equal '', rest 
     853    assert_kind_of ROUTING::StaticSegment, segment 
     854    assert_equal 'ulysses', segment.value 
     855  end 
     856   
     857  def test_segment_for_action 
     858    segment, rest = builder.segment_for ':action' 
     859    assert_equal '', rest 
     860    assert_kind_of ROUTING::DynamicSegment, segment 
     861    assert_equal :action, segment.key 
     862    assert_equal 'index', segment.default 
     863  end 
     864   
     865  def test_segment_for_dynamic 
     866    segment, rest = builder.segment_for ':login' 
     867    assert_equal '', rest 
     868    assert_kind_of ROUTING::DynamicSegment, segment 
     869    assert_equal :login, segment.key 
     870    assert_equal nil, segment.default 
     871    assert ! segment.optional? 
     872  end 
     873   
     874  def test_segment_for_with_rest 
     875    segment, rest = builder.segment_for ':login/:action' 
     876    assert_equal :login, segment.key 
     877    assert_equal '/:action', rest 
     878    segment, rest = builder.segment_for rest 
     879    assert_equal '/', segment.value 
     880    assert_equal ':action', rest 
     881    segment, rest = builder.segment_for rest 
     882    assert_equal :action, segment.key 
     883    assert_equal '', rest 
     884  end 
     885   
     886  def test_segments_for 
     887    segments = builder.segments_for_route_path '/:controller/:action/:id' 
     888     
     889    assert_kind_of ROUTING::DividerSegment, segments[0] 
     890    assert_equal '/', segments[2].value 
     891     
     892    assert_kind_of ROUTING::DynamicSegment, segments[1] 
     893    assert_equal :controller, segments[1].key 
     894     
     895    assert_kind_of ROUTING::DividerSegment, segments[2] 
     896    assert_equal '/', segments[2].value 
     897     
     898    assert_kind_of ROUTING::DynamicSegment, segments[3] 
     899    assert_equal :action, segments[3].key 
     900     
     901    assert_kind_of ROUTING::DividerSegment, segments[4] 
     902    assert_equal '/', segments[4].value 
     903     
     904    assert_kind_of ROUTING::DynamicSegment, segments[5] 
     905    assert_equal :id, segments[5].key 
     906  end 
     907   
     908  def test_segment_for_action 
     909    s, r = builder.segment_for(':action/something/else') 
     910    assert_equal '/something/else', r 
     911    assert_equal 'index', s.default 
     912    assert_equal :action, s.key 
     913  end 
     914   
     915  def test_action_default_should_not_trigger_on_prefix 
     916    s, r = builder.segment_for ':action_name/something/else' 
     917    assert_equal '/something/else', r 
     918    assert_equal :action_name, s.key 
     919    assert_equal nil, s.default 
     920  end 
     921   
     922  def test_divide_route_options 
     923    segments = builder.segments_for_route_path '/cars/:action/:person/:car/' 
     924    defaults, requirements = builder.divide_route_options(segments, 
     925      :action => 'buy', :person => /\w+/, :car => /\w+/, 
     926      :defaults => {:person => nil, :car => nil} 
     927    ) 
     928     
     929    assert_equal({:action => 'buy', :person => nil, :car => nil}, defaults) 
     930    assert_equal({:person => /\w+/, :car => /\w+/}, requirements) 
     931  end 
     932   
     933  def test_assign_route_options 
     934    segments = builder.segments_for_route_path '/cars/:action/:person/:car/' 
     935    defaults = {:action => 'buy', :person => nil, :car => nil} 
     936    requirements = {:person => /\w+/, :car => /\w+/} 
     937     
     938    route_requirements = builder.assign_route_options(segments, defaults, requirements) 
     939    assert_equal({}, route_requirements) 
     940     
     941    assert_equal :action, segments[3].key 
     942    assert_equal 'buy', segments[3].default 
     943     
     944    assert_equal :person, segments[5].key 
     945    assert_equal %r/\w+/, segments[5].regexp 
     946    assert segments[5].optional? 
     947     
     948    assert_equal :car, segments[7].key 
     949    assert_equal %r/\w+/, segments[7].regexp 
     950    assert segments[7].optional? 
     951  end 
     952 
     953  def test_optional_segments_preceding_required_segments 
     954    segments = builder.segments_for_route_path '/cars/:action/:person/:car/' 
     955    defaults = {:action => 'buy', :person => nil, :car => "model-t"} 
     956    assert builder.assign_route_options(segments, defaults, {}).empty? 
     957     
     958    0.upto(1) { |i| assert !segments[i].optional?, "segment #{i} is optional and it shouldn't be" } 
     959    assert segments[2].optional? 
     960     
     961    assert_equal nil, builder.warn_output # should only warn on the :person segment 
     962  end 
     963   
     964  def test_segmentation_of_semicolon_path 
     965    segments = builder.segments_for_route_path '/books/:id;:action' 
     966    defaults = { :action => 'show' } 
     967    assert builder.assign_route_options(segments, defaults, {}).empty? 
     968    segments.each do |segment| 
     969      assert ! segment.optional? || segment.key == :action 
     970    end 
     971  end 
     972   
     973  def test_segmentation_of_dot_path 
     974    segments = builder.segments_for_route_path '/books/:action.rss' 
     975    assert builder.assign_route_options(segments, {}, {}).empty? 
     976    assert_equal 6, segments.length # "/", "books", "/", ":action", ".", "rss" 
     977    assert !segments.any? { |seg| seg.optional? } 
     978  end 
     979   
     980  def test_segmentation_of_dynamic_dot_path 
     981    segments = builder.segments_for_route_path '/books/:action.:format' 
     982    assert builder.assign_route_options(segments, {}, {}).empty? 
     983    assert_equal 6, segments.length # "/", "books", "/", ":action", ".", ":format" 
     984    assert !segments.any? { |seg| seg.optional? } 
     985    assert_kind_of ROUTING::DynamicSegment, segments.last 
     986  end 
     987   
     988  def test_assignment_of_is_optional_when_default 
     989    segments = builder.segments_for_route_path '/books/:action.rss' 
     990    assert_equal segments[3].key, :action 
     991    segments[3].default = 'changes' 
     992    builder.ensure_required_segments(segments) 
     993    assert ! segments[3].optional? 
     994  end 
     995   
     996  def test_is_optional_is_assigned_to_default_segments 
     997    segments = builder.segments_for_route_path '/books/:action' 
     998    builder.assign_route_options(segments, {:action => 'index'}, {}) 
     999     
     1000    assert_equal segments[3].key, :action 
     1001    assert segments[3].optional? 
     1002    assert_kind_of ROUTING::DividerSegment, segments[2] 
     1003    assert segments[2].optional? 
     1004  end 
     1005   
     1006  # XXX is optional not being set right? 
     1007  # /blah/:defaulted_segment <-- is the second slash optional? it should be. 
     1008   
     1009  def test_route_build 
     1010    ActionController::Routing.with_controllers %w(users pages) do 
     1011      r = builder.build '/:controller/:action/:id/', :action => nil 
     1012       
     1013      [0, 2, 4].each do |i| 
     1014        assert_kind_of ROUTING::DividerSegment, r.segments[i] 
     1015        assert_equal '/', r.segments[i].value 
     1016        assert r.segments[i].optional? if i > 1 
     1017      end 
     1018       
     1019      assert_kind_of ROUTING::DynamicSegment, r.segments[1] 
     1020      assert_equal :controller, r.segments[1].key 
     1021      assert_equal nil, r.segments[1].default 
     1022       
     1023      assert_kind_of ROUTING::DynamicSegment, r.segments[3] 
     1024      assert_equal :action, r.segments[3].key 
     1025      assert_equal 'index', r.segments[3].default 
     1026       
     1027      assert_kind_of ROUTING::DynamicSegment, r.segments[5] 
     1028      assert_equal :id, r.segments[5].key 
     1029      assert r.segments[5].optional? 
     1030    end 
     1031  end 
     1032   
     1033  def test_slashes_are_implied 
     1034    routes = [ 
     1035      builder.build('/:controller/:action/:id/', :action => nil), 
     1036      builder.build('/:controller/:action/:id', :action => nil), 
     1037      builder.build(':controller/:action/:id', :action => nil), 
     1038      builder.build('/:controller/:action/:id/', :action => nil) 
     1039    ] 
     1040    expected = routes.first.segments.length 
     1041    routes.each_with_index do |route, i| 
     1042      found = route.segments.length 
     1043      assert_equal expected, found, "Route #{i + 1} has #{found} segments, expected #{expected}" 
     1044    end 
     1045  end 
     1046   
     1047end 
     1048 
     1049class RouteSetTest < Test::Unit::TestCase 
     1050  class MockController 
     1051    attr_accessor :routes 
     1052 
     1053    def initialize(routes) 
     1054      self.routes = routes 
     1055    end 
     1056 
     1057    def url_for(options) 
     1058      path = routes.generate(options) 
     1059      "http://named.route.test#{path}" 
     1060    end 
     1061  end 
     1062 
     1063  class MockRequest 
     1064    attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method 
     1065 
     1066    def initialize(values={}) 
     1067      values.each { |key, value| send("#{key}=", value) } 
     1068      if values[:host] 
     1069        subdomain, self.domain = values[:host].split(/\./, 2) 
     1070        self.subdomains = [subdomain] 
     1071      end 
     1072    end 
     1073  end 
     1074 
     1075  def set 
     1076    @set ||= ROUTING::RouteSet.new 
     1077  end 
     1078 
     1079  def request 
     1080    @request ||= MockRequest.new(:host => "named.routes.test", :method => :get) 
     1081  end 
     1082 
     1083  def test_generate_extras 
     1084    set.draw { |m| m.connect ':controller/:action/:id' } 
     1085    path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") 
     1086    assert_equal "/foo/bar/15", path 
     1087    assert_equal %w(that this), extras.map(&:to_s).sort 
     1088  end 
     1089 
     1090  def test_extra_keys 
     1091    set.draw { |m| m.connect ':controller/:action/:id' } 
     1092    extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world") 
     1093    assert_equal %w(that this), extras.map(&:to_s).sort 
     1094  end 
     1095 
     1096  def test_draw 
     1097    assert_equal 0, set.routes.size 
     1098    set.draw do |map| 
     1099      map.connect '/hello/world', :controller => 'a', :action => 'b' 
     1100    end 
     1101    assert_equal 1, set.routes.size 
     1102  end 
     1103   
     1104  def test_named_draw 
     1105    assert_equal 0, set.routes.size 
     1106    set.draw do |map| 
     1107      map.hello '/hello/world', :controller => 'a', :action => 'b' 
     1108    end 
     1109    assert_equal 1, set.routes.size 
     1110    assert_equal set.routes.first, set.named_routes[:hello] 
     1111  end 
     1112   
     1113  def test_later_named_routes_take_precedence 
     1114    set.draw do |map| 
     1115      map.hello '/hello/world', :controller => 'a', :action => 'b' 
     1116      map.hello '/hello', :controller => 'a', :action => 'b' 
     1117    end 
     1118    assert_equal set.routes.last, set.named_routes[:hello] 
     1119  end 
     1120 
     1121  def setup_named_route_test 
     1122    set.draw do |map| 
     1123      map.show '/people/:id', :controller => 'people', :action => 'show' 
     1124      map.index '/people', :controller => 'people', :action => 'index' 
     1125      map.multi '/people/go/:foo/:bar/joe/:id', :controller => 'people', :action => 'multi' 
     1126    end 
     1127 
     1128    klass = Class.new(MockController) 
     1129    set.named_routes.install(klass) 
     1130    klass.new(set) 
     1131  end 
     1132 
     1133  def test_named_route_hash_access_method 
     1134    controller = setup_named_route_test 
     1135 
     1136    assert_equal( 
     1137      { :controller => 'people', :action => 'show', :id => 5, :use_route => :show }, 
     1138      controller.send(:hash_for_show_url, :id => 5)) 
     1139 
     1140    assert_equal( 
     1141      { :controller => 'people', :action => 'index', :use_route => :index }, 
     1142      controller.send(:hash_for_index_url)) 
     1143  end 
     1144 
     1145  def test_named_route_url_method 
     1146    controller = setup_named_route_test 
     1147 
     1148    assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5) 
     1149    assert_equal "http://named.route.test/people", controller.send(:index_url) 
     1150  end 
     1151 
     1152  def test_namd_route_url_method_with_ordered_parameters 
     1153    controller = setup_named_route_test 
     1154    assert_equal "http://named.route.test/people/go/7/hello/joe/5", 
     1155      controller.send(:multi_url, 7, "hello", 5) 
     1156  end 
     1157   
     1158  def test_draw_default_route 
     1159    ActionController::Routing.with_controllers(['users']) do 
     1160      set.draw do |map| 
     1161        map.connect '/:controller/:action/:id' 
     1162      end 
     1163       
     1164      assert_equal 1, set.routes.size 
     1165      route = set.routes.first 
     1166       
     1167      assert route.segments.last.optional? 
     1168       
     1169      assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) 
     1170      assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) 
     1171       
     1172      assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) 
     1173      assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) 
     1174    end 
     1175  end 
     1176   
     1177  def test_route_with_parameter_shell 
     1178    ActionController::Routing.with_controllers(['users', 'pages']) do 
     1179      set.draw do |map| 
     1180        map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ 
     1181        map.connect '/:controller/:action/:id' 
     1182      end 
     1183       
     1184      assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) 
     1185      assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) 
     1186      assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) 
     1187       
     1188      assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) 
     1189      assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) 
     1190    end 
     1191  end 
     1192 
     1193  def test_recognize_with_conditions 
     1194    Object.const_set(:PeopleController, Class.new) 
     1195 
     1196    set.draw do |map| 
     1197      map.with_options(:controller => "people") do |people| 
     1198        people.people  "/people",     :action => "index",   :conditions => { :method => :get } 
     1199        people.connect "/people",     :action => "create",  :conditions => { :method => :post } 
     1200        people.person  "/people/:id", :action => "show",    :conditions => { :method => :get } 
     1201        people.connect "/people/:id", :action => "update",  :conditions => { :method => :put } 
     1202        people.connect "/people/:id", :action => "destroy", :conditions => { :method => :delete } 
     1203      end 
     1204    end 
     1205 
     1206    request.path = "/people" 
     1207    request.method = :get 
     1208    assert_nothing_raised { set.recognize(request) } 
     1209    assert_equal("index", request.path_parameters[:action]) 
     1210     
     1211    request.method = :post 
     1212    assert_nothing_raised { set.recognize(request) } 
     1213    assert_equal("create", request.path_parameters[:action]) 
     1214 
     1215    request.path = "/people/5" 
     1216    request.method = :get 
     1217    assert_nothing_raised { set.recognize(request) } 
     1218    assert_equal("show", request.path_parameters[:action]) 
     1219    assert_equal("5", request.path_parameters[:id]) 
     1220 
     1221    request.method = :put 
     1222    assert_nothing_raised { set.recognize(request) } 
     1223    assert_equal("update", request.path_parameters[:action]) 
     1224    assert_equal("5", request.path_parameters[:id]) 
     1225 
     1226    request.method = :delete 
     1227    assert_nothing_raised { set.recognize(request) } 
     1228    assert_equal("destroy", request.path_parameters[:action]) 
     1229    assert_equal("5", request.path_parameters[:id]) 
     1230  ensure 
     1231    Object.send(:remove_const, :PeopleController) 
     1232  end 
     1233 
     1234  def test_recognize_with_conditions_and_format 
     1235    Object.const_set(:PeopleController, Class.new) 
     1236 
     1237    set.draw do |map| 
     1238      map.with_options(:controller => "people") do |people| 
     1239        people.person  "/people/:id", :action => "show",    :conditions => { :method => :get } 
     1240        people.connect "/people/:id", :action => "update",  :conditions => { :method => :put } 
     1241        people.connect "/people/:id.:_format", :action => "show", :conditions => { :method => :get } 
     1242      end 
     1243    end 
     1244 
     1245    request.path = "/people/5" 
     1246    request.method = :get 
     1247    assert_nothing_raised { set.recognize(request) } 
     1248    assert_equal("show", request.path_parameters[:action]) 
     1249    assert_equal("5", request.path_parameters[:id]) 
     1250 
     1251    request.method = :put 
     1252    assert_nothing_raised { set.recognize(request) } 
     1253    assert_equal("update", request.path_parameters[:action]) 
     1254 
     1255    request.path = "/people/5.png" 
     1256    request.method = :get 
     1257    assert_nothing_raised { set.recognize(request) } 
     1258    assert_equal("show", request.path_parameters[:action]) 
     1259    assert_equal("5", request.path_parameters[:id]) 
     1260    assert_equal("png", request.path_parameters[:_format]) 
     1261  ensure 
     1262    Object.send(:remove_const, :PeopleController) 
     1263  end 
     1264 
     1265  def test_generate_with_default_action 
     1266    set.draw do |map| 
     1267      map.connect "/people", :controller => "people" 
     1268      map.connect "/people/list", :controller => "people", :action => "list" 
     1269    end 
     1270 
     1271    url = set.generate(:controller => "people", :action => "list") 
     1272    assert_equal "/people/list", url 
     1273  end 
     1274 
     1275  def test_generate_finds_best_fit 
     1276    set.draw do |map| 
     1277      map.connect "/people", :controller => "people", :action => "index" 
     1278      map.connect "/ws/people", :controller => "people", :action => "index", :ws => true 
     1279    end 
     1280 
     1281    url = set.generate(:controller => "people", :action => "index", :ws => true) 
     1282    assert_equal "/ws/people", url 
     1283  end 
     1284end 
     1285 
     1286class RoutingTest < Test::Unit::TestCase 
     1287   
     1288  def test_possible_controllers 
     1289    true_load_paths = $LOAD_PATH.dup 
     1290     
     1291    ActionController::Routing.use_controllers! nil 
     1292    Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures') 
     1293     
     1294    $LOAD_PATH.clear 
     1295    $LOAD_PATH.concat [ 
     1296      RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib' 
     1297    ] 
     1298     
     1299    assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort 
     1300  ensure 
     1301    if true_load_paths 
     1302      $LOAD_PATH.clear 
     1303      $LOAD_PATH.concat true_load_paths 
     1304    end 
     1305    Object.send(:remove_const, :RAILS_ROOT) rescue nil 
     1306  end 
     1307   
     1308  def test_with_controllers 
     1309    c = %w(admin/accounts admin/users account pages) 
     1310    ActionController::Routing.with_controllers c do 
     1311      assert_equal c, ActionController::Routing.possible_controllers 
     1312    end 
     1313  end 
     1314   
     1315end 
  • trunk/actionpack/test/controller/test_test.rb

    r4361 r4394  
    8282    @response   = ActionController::TestResponse.new 
    8383    ActionController::Routing::Routes.reload 
     84    ActionController::Routing.use_controllers! %w(content admin/user) 
    8485  end 
    8586 
     
    318319  def test_array_path_parameter_handled_properly 
    319320    with_routing do |set| 
    320       set.draw do  
    321         set.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params' 
    322         set.connect ':controller/:action/:id' 
     321      set.draw do |map| 
     322        map.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params' 
     323        map.connect ':controller/:action/:id' 
    323324      end 
    324325       
     
    441442    def with_foo_routing 
    442443      with_routing do |set| 
    443         set.draw do 
    444           set.generate_url 'foo', :controller => 'test' 
    445           set.connect      ':controller/:action/:id' 
     444        set.draw do |map| 
     445          map.generate_url 'foo', :controller => 'test' 
     446          map.connect      ':controller/:action/:id' 
    446447        end 
    447448        yield set 
  • trunk/actionpack/test/controller/url_rewriter_test.rb

    r2244 r4394  
    77    @rewriter = ActionController::UrlRewriter.new(@request, @params) 
    88  end  
    9    
    10   def test_simple_build_query_string 
    11     assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2') 
    12   end 
    13   def test_convert_ints_build_query_string 
    14     assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2) 
    15   end 
    16   def test_escape_spaces_build_query_string 
    17     assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world') 
    18   end 
    19   def test_expand_array_build_query_string 
    20     assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2]) 
    21   end 
    22  
    23   def test_escape_spaces_build_query_string_selected_keys 
    24     assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x]) 
    25   end 
    269 
    2710  def test_overwrite_params 
  • trunk/railties/CHANGELOG

    r4383 r4394  
    11*SVN* 
     2 
     3* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck] 
    24 
    35* Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson] 
  • trunk/railties/lib/dispatcher.rb

    r4168 r4394  
    3636        request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi) 
    3737        prepare_application 
    38         ActionController::Routing::Routes.recognize!(request).process(request, response).out(output) 
     38        ActionController::Routing::Routes.recognize(request).process(request, response).out(output) 
    3939      end 
    4040    rescue Object => exception