Changeset 4394
- Timestamp:
- 06/01/06 15:42:08 (3 years ago)
- Files:
-
- trunk/actionpack/CHANGELOG (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/assertions.rb (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/base.rb (modified) (1 diff)
- trunk/actionpack/lib/action_controller/code_generation.rb (deleted)
- trunk/actionpack/lib/action_controller/routing.rb (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/test_process.rb (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/url_rewriter.rb (modified) (1 diff)
- trunk/actionpack/test/controller/action_pack_assertions_test.rb (modified) (2 diffs)
- trunk/actionpack/test/controller/controller_fixtures (added)
- trunk/actionpack/test/controller/controller_fixtures/app (added)
- trunk/actionpack/test/controller/controller_fixtures/app/controllers (added)
- trunk/actionpack/test/controller/controller_fixtures/app/controllers/admin (added)
- trunk/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb (added)
- trunk/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb (added)
- trunk/actionpack/test/controller/controller_fixtures/vendor (added)
- trunk/actionpack/test/controller/controller_fixtures/vendor/plugins (added)
- trunk/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin (added)
- trunk/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib (added)
- trunk/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb (added)
- trunk/actionpack/test/controller/mime_type_test.rb (modified) (1 diff)
- trunk/actionpack/test/controller/routing_test.rb (modified) (15 diffs)
- trunk/actionpack/test/controller/test_test.rb (modified) (3 diffs)
- trunk/actionpack/test/controller/url_rewriter_test.rb (modified) (1 diff)
- trunk/railties/CHANGELOG (modified) (1 diff)
- trunk/railties/lib/dispatcher.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/CHANGELOG
r4388 r4394 1 1 *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 } 2 8 3 9 * Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper] … … 38 44 All this relies on the fact that you have a route that includes .:format. 39 45 40 41 46 * 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] 42 47 trunk/actionpack/lib/action_controller/assertions.rb
r4261 r4394 190 190 ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? 191 191 192 generated_path, extra_keys = ActionController::Routing::Routes.generate (options, extras)192 generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, extras) 193 193 found_extras = options.reject {|k, v| ! extra_keys.include? k} 194 194 … … 366 366 request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method 367 367 request.path = path 368 ActionController::Routing::Routes.recognize!(request) 368 369 ActionController::Routing::Routes.recognize(request) 369 370 request 370 371 end trunk/actionpack/lib/action_controller/base.rb
r4347 r4394 3 3 require 'action_controller/response' 4 4 require 'action_controller/routing' 5 require 'action_controller/code_generation'6 5 require 'action_controller/url_rewriter' 7 6 require 'drb' trunk/actionpack/lib/action_controller/routing.rb
r4381 r4394 1 require 'cgi' 2 require 'pathname' 3 4 class Object 5 def to_param 6 to_s 7 end 8 end 9 10 class TrueClass 11 def to_param 12 self 13 end 14 end 15 16 class FalseClass 17 def to_param 18 self 19 end 20 end 21 22 class NilClass 23 def to_param 24 self 25 end 26 end 27 28 class 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 48 end 49 1 50 module ActionController 2 module Routing #:nodoc: 51 module Routing 52 SEPARATORS = %w( / ; . , ? ) 53 3 54 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 14 88 def controller_relative_to(controller, previous) 15 89 if controller.nil? then previous … … 17 91 elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}" 18 92 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}]" 26 147 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) 161 337 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 340 558 class Result < ::Array #:nodoc: 341 def to_s() join '/' end 559 def to_s() join '/' end 342 560 def self.new_escaped(strings) 343 561 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 377 609 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 440 614 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? $&) 451 619 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 || {} 582 820 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 586 834 end 587 835 end 836 837 url_for(send(hash_access_method, opts)) 588 838 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) 593 999 end 594 1000 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 } 734 1033 end 735 1034 end trunk/actionpack/lib/action_controller/test_process.rb
r4382 r4394 107 107 value = value.to_s 108 108 elsif value.is_a? Array 109 value = ActionController::Routing::Path Component::Result.new(value)109 value = ActionController::Routing::PathSegment::Result.new(value) 110 110 end 111 111 … … 434 434 435 435 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) 437 437 return super 438 438 end trunk/actionpack/lib/action_controller/url_rewriter.rb
r2147 r4394 42 42 end 43 43 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 hash45 44 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) 72 47 end 73 48 end trunk/actionpack/test/controller/action_pack_assertions_test.rb
r4261 r4394 228 228 def test_assert_redirect_to_named_route 229 229 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 234 236 process :redirect_to_named_route 235 237 assert_redirected_to 'http://test.host/route_one' … … 241 243 def test_assert_redirect_to_named_route_failure 242 244 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' 247 249 end 248 250 process :redirect_to_named_route trunk/actionpack/test/controller/mime_type_test.rb
r3917 r4394 6 6 7 7 def test_parse_single 8 p Mime::LOOKUP.keys.sort 8 9 Mime::LOOKUP.keys.each do |mime_type| 9 10 assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type) trunk/actionpack/test/controller/routing_test.rb
r4319 r4394 2 2 require 'test/unit' 3 3 require File.dirname(__FILE__) + '/fake_controllers' 4 require ' stringio'4 require 'action_controller/routing' 5 5 6 6 RunTimeTests = 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 7 ROUTING = ActionController::Routing 8 9 class ROUTING::RouteBuilder 10 attr_reader :warn_output 11 12 def warn(msg) 13 (@warn_output ||= []) << msg 14 end 50 15 end 51 16 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 17 class LegacyRouteSetTests < Test::Unit::TestCase 624 18 attr_reader :rs 625 19 def setup 626 20 @rs = ::ActionController::Routing::RouteSet.new 21 ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) 627 22 @rs.draw {|m| m.connect ':controller/:action/:id' } 628 ::ActionController::Routing::NamedRoutes.clear629 23 end 630 24 631 25 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'}) 645 39 end 646 40 … … 656 50 rectime = Benchmark.realtime do 657 51 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") 664 58 end 665 59 end … … 705 99 end 706 100 707 def test_route_generating_string_literal_in_comparison_warning708 old_stderr = $stderr709 $stderr = StringIO.new710 rs.draw do |map|711 map.connect 'subscriptions/:action/:subscription_type', :controller => "subscriptions"712 end713 assert_equal "", $stderr.string714 ensure715 $stderr = old_stderr716 end717 718 101 def test_route_with_regexp_for_controller 719 102 rs.draw do |map| … … 721 104 map.connect ':controller/:action/:id' 722 105 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") 729 111 end 730 112 731 113 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)) 736 118 end 737 119 738 120 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')) 743 125 end 744 126 745 127 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")) 752 134 753 135 end … … 756 138 x = Class.new 757 139 x.send(:define_method, :url_for) {|x| x} 758 x.send :include, ::ActionController::Routing::NamedRoutes140 rs.named_routes.install(x) 759 141 x 760 142 end … … 762 144 def test_named_route_without_hash 763 145 rs.draw do |map| 764 rs.normal ':controller/:action/:id'146 map.normal ':controller/:action/:id' 765 147 end 766 148 end … … 768 150 def test_named_route_with_regexps 769 151 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', 771 153 :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 775 157 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') 778 160 ) 779 161 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) 782 164 ) 783 165 end 784 166 785 167 def test_changing_controller 786 assert_equal ['/admin/stuff/show/10', []], rs.generate(168 assert_equal '/admin/stuff/show/10', rs.generate( 787 169 {:controller => 'stuff', :action => 'show', :id => 10}, 788 170 {:controller => 'admin/user', :action => 'index'} … … 792 174 def test_paths_escaped 793 175 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" 798 180 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" 802 184 assert results, "Recognition should have succeeded" 803 assert_equal [], results[ 'path']185 assert_equal [], results[:path] 804 186 end 805 187 806 188 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") } 811 193 end 812 194 … … 814 196 assert_raises(ActionController::RoutingError) do 815 197 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' 818 200 end 819 201 end 820 202 821 203 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' 824 206 end 825 207 end … … 827 209 def test_dynamic_path_allowed 828 210 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)) 833 215 end 834 216 835 217 def test_backwards 836 218 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') 844 226 end 845 227 846 228 def test_route_with_fixnum_default 847 229 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")) 862 242 end 863 243 864 244 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'}) 866 246 end 867 247 868 248 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")) 875 256 end 876 257 877 258 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', 880 261 :defaults => { :year => nil }, 881 262 :requirements => { :year => /\d{4}/ } 882 263 ) 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)) 892 273 end 893 274 894 275 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 => nil897 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', 901 282 rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005) 902 assert_equal ['/pages/2005/6', []],283 assert_equal '/pages/2005/6', 903 284 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', 905 286 rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12) 906 287 907 assert_equal ['/pages/2005/6/4', []],288 assert_equal '/pages/2005/6/4', 908 289 rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) 909 290 910 assert_equal ['/pages/2005/6', []],291 assert_equal '/pages/2005/6', 911 292 rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) 912 293 913 assert_equal ['/pages/2005', []],294 assert_equal '/pages/2005', 914 295 rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'}) 915 296 end 916 297 917 298 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') 925 306 end 926 307 927 308 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)) 939 320 end 940 321 941 322 def test_url_generated_when_forgetting_action 942 323 [{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash| 943 rs.draw do 944 rs.root '', hash945 rs.connect ':controller/:action/:id'324 rs.draw do |map| 325 map.root '', hash 326 map.connect ':controller/:action/:id' 946 327 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'}) 950 331 end 951 332 end 952 333 953 334 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 966 345 test_named_route_method 967 assert_equal [:categories _url, :hash_for_categories_url], ::ActionController::Routing::NamedRoutes::Helpers346 assert_equal [:categories], rs.named_routes.names 968 347 end 969 348 970 349 def test_nil_defaults 971 rs.draw do 972 rs.connect 'journal',350 rs.draw do |map| 351 map.connect 'journal', 973 352 :controller => 'content', 974 353 :action => 'list_journal', 975 354 :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) 980 359 end 981 360 … … 986 365 987 366 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 } 992 371 end 993 372 end … … 1001 380 1002 381 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] 1004 383 ensure 1005 384 Object.send(:remove_const, :BooksController) rescue nil … … 1018 397 end 1019 398 1020 hash = rs.recognize_path %w(books 17;edit)399 hash = rs.recognize_path "/books/17;edit" 1021 400 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" 1025 404 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" 1029 408 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" 1033 412 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]] 1043 414 ensure 1044 415 Object.send(:remove_const, :SubpathBooksController) rescue nil … … 1054 425 end 1055 426 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") 1059 430 ensure 1060 431 Object.send(:remove_const, :SubpathBooksController) rescue nil … … 1062 433 end 1063 434 435 class 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 1064 458 end 459 460 class 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 493 end 494 495 class 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 678 end 679 680 class 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 697 end 698 699 class 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 842 end 843 844 class 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 1047 end 1048 1049 class 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 1284 end 1285 1286 class 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 1315 end trunk/actionpack/test/controller/test_test.rb
r4361 r4394 82 82 @response = ActionController::TestResponse.new 83 83 ActionController::Routing::Routes.reload 84 ActionController::Routing.use_controllers! %w(content admin/user) 84 85 end 85 86 … … 318 319 def test_array_path_parameter_handled_properly 319 320 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' 323 324 end 324 325 … … 441 442 def with_foo_routing 442 443 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' 446 447 end 447 448 yield set trunk/actionpack/test/controller/url_rewriter_test.rb
r2244 r4394 7 7 @rewriter = ActionController::UrlRewriter.new(@request, @params) 8 8 end 9 10 def test_simple_build_query_string11 assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2')12 end13 def test_convert_ints_build_query_string14 assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2)15 end16 def test_escape_spaces_build_query_string17 assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world')18 end19 def test_expand_array_build_query_string20 assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2])21 end22 23 def test_escape_spaces_build_query_string_selected_keys24 assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x])25 end26 9 27 10 def test_overwrite_params trunk/railties/CHANGELOG
r4383 r4394 1 1 *SVN* 2 3 * Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck] 2 4 3 5 * Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson] trunk/railties/lib/dispatcher.rb
r4168 r4394 36 36 request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi) 37 37 prepare_application 38 ActionController::Routing::Routes.recognize !(request).process(request, response).out(output)38 ActionController::Routing::Routes.recognize(request).process(request, response).out(output) 39 39 end 40 40 rescue Object => exception