aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG7
-rw-r--r--actionpack/lib/action_controller/assertions.rb5
-rwxr-xr-xactionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/code_generation.rb243
-rw-r--r--actionpack/lib/action_controller/routing.rb1471
-rw-r--r--actionpack/lib/action_controller/test_process.rb4
-rw-r--r--actionpack/lib/action_controller/url_rewriter.rb29
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb16
-rw-r--r--actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb0
-rw-r--r--actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb0
-rw-r--r--actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb0
-rw-r--r--actionpack/test/controller/mime_type_test.rb1
-rw-r--r--actionpack/test/controller/routing_test.rb1811
-rw-r--r--actionpack/test/controller/test_test.rb13
-rw-r--r--actionpack/test/controller/url_rewriter_test.rb17
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/lib/dispatcher.rb2
17 files changed, 1949 insertions, 1673 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 47d5f8fb8c..2af08a5ae2 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,11 @@
*SVN*
+* 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]
+
+ map.connect '/foo/:id', :controller => '...', :action => '...'
+ map.connect '/foo/:id.:format', :controller => '...', :action => '...'
+ map.connect '/foo/:id', ..., :conditions => { :method => :get }
+
* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
@@ -37,7 +43,6 @@
All this relies on the fact that you have a route that includes .:format.
-
* 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]
* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH]
diff --git a/actionpack/lib/action_controller/assertions.rb b/actionpack/lib/action_controller/assertions.rb
index 4e3443c456..a89f515af6 100644
--- a/actionpack/lib/action_controller/assertions.rb
+++ b/actionpack/lib/action_controller/assertions.rb
@@ -189,7 +189,7 @@ module Test #:nodoc:
# Load routes.rb if it hasn't been loaded.
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
- generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
+ generated_path, extra_keys = ActionController::Routing::Routes.generate_extras(options, extras)
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
@@ -365,7 +365,8 @@ module Test #:nodoc:
request = ActionController::TestRequest.new({}, {}, nil)
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
request.path = path
- ActionController::Routing::Routes.recognize!(request)
+
+ ActionController::Routing::Routes.recognize(request)
request
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 098b7265de..0902e70082 100755
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -2,7 +2,6 @@ require 'action_controller/mime_type'
require 'action_controller/request'
require 'action_controller/response'
require 'action_controller/routing'
-require 'action_controller/code_generation'
require 'action_controller/url_rewriter'
require 'drb'
require 'set'
diff --git a/actionpack/lib/action_controller/code_generation.rb b/actionpack/lib/action_controller/code_generation.rb
deleted file mode 100644
index 312dff6be0..0000000000
--- a/actionpack/lib/action_controller/code_generation.rb
+++ /dev/null
@@ -1,243 +0,0 @@
-module ActionController
- module CodeGeneration #:nodoc:
- class GenerationError < StandardError #:nodoc:
- end
-
- class Source #:nodoc:
- attr_reader :lines, :indentation_level
- IndentationString = ' '
- def initialize
- @lines, @indentation_level = [], 0
- end
- def line(line)
- @lines << (IndentationString * @indentation_level + line)
- end
- alias :<< :line
-
- def indent
- @indentation_level += 1
- yield
- ensure
- @indentation_level -= 1
- end
-
- def to_s() lines.join("\n") end
- end
-
- class CodeGenerator #:nodoc:
- attr_accessor :source, :locals
- def initialize(source = nil)
- @locals = []
- @source = source || Source.new
- end
-
- BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym}
- ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym}
- Keywords = BeginKeywords + ResumeKeywords
-
- def method_missing(keyword, *text)
- if Keywords.include? keyword
- if ResumeKeywords.include? keyword
- raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/
- source.lines.pop # Remove the 'end'
- end
-
- line "#{keyword} #{text.join ' '}"
- begin source.indent { yield(self.dup) }
- ensure line 'end'
- end
- else
- super(keyword, *text)
- end
- end
-
- def line(*args) self.source.line(*args) end
- alias :<< :line
- def indent(*args, &block) source(*args, &block) end
- def to_s() source.to_s end
-
- def share_locals_with(other)
- other.locals = self.locals = (other.locals | locals)
- end
-
- FieldsToDuplicate = [:locals]
- def dup
- copy = self.class.new(source)
- self.class::FieldsToDuplicate.each do |sym|
- value = self.send(sym)
- value = value.dup unless value.nil? || value.is_a?(Numeric) || value.is_a?(Symbol)
- copy.send("#{sym}=", value)
- end
- return copy
- end
- end
-
- class RecognitionGenerator < CodeGenerator #:nodoc:
- Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement, :path_name, :base_segment_name, :base_index_name]
- attr_accessor(*Attributes)
- FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
-
- def initialize(*args)
- super(*args)
- @after, @before = [], []
- @current = nil
- @results, @constants = {}, {}
- @depth = 0
- @move_ahead = nil
- @finish_statement = Proc.new {|hash_expr| hash_expr}
- @path_name = :path
- @base_segment_name = :segment
- @base_index_name = :index
- end
-
- def if_next_matches(string, &block)
- test = Routing.test_condition(next_segment(true), string)
- self.if(test, &block)
- end
-
- def move_forward(places = 1)
- dup = self.dup
- dup.depth += 1
- dup.move_ahead = places
- yield dup
- end
-
- def next_segment(assign_inline = false, default = nil)
- if locals.include?(segment_name)
- code = segment_name
- else
- code = "#{segment_name} = #{path_name}[#{index_name}]"
- if assign_inline
- code = "(#{code})"
- else
- line(code)
- code = segment_name
- end
-
- locals << segment_name
- end
- code = "(#{code} || #{default.inspect})" if default
-
- return code.to_s
- end
-
- def segment_name() "#{base_segment_name}#{depth}".to_sym end
- def index_name
- move_ahead, @move_ahead = @move_ahead, nil
- move_ahead ? "#{base_index_name} += #{move_ahead}" : base_index_name
- end
-
- def continue
- dup = self.dup
- dup.before << dup.current
- dup.current = dup.after.shift
- dup.go
- end
-
- def go
- if current then current.write_recognition(self)
- else self.finish
- end
- end
-
- def result(key, expression, delay = false)
- unless delay
- line "#{key}_value = #{expression}"
- expression = "#{key}_value"
- end
- results[key] = expression
- end
- def constant_result(key, object)
- constants[key] = object
- end
-
- def finish(ensure_traversal_finished = true)
- pairs = []
- (results.keys + constants.keys).uniq.each do |key|
- pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}"
- end
- hash_expr = "{#{pairs.join(', ')}}"
-
- statement = finish_statement.call(hash_expr)
- if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement}
- else self << statement
- end
- end
- end
-
- class GenerationGenerator < CodeGenerator #:nodoc:
- Attributes = [:after, :before, :current, :segments, :subpath_at]
- attr_accessor(*Attributes)
- FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
-
- def initialize(*args)
- super(*args)
- @after, @before = [], []
- @current = nil
- @segments = []
- @subpath_at = nil
- end
-
- def hash_name() 'hash' end
- def local_name(key) "#{key}_value" end
-
- def hash_value(key, assign = true, default = nil)
- if locals.include?(local_name(key)) then code = local_name(key)
- else
- code = "hash[#{key.to_sym.inspect}]"
- if assign
- code = "(#{local_name(key)} = #{code})"
- locals << local_name(key)
- end
- end
- code = "(#{code} || (#{default.inspect}))" if default
- return code
- end
-
- def expire_for_keys(*keys)
- return if keys.empty?
- conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"}
- line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}"
- end
-
- def add_segment(*segments)
- d = dup
- d.segments.concat segments
- yield d
- end
-
- def go
- if current then current.write_generation(self)
- else self.finish
- end
- end
-
- def continue
- d = dup
- d.before << d.current
- d.current = d.after.shift
- d.go
- end
-
- def start_subpath!
- @subpath_at ||= segments.length
- end
-
- def finish
- segments[subpath_at..-1] = [segments[subpath_at..-1].join(";")] if subpath_at
- line %("/#{segments.join('/')}")
- end
-
- def check_conditions(conditions)
- tests = []
- generator = nil
- conditions.each do |key, condition|
- tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp
- tests << Routing.test_condition((generator || self).hash_value(key, false), condition)
- generator = self.dup unless generator
- end
- return tests.join(' && ')
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index 67339a6f21..e27520e2e8 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -1,739 +1,1038 @@
+require 'cgi'
+require 'pathname'
+
+class Object
+ def to_param
+ to_s
+ end
+end
+
+class TrueClass
+ def to_param
+ self
+ end
+end
+
+class FalseClass
+ def to_param
+ self
+ end
+end
+
+class NilClass
+ def to_param
+ self
+ end
+end
+
+class Regexp
+ def number_of_captures
+ Regexp.new("|#{source}").match('').captures.length
+ end
+
+ class << self
+ def optionalize(pattern)
+ case unoptionalize(pattern)
+ when /\A(.|\(.*\))\Z/ then "#{pattern}?"
+ else "(?:#{pattern})?"
+ end
+ end
+
+ def unoptionalize(pattern)
+ [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
+ return $1 if regexp =~ pattern
+ end
+ return pattern
+ end
+ end
+end
+
module ActionController
- module Routing #:nodoc:
+ module Routing
+ SEPARATORS = %w( / ; . , ? )
+
class << self
- def expiry_hash(options, recall)
- k = v = nil
- expire_on = {}
- options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
- expire_on
+ def with_controllers(names)
+ use_controllers! names
+ yield
+ ensure
+ use_controllers! nil
+ end
+
+ def possible_controllers
+ unless @possible_controllers
+ @possible_controllers = []
+
+ paths = $LOAD_PATH.select { |path| File.directory? path }
+ paths.collect! { |path| Pathname.new(path).realpath.to_s }
+ paths = paths.sort_by { |path| - path.length }
+
+ seen_paths = Hash.new {|h, k| h[k] = true; false}
+ paths.each do |load_path|
+ Dir["#{load_path}/**/*_controller.rb"].collect do |path|
+ next if seen_paths[path]
+
+ controller_name = path[(load_path.length + 1)..-1]
+ controller_name.gsub!(/_controller\.rb\Z/, '')
+ @possible_controllers << controller_name
+ end
+ end
+ end
+ @possible_controllers
end
- def extract_parameter_value(parameter) #:nodoc:
- CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)
+ def use_controllers!(controller_names)
+ @possible_controllers = controller_names
end
+
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
else controller
- end
+ end
+ end
+ end
+
+ class Route
+ attr_accessor :segments, :requirements, :conditions
+
+ def initialize
+ @segments = []
+ @requirements = {}
+ @conditions = {}
end
+
+ # Write and compile a +generate+ method for this Route.
+ def write_generation
+ # Build the main body of the generation
+ body = "not_expired = true\n#{generation_extraction}\n#{generation_structure}"
+
+ # If we have conditions that must be tested first, nest the body inside an if
+ body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
+ args = "options, hash, expire_on = {}"
+
+ # Nest the body inside of a def block, and then compile it.
+ method_decl = "def generate_raw(#{args})\path = begin\n#{body}\nend\n[path, hash]\nend"
+# puts "\n======================"
+# puts
+# p self
+# puts
+# puts method_decl
+# puts
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+
+ method_decl = "def generate(#{args})\nappend_query_string(*generate_raw(options, hash, expire_on))\nend"
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
- def treat_hash(hash, keys_to_delete = [])
- k = v = nil
- hash.each do |k, v|
- if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
+ method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, hash.keys.map(&:to_sym) - significant_keys]\nend"
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+ end
+
+ # Build several lines of code that extract values from the options hash. If any
+ # of the values are missing or rejected then a return will be executed.
+ def generation_extraction
+ segments.collect do |segment|
+ segment.extraction_code
+ end.compact * "\n"
+ end
+
+ # Produce a condition expression that will check the requirements of this route
+ # upon generation.
+ def generation_requirements
+ requirement_conditions = requirements.collect do |key, req|
+ if req.is_a? Regexp
+ value_regexp = Regexp.new "\\A#{req.source}\\Z"
+ "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
else
- hash.delete k
- keys_to_delete << k
+ "hash[:#{key}] == #{req.inspect}"
end
end
- hash
- end
-
- def test_condition(expression, condition)
- case condition
- when String then "(#{expression} == #{condition.inspect})"
- when Regexp then
- condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
- "(#{condition.inspect} =~ #{expression})"
- when Array then
- conds = condition.collect do |condition|
- cond = test_condition(expression, condition)
- (cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
- end
- "(#{conds.join(' || ')})"
- when true then expression
- when nil then "! #{expression}"
- else
- raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
- end
+ requirement_conditions * ' && ' unless requirement_conditions.empty?
+ end
+ def generation_structure
+ segments.last.string_structure segments[0..-2]
end
- end
-
- class Component #:nodoc:
- def dynamic?() false end
- def optional?() false end
-
- def key() nil end
- def self.new(string, *args)
- return super(string, *args) unless self == Component
- case string
- when /.*;.*/ then SubpathComponent.new(string.split(/;/), *args)
- when ':controller' then ControllerComponent.new(:controller, *args)
- when /^:(\w+)$/ then DynamicComponent.new($1, *args)
- when /^\*(\w+)$/ then PathComponent.new($1, *args)
- else StaticComponent.new(string, *args)
- end
- end
- end
-
- class SubpathComponent < Component #:nodoc:
- attr_reader :parts
+ # Write and compile a +recognize+ method for this Route.
+ def write_recognition
+ # Create an if structure to extract the params from a match if it occurs.
+ body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
+ body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
+
+ # Build the method declaration and compile it
+ method_decl = "def recognize(path, env={})\n#{body}\nend"
+# puts "\n======================"
+# puts
+# p self
+# puts
+# puts method_decl
+# puts
+ instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
+ method_decl
+ end
- def initialize(parts, *args)
- @parts = parts.map { |part| Component.new(part, *args) }
+ # Plugins may override this method to add other conditions, like checks on
+ # host, subdomain, and so forth. Note that changes here only affect route
+ # recognition, not generation.
+ def recognition_conditions
+ result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
+ result << "conditions[:method] === env[:method]" if conditions[:method]
+ result
end
- def write_recognition(g)
- raise RoutingError, "Subpath components must occur last" unless g.after.empty?
- g.if("#{g.next_segment(true)} && #{g.next_segment}.include?(';')") do |gp|
- gp.line "subindex, subpath = 0, #{gp.next_segment}.split(/;/)"
- tweak_recognizer(gp).go
- gp.move_forward { |gpp| gpp.continue }
+ # Build the regular expression pattern that will match this route.
+ def recognition_pattern(wrap = true)
+ pattern = ''
+ segments.reverse_each do |segment|
+ pattern = segment.build_pattern pattern
end
+ wrap ? ("\\A" + pattern + "\\Z") : pattern
end
-
- def write_generation(g)
- raise RoutingError, "Subpath components must occur last" unless g.after.empty?
- tweak_generator(g).go
+
+ # Write the code to extract the parameters from a matched route.
+ def recognition_extraction
+ next_capture = 1
+ extraction = segments.collect do |segment|
+ x = segment.match_extraction next_capture
+ next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
+ x
+ end
+ extraction.compact
end
-
- def key
- parts.map { |p| p.key }
+
+ # Write the real generation implementation and then resend the message.
+ def generate(options, hash, expire_on = {})
+ write_generation
+ generate options, hash, expire_on
end
- private
+ def generate_extras(options, hash, expire_on = {})
+ write_generation
+ generate_extras options, hash, expire_on
+ end
- def tweak_recognizer(g)
- gg = g.dup
+ # Generate the query string with any extra keys in the hash and append
+ # it to the given path, returning the new path.
+ def append_query_string(path, hash)
+ return nil unless path
+ query = hash.keys.map(&:to_sym) - significant_keys
+ "#{path}#{build_query_string(hash, query)}"
+ end
- gg.path_name = :subpath
- gg.base_segment_name = :subsegment
- gg.base_index_name = :subindex
- gg.depth = 0
+ # Build a query string from the keys of the given hash. If +only_keys+
+ # is given (as an array), only the keys indicated will be used to build
+ # the query string. The query string will correctly build array parameter
+ # values.
+ def build_query_string(hash, only_keys=nil)
+ elements = []
- gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || [])
+ only_keys ||= hash.keys
+
+ only_keys.each do |key|
+ value = hash[key]
+ key = CGI.escape key.to_s
+ if value.class == Array
+ key << '[]'
+ else
+ value = [ value ]
+ end
+ value.each { |val| elements << "#{key}=#{CGI.escape(val.to_param.to_s)}" }
+ end
+
+ query_string = "?#{elements.join("&")}" unless elements.empty?
+ query_string || ""
+ end
+
+ # Write the real recognition implementation and then resend the message.
+ def recognize(path, environment={})
+ write_recognition
+ recognize path, environment
+ end
+
+ # A route's parameter shell contains parameter values that are not in the
+ # route's path, but should be placed in the recognized hash.
+ #
+ # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
+ #
+ # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
+ #
+ def parameter_shell
+ @parameter_shell ||= returning({}) do |shell|
+ requirements.each do |key, requirement|
+ shell[key] = requirement unless requirement.is_a? Regexp
+ end
+ end
+ end
+
+ # Return an array containing all the keys that are used in this route. This
+ # includes keys that appear inside the path, and keys that have requirements
+ # placed upon them.
+ def significant_keys
+ @significant_keys ||= returning [] do |sk|
+ segments.each { |segment| sk << segment.key if segment.respond_to? :key }
+ sk.concat requirements.keys
+ sk.uniq!
+ end
+ end
- gg
+ # Return a hash of key/value pairs representing the keys in the route that
+ # have defaults, or which are specified by non-regexp requirements.
+ def defaults
+ @defaults ||= returning({}) do |hash|
+ segments.each do |segment|
+ next unless segment.respond_to? :default
+ hash[segment.key] = segment.default unless segment.default.nil?
+ end
+ requirements.each do |key,req|
+ next if Regexp === req || req.nil?
+ hash[key] = req
+ end
end
+ end
+
+ def matches_controller_and_action?(controller, action)
+ unless @matching_prepared
+ @controller_requirement = requirement_for(:controller)
+ @action_requirement = requirement_for(:action)
+ @matching_prepared = true
+ end
+
+ (@controller_requirement.nil? || @controller_requirement === controller) &&
+ (@action_requirement.nil? || @action_requirement === action)
+ end
- def tweak_generator(g)
- gg = g.dup
- gg.before, gg.current, gg.after = [], parts.first, (parts[1..-1] || [])
- gg.start_subpath!
- gg
+ def to_s
+ @to_s ||= segments.inject("") { |str,s| str << s.to_s }
+ end
+
+ protected
+
+ def requirement_for(key)
+ return requirements[key] if requirements.key? key
+ segments.each do |segment|
+ return segment.regexp if segment.respond_to?(:key) && segment.key == key
end
+ nil
+ end
+
end
- class StaticComponent < Component #:nodoc:
- attr_reader :value
-
- def initialize(value)
- @value = value
+ class Segment
+ attr_accessor :is_optional
+ alias_method :optional?, :is_optional
+
+ def initialize
+ self.is_optional = false
end
- def write_recognition(g)
- g.if_next_matches(value) do |gp|
- gp.move_forward {|gpp| gpp.continue}
+ def extraction_code
+ nil
+ end
+
+ # Continue generating string for the prior segments.
+ def continue_string_structure(prior_segments)
+ if prior_segments.empty?
+ interpolation_statement(prior_segments)
+ else
+ new_priors = prior_segments[0..-2]
+ prior_segments.last.string_structure(new_priors)
end
end
-
- def write_generation(g)
- g.add_segment(value) {|gp| gp.continue }
+
+ # Return a string interpolation statement for this segment and those before it.
+ def interpolation_statement(prior_segments)
+ chunks = prior_segments.collect { |s| s.interpolation_chunk }
+ chunks << interpolation_chunk
+ "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
+ end
+
+ def string_structure(prior_segments)
+ optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
+ end
+
+ # Return an if condition that is true if all the prior segments can be generated.
+ # If there are no optional segments before this one, then nil is returned.
+ def all_optionals_available_condition(prior_segments)
+ optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
+ optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
+ end
+
+ # Recognition
+
+ def match_extraction(next_capture)
+ nil
+ end
+
+ # Warning
+
+ # Returns true if this segment is optional? because of a default. If so, then
+ # no warning will be emitted regarding this segment.
+ def optionality_implied?
+ false
end
+
end
- class DynamicComponent < Component #:nodoc:
- attr_reader :key, :default
- attr_accessor :condition
+ class StaticSegment < Segment
+ attr_accessor :value, :raw
+ alias_method :raw?, :raw
- def dynamic?() true end
- def optional?() @optional end
-
- def default=(default)
- @optional = true
- @default = default
+ def initialize(value = nil)
+ super()
+ self.value = value
end
-
- def initialize(key, options = {})
- @key = key.to_sym
- @optional = false
- default, @condition = options[:default], options[:condition]
- self.default = default if options.key?(:default)
+
+ def interpolation_chunk
+ raw? ? value : CGI.escape(value)
end
-
- def default_check(g)
- presence = "#{g.hash_value(key, !! default)}"
- if default
- "!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
+
+ def regexp_chunk
+ chunk = Regexp.escape value
+ optional? ? Regexp.optionalize(chunk) : chunk
+ end
+
+ def build_pattern(pattern)
+ escaped = Regexp.escape(value)
+ if optional? && ! pattern.empty?
+ "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
+ elsif optional?
+ Regexp.optionalize escaped
else
- "! #{presence}"
+ escaped + pattern
end
end
- def write_generation(g)
- wrote_dropout = write_dropout_generation(g)
- write_continue_generation(g, wrote_dropout)
+ def to_s
+ value
end
+
+ end
- def write_dropout_generation(g)
- return false unless optional? && g.after.all? {|c| c.optional?}
-
- check = [default_check(g)]
- gp = g.dup # Use another generator to write the conditions after the first &&
- # We do this to ensure that the generator will not assume x_value is set. It will
- # not be set if it follows a false condition -- for example, false && (x = 2)
-
- check += gp.after.map {|c| c.default_check gp}
- gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
- true
+ class DividerSegment < StaticSegment
+
+ def initialize(value = nil)
+ super(value)
+ self.raw = true
+ self.is_optional = true
end
-
- def write_continue_generation(g, use_else)
- test = Routing.test_condition(g.hash_value(key, true, default), condition || true)
- check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
-
- g.send(*check) do |gp|
- gp.expire_for_keys(key) unless gp.after.empty?
- add_segments_to(gp) {|gpp| gpp.continue}
- end
+
+ def optionality_implied?
+ true
end
+
+ end
- def add_segments_to(g)
- g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
+ class DynamicSegment < Segment
+ attr_accessor :key, :default, :regexp
+
+ def initialize(key = nil, options = {})
+ super()
+ self.key = key
+ self.default = options[:default] if options.key? :default
+ self.is_optional = true if options[:optional] || options.key?(:default)
end
- def recognition_check(g)
- test_type = [true, nil].include?(condition) ? :presence : :constraint
-
- prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
- check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
-
- g.if(check) {|gp| yield gp, test_type}
+ def to_s
+ ":#{key}"
end
- def write_recognition(g)
- test_type = nil
- recognition_check(g) do |gp, test_type|
- assign_result(gp) {|gpp| gpp.continue}
+ # The local variable name that the value of this segment will be extracted to.
+ def local_name
+ "#{key}_value"
+ end
+
+ def extract_value
+ "#{local_name} = hash[:#{key}] #{"|| #{default.inspect}" if default}"
+ end
+ def value_check
+ if default # Then we know it won't be nil
+ "#{value_regexp.inspect} =~ #{local_name}" if regexp
+ elsif optional?
+ # If we have a regexp check that the value is not given, or that it matches.
+ # If we have no regexp, return nil since we do not require a condition.
+ "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
+ else # Then it must be present, and if we have a regexp, it must match too.
+ "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
end
-
- if optional? && g.after.all? {|c| c.optional?}
- call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
-
- g.send(*call) do |gp|
- assign_default(gp)
- gp.after.each {|c| c.assign_default(gp)}
- gp.finish(false)
- end
+ end
+ def expiry_statement
+ "not_expired, hash = false, options if not_expired && expire_on[:#{key}]"
+ end
+
+ def extraction_code
+ s = extract_value
+ vc = value_check
+ s << "\nreturn [nil,nil] unless #{vc}" if vc
+ s << "\n#{expiry_statement}"
+ end
+
+ def interpolation_chunk
+ "\#{CGI.escape(#{local_name}.to_s)}"
+ end
+
+ def string_structure(prior_segments)
+ if optional? # We have a conditional to do...
+ # If we should not appear in the url, just write the code for the prior
+ # segments. This occurs if our value is the default value, or, if we are
+ # optional, if we have nil as our value.
+ "if #{local_name} == #{default.inspect}\n" +
+ continue_string_structure(prior_segments) +
+ "\nelse\n" + # Otherwise, write the code up to here
+ "#{interpolation_statement(prior_segments)}\nend"
+ else
+ interpolation_statement(prior_segments)
end
end
-
- def assign_result(g, with_default = false)
- g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
- g.move_forward {|gp| yield gp}
+
+ def value_regexp
+ Regexp.new "\\A#{regexp.source}\\Z" if regexp
end
-
- def assign_default(g)
- g.constant_result key, default unless default.nil?
+ def regexp_chunk
+ regexp ? regexp.source : "([^#{Routing::SEPARATORS.join}]+)"
end
+
+ def build_pattern(pattern)
+ chunk = regexp_chunk
+ chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
+ pattern = "#{chunk}#{pattern}"
+ optional? ? Regexp.optionalize(pattern) : pattern
+ end
+ def match_extraction(next_capture)
+ hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]")
+ "params[:#{key}] = match[#{next_capture}] #{hangon}"
+ end
+
+ def optionality_implied?
+ [:action, :id].include? key
+ end
+
end
- class ControllerComponent < DynamicComponent #:nodoc:
- def key() :controller end
-
- def add_segments_to(g)
- g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
- end
-
- def recognition_check(g)
- g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
- g.if('controller_result') do |gp|
- gp << 'controller_value, segments_to_controller = controller_result'
- if condition
- gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
- gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
- gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
- end
- else
- gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
- end
- end
+ class ControllerSegment < DynamicSegment
+ def regexp_chunk
+ possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
+ "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
end
- def assign_result(g)
- g.result key, 'controller_value'
- yield g
+ # Don't CGI.escape the controller name, since it may have slashes in it,
+ # like admin/foo.
+ def interpolation_chunk
+ "\#{#{local_name}.to_s}"
end
- def assign_default(g)
- ControllerComponent.assign_controller(g, default)
+ # Make sure controller names like Admin/Content are correctly normalized to
+ # admin/content
+ def extract_value
+ "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
end
-
- class << self
- def assign_controller(g, controller)
- expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
- g.result :controller, expr, true
- end
- def traverse_to_controller(segments, start_at = 0)
- mod = ::Object
- length = segments.length
- index = start_at
- mod_name = controller_name = segment = nil
-
- while index < length
- return nil unless /^[A-Za-z][A-Za-z\d_]*$/ =~ (segment = segments[index])
- index += 1
-
- mod_name = segment.camelize
- controller_name = "#{mod_name}Controller"
-
- begin
- # We use eval instead of const_get to avoid obtaining values from parent modules.
- controller = eval("mod::#{controller_name}", nil, __FILE__, __LINE__)
- expected_name = "#{mod.name}::#{controller_name}"
-
- # Detect the case when const_get returns an object from a parent namespace.
- if controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) && (mod == Object || controller.name == expected_name)
- return controller, (index - start_at)
- end
- rescue NameError => e
- raise unless /^uninitialized constant .*#{controller_name}$/ =~ e.message
- end
-
- begin
- next_mod = eval("mod::#{mod_name}", nil, __FILE__, __LINE__)
- # Check that we didn't get a module from a parent namespace
- mod = (mod == Object || next_mod.name == "#{mod.name}::#{mod_name}") ? next_mod : nil
- rescue NameError => e
- raise unless /^uninitialized constant .*#{mod_name}$/ =~ e.message
- end
-
- return nil unless mod
- end
- end
+ def match_extraction(next_capture)
+ hangon = (default ? "|| #{default.inspect}" : "if match[#{next_capture}]")
+ "params[:#{key}] = match[#{next_capture}].downcase #{hangon}"
end
end
- class PathComponent < DynamicComponent #:nodoc:
- def optional?() true end
- def default() [] end
- def condition() nil end
+ class PathSegment < DynamicSegment
+ EscapedSlash = CGI.escape("/")
+ def interpolation_chunk
+ "\#{CGI.escape(#{local_name}).gsub(#{EscapedSlash.inspect}, '/')}"
+ end
- def default=(value)
- raise RoutingError, "All path components have an implicit default of []" unless value == []
+ def default
+ ''
end
-
- def write_generation(g)
- raise RoutingError, 'Path components must occur last' unless g.after.empty?
- g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
- g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
- g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
- end
- g.else { g.finish }
+
+ def default=(path)
+ raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
end
-
- def write_recognition(g)
- raise RoutingError, "Path components must occur last" unless g.after.empty?
-
- start = g.index_name.to_s
- start = "(#{start})" unless /^\w+$/ =~ start.to_s
-
- value_expr = "#{g.path_name}[#{start}..-1] || []"
- g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
- g.finish(false)
+
+ def match_extraction(next_capture)
+ "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
end
-
+
+ def regexp_chunk
+ regexp || "(.*)"
+ end
+
class Result < ::Array #:nodoc:
- def to_s() join '/' end
+ def to_s() join '/' end
def self.new_escaped(strings)
new strings.collect {|str| CGI.unescape str}
- end
- end
+ end
+ end
end
- class Route #:nodoc:
- attr_accessor :components, :known
- attr_reader :path, :options, :keys, :defaults
+ class RouteBuilder
+ attr_accessor :separators, :optional_separators
- def initialize(path, options = {})
- @path, @options = path, options
-
- initialize_components path
- defaults, conditions = initialize_hashes options.dup
- @defaults = defaults.dup
- @request_method = conditions.delete(:method)
- configure_components(defaults, conditions)
- add_default_requirements
- initialize_keys
+ def initialize
+ self.separators = Routing::SEPARATORS
+ self.optional_separators = %w( / )
end
- def inspect
- "<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
+ def separator_pattern(inverted = false)
+ "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
end
- def write_generation(generator = CodeGeneration::GenerationGenerator.new)
- generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
+ def interval_regexp
+ Regexp.new "(.*?)(#{separators.source}|$)"
+ end
+
+ # Accepts a "route path" (a string defining a route), and returns the array
+ # of segments that corresponds to it. Note that the segment array is only
+ # partially initialized--the defaults and requirements, for instance, need
+ # to be set separately, via the #assign_route_options method, and the
+ # #optional? method for each segment will not be reliable until after
+ # #assign_route_options is called, as well.
+ def segments_for_route_path(path)
+ rest, segments = path, []
+
+ until rest.empty?
+ segment, rest = segment_for rest
+ segments << segment
+ end
+ segments
+ end
- if known.empty? then generator.go
- else
- # Alter the conditions to allow :action => 'index' to also catch :action => nil
- altered_known = known.collect do |k, v|
- if k == :action && v== 'index' then [k, [nil, 'index']]
- else [k, v]
+ # A factory method that returns a new segment instance appropriate for the
+ # format of the given string.
+ def segment_for(string)
+ segment = case string
+ when /\A:(\w+)/
+ key = $1.to_sym
+ case key
+ when :action then DynamicSegment.new(key, :default => 'index')
+ when :id then DynamicSegment.new(key, :optional => true)
+ when :controller then ControllerSegment.new(key)
+ else DynamicSegment.new key
+ end
+ when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
+ when /\A\?(.*?)\?/
+ returning segment = StaticSegment.new($1) do
+ segment.is_optional = true
+ end
+ when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
+ when Regexp.new(separator_pattern) then
+ returning segment = DividerSegment.new($&) do
+ segment.is_optional = (optional_separators.include? $&)
end
- end
- generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
end
-
- generator
+ [segment, $~.post_match]
end
- def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
- g = generator.dup
- g.share_locals_with generator
- g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
+ # Split the given hash of options into requirement and default hashes. The
+ # segments are passed alongside in order to distinguish between default values
+ # and requirements.
+ def divide_route_options(segments, options)
+ requirements = options.delete(:requirements) || {}
+ defaults = options.delete(:defaults) || {}
+ conditions = options.delete(:conditions) || {}
- known.each do |key, value|
- if key == :controller then ControllerComponent.assign_controller(g, value)
- else g.constant_result(key, value)
- end
- end
-
- if @request_method
- g.if("@request.method == :#{@request_method}") { |gp| gp.go }
- else
- g.go
+ path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
+ options.each do |key, value|
+ hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
+ hash[key] = value
end
- generator
- end
-
- def initialize_keys
- @keys = (components.collect {|c| c.key} + known.keys).flatten.compact
- @keys.freeze
+ [defaults, requirements, conditions]
end
- def extra_keys(options)
- options.keys - @keys
- end
+ # Takes a hash of defaults and a hash of requirements, and assigns them to
+ # the segments. Any unused requirements (which do not correspond to a segment)
+ # are returned as a hash.
+ def assign_route_options(segments, defaults, requirements)
+ route_requirements = {} # Requirements that do not belong to a segment
- def matches_controller?(controller)
- if known[:controller] then known[:controller] == controller
- else
- c = components.find {|c| c.key == :controller}
- return false unless c
- return c.condition.nil? || eval(Routing.test_condition('controller', c.condition))
- end
- end
-
- protected
- def initialize_components(path)
- path = path.split('/') if path.is_a? String
- path.shift if path.first.blank?
- self.components = path.collect {|str| Component.new str}
+ segment_named = Proc.new do |key|
+ segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
end
- def initialize_hashes(options)
- path_keys = components.collect {|c| c.key }.flatten.compact
- self.known = {}
- defaults = options.delete(:defaults) || {}
- conditions = options.delete(:require) || {}
- conditions.update(options.delete(:requirements) || {})
-
- options.each do |k, v|
- if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
- else known[k] = v
- end
+ requirements.each do |key, requirement|
+ segment = segment_named[key]
+ if segment
+ raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
+ segment.regexp = requirement
+ else
+ route_requirements[key] = requirement
end
- [defaults, conditions]
end
- def configure_components(defaults, conditions)
- all_components = components.map { |c| SubpathComponent === c ? c.parts : c }.flatten
- all_components.each do |component|
- if defaults.key?(component.key) then component.default = defaults[component.key]
- elsif component.key == :action then component.default = 'index'
- elsif component.key == :id then component.default = nil
- end
-
- component.condition = conditions[component.key] if conditions.key?(component.key)
- end
- end
-
- def add_default_requirements
- component_keys = components.collect {|c| c.key}.flatten
- known[:action] ||= 'index' unless component_keys.include? :action
+ defaults.each do |key, default|
+ segment = segment_named[key]
+ raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
+ segment.is_optional = true
+ segment.default = default.to_param if default
end
- end
- class RouteSet #:nodoc:
- attr_reader :routes, :categories, :controller_to_selector
- def initialize
- @routes = []
- @generation_methods = Hash.new(:generate_default_path)
- end
-
- def generate(options, request_or_recall_hash = {})
- recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters
- use_recall = true
-
- controller = options[:controller]
- options[:action] ||= 'index' if controller
- recall_controller = recall[:controller]
- if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/))
- recall = {} if controller && controller[0] == ?/
- options[:controller] = Routing.controller_relative_to(controller, recall_controller)
- end
- options = recall.dup if options.empty? # XXX move to url_rewriter?
-
- keys_to_delete = []
- Routing.treat_hash(options, keys_to_delete)
-
- merged = recall.merge(options)
- keys_to_delete.each {|key| merged.delete key}
- expire_on = Routing.expiry_hash(options, recall)
-
- generate_path(merged, options, expire_on)
- end
-
- def generate_path(merged, options, expire_on)
- send @generation_methods[merged[:controller]], merged, options, expire_on
- end
- def generate_default_path(*args)
- write_generation
- generate_default_path(*args)
+ ensure_required_segments(segments)
+ route_requirements
end
- def write_generation
- method_sources = []
- @generation_methods = Hash.new(:generate_default_path)
- categorize_routes.each do |controller, routes|
- next unless routes.length < @routes.length
-
- ivar = controller.gsub('/', '__')
- method_name = "generate_path_for_#{ivar}".to_sym
- instance_variable_set "@#{ivar}", routes
- code = generation_code_for(ivar, method_name).to_s
- method_sources << code
-
- filename = "generated_code/routing/generation_for_controller_#{controller}.rb"
- eval(code, nil, filename)
-
- @generation_methods[controller.to_s] = method_name
- @generation_methods[controller.to_sym] = method_name
+ # Makes sure that there are no optional segments that precede a required
+ # segment. If any are found that precede a required segment, they are
+ # made required.
+ def ensure_required_segments(segments)
+ allow_optional = true
+ segments.reverse_each do |segment|
+ allow_optional &&= segment.optional?
+ if !allow_optional && segment.optional?
+ unless segment.optionality_implied?
+ warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
+ end
+ segment.is_optional = false
+ elsif allow_optional & segment.respond_to?(:default) && segment.default
+ # if a segment has a default, then it is optional
+ segment.is_optional = true
+ end
end
-
- code = generation_code_for('routes', 'generate_default_path').to_s
- eval(code, nil, 'generated_code/routing/generation.rb')
-
- return (method_sources << code)
end
- def recognize(request)
- @request = request
+ # Construct and return a route with the given path and options.
+ def build(path, options)
+ # Wrap the path with slashes
+ path = "/#{path}" unless path[0] == ?/
+ path = "#{path}/" unless path[-1] == ?/
+
+ segments = segments_for_route_path(path)
+ defaults, requirements, conditions = divide_route_options(segments, options)
+ requirements = assign_route_options(segments, defaults, requirements)
+
+ route = Route.new
+ route.segments = segments
+ route.requirements = requirements
+ route.conditions = conditions
- string_path = @request.path
- string_path.chomp! if string_path[0] == ?/
- path = string_path.split '/'
- path.shift
-
- hash = recognize_path(path)
- return recognition_failed(@request) unless hash && hash['controller']
-
- controller = hash['controller']
- hash['controller'] = controller.controller_path
- @request.path_parameters = hash
- controller.new
+ if !route.significant_keys.include?(:action) && !route.requirements[:action]
+ route.requirements[:action] = "index"
+ route.significant_keys << :action
+ end
+
+ route
end
- alias :recognize! :recognize
+ end
+
+ class RouteSet
- def recognition_failed(request)
- raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}"
+ # Mapper instances are used to build routes. The object passed to the draw
+ # block in config/routes.rb is a Mapper instance.
+ #
+ # Mapper instances have relatively few instance methods, in order to avoid
+ # clashes with named routes.
+ class Mapper
+ def initialize(set)
+ @set = set
+ end
+
+ # Create an unnamed route with the provided +path+ and +options+. See
+ # SomeHelpfulUrl for an introduction to routes.
+ def connect(path, options = {})
+ @set.add_route(path, options)
+ end
+
+ def method_missing(route_name, *args, &proc)
+ super unless args.length >= 1 && proc.nil?
+ @set.add_named_route(route_name, *args)
+ end
end
- def write_recognition
- g = generator = CodeGeneration::RecognitionGenerator.new
- g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"}
-
- g.def "self.recognize_path(path)" do
- each do |route|
- g << 'index = 0'
- route.write_recognition(g)
+ # A NamedRouteCollection instance is a collection of named routes, and also
+ # maintains an anonymous module that can be used to install helpers for the
+ # named routes.
+ class NamedRouteCollection
+ include Enumerable
+
+ attr_reader :routes, :helpers
+
+ def initialize
+ clear!
+ end
+
+ def clear!
+ @routes = {}
+ @helpers = []
+ @module = Module.new
+ end
+
+ def add(name, route)
+ routes[name.to_sym] = route
+ define_hash_access_method(name, route)
+ define_url_helper_method(name, route)
+ end
+
+ def get(name)
+ routes[name.to_sym]
+ end
+
+ alias []= add
+ alias [] get
+ alias clear clear!
+
+ def each
+ routes.each { |name, route| yield name, route }
+ self
+ end
+
+ def names
+ routes.keys
+ end
+
+ def length
+ routes.length
+ end
+
+ def install(dest = ActionController::Base)
+ dest.send :include, @module
+ if dest.respond_to? :helper_method
+ helpers.each { |name| dest.send :helper_method, name }
end
end
- eval g.to_s, nil, 'generated/routing/recognition.rb'
- return g.to_s
- end
-
- def generation_code_for(ivar = 'routes', method_name = nil)
- routes = instance_variable_get('@' + ivar)
- key_ivar = "@keys_for_#{ivar}"
- instance_variable_set(key_ivar, routes.collect {|route| route.keys})
-
- g = generator = CodeGeneration::GenerationGenerator.new
- g.def "self.#{method_name}(merged, options, expire_on)" do
- g << 'unused_count = options.length + 1'
- g << "unused_keys = keys = options.keys"
- g << 'path = nil'
-
- routes.each_with_index do |route, index|
- g << "new_unused_keys = keys - #{key_ivar}[#{index}]"
- g << 'new_path = ('
- g.source.indent do
- if index.zero?
- g << "new_unused_count = new_unused_keys.length"
- g << "hash = merged; not_expired = true"
- route.write_generation(g.dup)
+ private
+
+ def url_helper_name(name)
+ :"#{name}_url"
+ end
+
+ def hash_access_name(name)
+ :"hash_for_#{name}_url"
+ end
+
+ def define_hash_access_method(name, route)
+ method_name = hash_access_name(name)
+
+ @module.send(:define_method, method_name) do |*args|
+ hash = route.defaults.merge(:use_route => name)
+ args.first ? hash.merge(args.first) : hash
+ end
+
+ @module.send(:protected, method_name)
+ helpers << method_name
+ end
+
+ def define_url_helper_method(name, route)
+ hash_access_method = hash_access_name(name)
+ method_name = url_helper_name(name)
+
+ @module.send(:define_method, method_name) do |*args|
+ opts = if args.empty? || Hash === args.first
+ args.first || {}
else
- g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp|
- gp << "hash = merged; not_expired = true"
- route.write_generation(gp)
+ # allow ordered parameters to be associated with corresponding
+ # dynamic segments, so you can do
+ #
+ # foo_url(bar, baz, bang)
+ #
+ # instead of
+ #
+ # foo_url(:bar => bar, :baz => baz, :bang => bang)
+ route.segments.inject({}) do |opts, seg|
+ next opts unless seg.respond_to?(:key) && seg.key
+ opts[seg.key] = args.shift
+ break opts if args.empty?
+ opts
end
end
+
+ url_for(send(hash_access_method, opts))
end
- g.source.lines.last << ' )' # Add the closing brace to the end line
- g.if 'new_path' do
- g << 'return new_path, [] if new_unused_count.zero?'
- g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count'
- end
- end
-
- g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path"
- g << "return path, unused_keys"
- end
-
- return g
- end
-
- def categorize_routes
- @categorized_routes = by_controller = Hash.new(self)
-
- known_controllers.each do |name|
- set = by_controller[name] = []
- each do |route|
- set << route if route.matches_controller? name
- end
- end
-
- @categorized_routes
- end
-
- def known_controllers
- @routes.inject([]) do |known, route|
- if (controller = route.known[:controller])
- if controller.is_a?(Regexp)
- known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word}
- else known << controller
- end
+
+ @module.send(:protected, method_name)
+ helpers << method_name
end
- known
- end.uniq
+ end
+
+ attr_accessor :routes, :named_routes
+
+ def initialize
+ self.routes = []
+ self.named_routes = NamedRouteCollection.new
end
- def reload
- NamedRoutes.clear
-
- if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb'))
- else connect(':controller/:action/:id', :action => 'index', :id => nil)
- end
+ # Subclasses and plugins may override this method to specify a different
+ # RouteBuilder instance, so that other route DSL's can be created.
+ def builder
+ @builder ||= RouteBuilder.new
+ end
- NamedRoutes.install
+ def draw
+ clear!
+ yield Mapper.new(self)
+ named_routes.install
+ end
+
+ def clear!
+ routes.clear
+ named_routes.clear
+ @combined_regexp = nil
+ @routes_by_controller = nil
end
- def connect(*args)
- new_route = Route.new(*args)
- @routes << new_route
- return new_route
+ def empty?
+ routes.empty?
+ end
+
+ def load!
+ clear!
+ load_routes!
+ named_routes.install
end
- def draw
- old_routes = @routes
- @routes = []
-
- begin yield self
- rescue
- @routes = old_routes
- raise
+ alias reload load!
+
+ def load_routes!
+ if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
+ load File.join("#{RAILS_ROOT}/config/routes.rb")
+ else
+ add_route ":controller/:action/:id"
end
- write_generation
- write_recognition
end
-
- def empty?() @routes.empty? end
-
- def each(&block) @routes.each(&block) end
-
- # Defines a new named route with the provided name and arguments.
- # This method need only be used when you wish to use a name that a RouteSet instance
- # method exists for, such as categories.
- #
- # For example, map.categories '/categories', :controller => 'categories' will not work
- # due to RouteSet#categories.
- def named_route(name, path, hash = {})
- route = connect(path, hash)
- NamedRoutes.name_route(route, name)
+
+ def add_route(path, options = {})
+ route = builder.build(path, options)
+ routes << route
route
end
-
- def method_missing(name, *args)
- (1..2).include?(args.length) ? named_route(name, *args) : super(name, *args)
+
+ def add_named_route(name, path, options = {})
+ named_routes[name] = add_route(path, options)
end
+
+ def options_as_params(options)
+ # If an explicit :controller was given, always make :action explicit
+ # too, so that action expiry works as expected for things like
+ #
+ # generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ #
+ # (the above is from the unit tests). In the above case, because the
+ # controller was explicitly given, but no action, the action is implied to
+ # be "index", not the recalled action of "show".
+ #
+ # great fun, eh?
- def extra_keys(options, recall = {})
- generate(options.dup, recall).last
+ options_as_params = options[:controller] ? { :action => "index" } : {}
+ options.each do |k, value|
+ options_as_params[k] = value.to_param
+ end
+ options_as_params
end
- end
-
- module NamedRoutes #:nodoc:
- Helpers = []
- class << self
- def clear() Helpers.clear end
- def hash_access_name(name)
- "hash_for_#{name}_url"
+ def build_expiry(options, recall)
+ recall.inject({}) do |expiry, (key, recalled_value)|
+ expiry[key] = (options.key?(key) && options[key] != recalled_value)
+ expiry
end
+ end
- def url_helper_name(name)
- "#{name}_url"
+ # Generate the path indicated by the arguments, and return an array of
+ # the keys that were not used to generate it.
+ def extra_keys(options, recall={})
+ generate_extras(options, recall).last
+ end
+
+ def generate_extras(options, recall={})
+ generate(options, recall, :generate_extras)
+ end
+
+ def generate(options, recall = {}, method=:generate)
+ if options[:use_route]
+ options = options.dup
+ named_route = named_routes[options.delete(:use_route)]
+ options = named_route.parameter_shell.merge(options)
end
-
- def known_hash_for_route(route)
- hash = route.known.symbolize_keys
- route.defaults.each do |key, value|
- hash[key.to_sym] ||= value if value
- end
- hash[:controller] = "/#{hash[:controller]}"
-
- hash
+
+ options = options_as_params(options)
+ expire_on = build_expiry(options, recall)
+
+ # if the controller has changed, make sure it changes relative to the
+ # current controller module, if any. In other words, if we're currently
+ # on admin/get, and the new controller is 'set', the new controller
+ # should really be admin/set.
+ if expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
+ parts = recall[:controller].split('/')[0..-2] + [options[:controller]]
+ options[:controller] = parts.join('/')
end
-
- def define_hash_access_method(route, name)
- hash = known_hash_for_route(route)
- define_method(hash_access_name(name)) do |*args|
- args.first ? hash.merge(args.first) : hash
+
+ # drop the leading '/' on the controller name
+ options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
+ merged = recall.merge(options)
+
+ if named_route
+ return named_route.generate(options, merged, expire_on)
+ else
+ merged[:action] ||= 'index'
+ options[:action] ||= 'index'
+
+ controller = merged[:controller]
+ action = merged[:action]
+
+ raise "Need controller and action!" unless controller && action
+ routes = routes_by_controller[controller][action][merged.keys.sort_by { |x| x.object_id }]
+
+ routes.each do |route|
+ results = route.send(method, options, merged, expire_on)
+ return results if results
end
end
-
- def name_route(route, name)
- define_hash_access_method(route, name)
-
- module_eval(%{def #{url_helper_name name}(options = {})
- url_for(#{hash_access_name(name)}.merge(options.is_a?(Hash) ? options : { :id => options }))
- end}, "generated/routing/named_routes/#{name}.rb")
-
- protected url_helper_name(name), hash_access_name(name)
-
- Helpers << url_helper_name(name).to_sym
- Helpers << hash_access_name(name).to_sym
- Helpers.uniq!
- end
- def install(cls = ActionController::Base)
- cls.send :include, self
- if cls.respond_to? :helper_method
- Helpers.each do |helper_name|
- cls.send :helper_method, helper_name
+ raise RoutingError, "No route matches #{options.inspect}"
+ end
+
+ def recognize(request)
+ params = recognize_path(request.path, extract_request_environment(request))
+ request.path_parameters = params.with_indifferent_access
+ "#{params[:controller].camelize}Controller".constantize
+ end
+
+ def recognize_path(path, environment={})
+ routes.each do |route|
+ result = route.recognize(path, environment) and return result
+ end
+ raise RoutingError, "no route found to match #{path.inspect} with #{environment.inspect}"
+ end
+
+ def routes_by_controller
+ @routes_by_controller ||= Hash.new do |controller_hash, controller|
+ controller_hash[controller] = Hash.new do |action_hash, action|
+ action_hash[action] = Hash.new do |key_hash, keys|
+ key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
end
end
end
end
+
+ def routes_for(options, merged, expire_on)
+ raise "Need controller and action!" unless controller && action
+ controller = merged[:controller]
+ merged = options if expire_on[:controller]
+ action = merged[:action] || 'index'
+
+ routes_by_controller[controller][action][merged.keys]
+ end
+
+ def routes_for_controller_and_action(controller, action)
+ selected = routes.select do |route|
+ route.matches_controller_and_action? controller, action
+ end
+ (selected.length == routes.length) ? routes : selected
+ end
+
+ def routes_for_controller_and_action_and_keys(controller, action, keys)
+ selected = routes.select do |route|
+ route.matches_controller_and_action? controller, action
+ end
+ selected.sort_by do |route|
+ (keys - route.significant_keys).length
+ end
+ end
+
+ # Subclasses and plugins may override this method to extract further attributes
+ # from the request, for use by route conditions and such.
+ def extract_request_environment(request)
+ { :method => request.method }
+ end
end
Routes = RouteSet.new
end
-end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 4d79880278..3fb6713f7b 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -106,7 +106,7 @@ module ActionController #:nodoc:
if value.is_a? Fixnum
value = value.to_s
elsif value.is_a? Array
- value = ActionController::Routing::PathComponent::Result.new(value)
+ value = ActionController::Routing::PathSegment::Result.new(value)
end
if extra_keys.include?(key.to_sym)
@@ -433,7 +433,7 @@ module ActionController #:nodoc:
end
def method_missing(selector, *args)
- return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
+ return @controller.send(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
return super
end
diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb
index bc82fda5db..872bfcc122 100644
--- a/actionpack/lib/action_controller/url_rewriter.rb
+++ b/actionpack/lib/action_controller/url_rewriter.rb
@@ -41,34 +41,9 @@ module ActionController
options.update(overwrite)
end
RESERVED_OPTIONS.each {|k| options.delete k}
- path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash
- path << build_query_string(options, extra_keys) unless extra_keys.empty?
-
- path
- end
-
- # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
- # be added as a path element instead of a regular parameter pair.
- def build_query_string(hash, only_keys = nil)
- elements = []
- query_string = ""
-
- only_keys ||= hash.keys
-
- only_keys.each do |key|
- value = hash[key]
- key = CGI.escape key.to_s
- if value.class == Array
- key << '[]'
- else
- value = [ value ]
- end
- value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
- end
-
- query_string << ("?" + elements.join("&")) unless elements.empty?
- query_string
+ # Generates the query string, too
+ Routing::Routes.generate(options, @request.parameters)
end
end
end
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index f55fb6cbf8..bd9c8c1ff8 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -227,10 +227,12 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
# test the redirection to a named route
def test_assert_redirect_to_named_route
with_routing do |set|
- set.draw do
- set.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing'
- set.connect ':controller/:action/:id'
+ set.draw do |map|
+ map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing'
+ map.connect ':controller/:action/:id'
end
+ set.named_routes.install
+
process :redirect_to_named_route
assert_redirected_to 'http://test.host/route_one'
assert_redirected_to route_one_url
@@ -240,10 +242,10 @@ class ActionPackAssertionsControllerTest < Test::Unit::TestCase
def test_assert_redirect_to_named_route_failure
with_routing do |set|
- set.draw do
- set.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one'
- set.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
- set.connect ':controller/:action/:id'
+ set.draw do |map|
+ map.route_one 'route_one', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'one'
+ map.route_two 'route_two', :controller => 'action_pack_assertions', :action => 'nothing', :id => 'two'
+ map.connect ':controller/:action/:id'
end
process :redirect_to_named_route
assert_raise(Test::Unit::AssertionFailedError) do
diff --git a/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb b/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb
diff --git a/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb b/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/controller/controller_fixtures/app/controllers/user_controller.rb
diff --git a/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb b/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/actionpack/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb
diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb
index aa1d4459ee..49869d647e 100644
--- a/actionpack/test/controller/mime_type_test.rb
+++ b/actionpack/test/controller/mime_type_test.rb
@@ -5,6 +5,7 @@ class MimeTypeTest < Test::Unit::TestCase
Mime::PLAIN = Mime::Type.new("text/plain")
def test_parse_single
+p Mime::LOOKUP.keys.sort
Mime::LOOKUP.keys.each do |mime_type|
assert_equal [Mime::Type.lookup(mime_type)], Mime::Type.parse(mime_type)
end
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index 4f094a36ff..3f8397c95e 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -1,647 +1,41 @@
require File.dirname(__FILE__) + '/../abstract_unit'
require 'test/unit'
require File.dirname(__FILE__) + '/fake_controllers'
-require 'stringio'
+require 'action_controller/routing'
RunTimeTests = ARGV.include? 'time'
+ROUTING = ActionController::Routing
-module ActionController::CodeGeneration
+class ROUTING::RouteBuilder
+ attr_reader :warn_output
-class SourceTests < Test::Unit::TestCase
- attr_accessor :source
- def setup
- @source = Source.new
- end
-
- def test_initial_state
- assert_equal [], source.lines
- assert_equal 0, source.indentation_level
- end
-
- def test_trivial_operations
- source << "puts 'Hello World'"
- assert_equal ["puts 'Hello World'"], source.lines
- assert_equal "puts 'Hello World'", source.to_s
-
- source.line "puts 'Goodbye World'"
- assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], source.lines
- assert_equal "puts 'Hello World'\nputs 'Goodbye World'", source.to_s
- end
-
- def test_indentation
- source << "x = gets.to_i"
- source << 'if x.odd?'
- source.indent { source << "puts 'x is odd!'" }
- source << 'else'
- source.indent { source << "puts 'x is even!'" }
- source << 'end'
-
- assert_equal ["x = gets.to_i", "if x.odd?", " puts 'x is odd!'", 'else', " puts 'x is even!'", 'end'], source.lines
-
- text = "x = gets.to_i
-if x.odd?
- puts 'x is odd!'
-else
- puts 'x is even!'
-end"
-
- assert_equal text, source.to_s
- end
-end
-
-class CodeGeneratorTests < Test::Unit::TestCase
- attr_accessor :generator
- def setup
- @generator = CodeGenerator.new
- end
-
- def test_initial_state
- assert_equal [], generator.source.lines
- assert_equal [], generator.locals
- end
-
- def test_trivial_operations
- ["puts 'Hello World'", "puts 'Goodbye World'"].each {|l| generator << l}
- assert_equal ["puts 'Hello World'", "puts 'Goodbye World'"], generator.source.lines
- assert_equal "puts 'Hello World'\nputs 'Goodbye World'", generator.to_s
- end
-
- def test_if
- generator << "x = gets.to_i"
- generator.if("x.odd?") { generator << "puts 'x is odd!'" }
-
- assert_equal "x = gets.to_i\nif x.odd?\n puts 'x is odd!'\nend", generator.to_s
- end
-
- def test_else
- test_if
- generator.else { generator << "puts 'x is even!'" }
-
- assert_equal "x = gets.to_i\nif x.odd?\n puts 'x is odd!'\nelse \n puts 'x is even!'\nend", generator.to_s
- end
-
- def test_dup
- generator << 'x = 2'
- generator.locals << :x
-
- g = generator.dup
- assert_equal generator.source, g.source
- assert_equal generator.locals, g.locals
-
- g << 'y = 3'
- g.locals << :y
- assert_equal [:x, :y], g.locals # Make sure they don't share the same array.
- assert_equal [:x], generator.locals
- end
-end
-
-class RecognitionTests < Test::Unit::TestCase
- attr_accessor :generator
- alias :g :generator
- def setup
- @generator = RecognitionGenerator.new
- end
-
- def go(components)
- g.current = components.first
- g.after = components[1..-1] || []
- g.go
- end
-
- def execute(path, show = false)
- path = path.split('/') if path.is_a? String
- source = "index, path = 0, #{path.inspect}\n#{g.to_s}"
- puts source if show
- r = eval source
- r ? r.symbolize_keys : nil
- end
-
- Static = ::ActionController::Routing::StaticComponent
- Dynamic = ::ActionController::Routing::DynamicComponent
- Path = ::ActionController::Routing::PathComponent
- Controller = ::ActionController::Routing::ControllerComponent
-
- def test_all_static
- c = %w(hello world how are you).collect {|str| Static.new(str)}
-
- g.result :controller, "::ContentController", true
- g.constant_result :action, 'index'
-
- go c
-
- assert_nil execute('x')
- assert_nil execute('hello/world/how')
- assert_nil execute('hello/world/how/are')
- assert_nil execute('hello/world/how/are/you/today')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hello/world/how/are/you'))
- end
-
- def test_basic_dynamic
- c = [Static.new("hi"), Dynamic.new(:action)]
- g.result :controller, "::ContentController", true
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi')
- assert_nil execute('hi/dude/what')
- assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
- end
-
- def test_basic_dynamic_backwards
- c = [Dynamic.new(:action), Static.new("hi")]
- go c
-
- assert_nil execute('')
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi')
- assert_equal({:action => 'index'}, execute('index/hi'))
- assert_equal({:action => 'show'}, execute('show/hi'))
- assert_nil execute('hi/dude')
- end
-
- def test_dynamic_with_default
- c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')]
- g.result :controller, "::ContentController", true
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi/dude/what')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi'))
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
- assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
- end
-
- def test_dynamic_with_string_condition
- c = [Static.new("hi"), Dynamic.new(:action, :condition => 'index')]
- g.result :controller, "::ContentController", true
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi')
- assert_nil execute('hi/dude/what')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
- assert_nil execute('hi/dude')
- end
-
- def test_dynamic_with_string_condition_backwards
- c = [Dynamic.new(:action, :condition => 'index'), Static.new("hi")]
- g.result :controller, "::ContentController", true
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi')
- assert_nil execute('dude/what/hi')
- assert_nil execute('index/what')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('index/hi'))
- assert_nil execute('dude/hi')
- end
-
- def test_dynamic_with_regexp_condition
- c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)]
- g.result :controller, "::ContentController", true
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi')
- assert_nil execute('hi/FOXY')
- assert_nil execute('hi/138708jkhdf')
- assert_nil execute('hi/dkjfl8792343dfsf')
- assert_nil execute('hi/dude/what')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
- assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
- end
-
- def test_dynamic_with_regexp_and_default
- c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/, :default => 'index')]
- g.result :controller, "::ContentController", true
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi/FOXY')
- assert_nil execute('hi/138708jkhdf')
- assert_nil execute('hi/dkjfl8792343dfsf')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi'))
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('hi/index'))
- assert_equal({:controller => ::ContentController, :action => 'dude'}, execute('hi/dude'))
- assert_nil execute('hi/dude/what')
- end
-
- def test_path
- c = [Static.new("hi"), Path.new(:file)]
- g.result :controller, "::ContentController", true
- g.constant_result :action, "download"
-
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('hi'))
- assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)},
- execute('hi/books/agile_rails_dev.pdf'))
- assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('hi/dude'))
- assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s
- end
-
- def test_path_with_dynamic
- c = [Dynamic.new(:action), Path.new(:file)]
- g.result :controller, "::ContentController", true
-
- go c
-
- assert_nil execute('')
- assert_equal({:controller => ::ContentController, :action => 'download', :file => []}, execute('download'))
- assert_equal({:controller => ::ContentController, :action => 'download', :file => %w(books agile_rails_dev.pdf)},
- execute('download/books/agile_rails_dev.pdf'))
- assert_equal({:controller => ::ContentController, :action => 'download', :file => ['dude']}, execute('download/dude'))
- assert_equal 'dude/what', execute('hi/dude/what')[:file].to_s
- end
-
- def test_path_with_dynamic_and_default
- c = [Dynamic.new(:action, :default => 'index'), Path.new(:file)]
-
- go c
-
- assert_equal({:action => 'index', :file => []}, execute(''))
- assert_equal({:action => 'index', :file => []}, execute('index'))
- assert_equal({:action => 'blarg', :file => []}, execute('blarg'))
- assert_equal({:action => 'index', :file => ['content']}, execute('index/content'))
- assert_equal({:action => 'show', :file => ['rails_dev.pdf']}, execute('show/rails_dev.pdf'))
- end
-
- def test_controller
- c = [Static.new("hi"), Controller.new(:controller)]
- g.constant_result :action, "hi"
-
- go c
-
- assert_nil execute('boo')
- assert_nil execute('boo/blah')
- assert_nil execute('hi/x')
- assert_nil execute('hi/13870948')
- assert_nil execute('hi/content/dog')
- assert_nil execute('hi/admin/user/foo')
- assert_equal({:controller => ::ContentController, :action => 'hi'}, execute('hi/content'))
- assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user'))
- end
-
- def test_controller_with_regexp
- c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)]
- g.constant_result :action, "hi"
-
- go c
-
- assert_nil execute('hi')
- assert_nil execute('hi/x')
- assert_nil execute('hi/content')
- assert_equal({:controller => ::Admin::UserController, :action => 'hi'}, execute('hi/admin/user'))
- assert_equal({:controller => ::Admin::NewsFeedController, :action => 'hi'}, execute('hi/admin/news_feed'))
- assert_nil execute('hi/admin/user/foo')
- end
-
- def test_standard_route(time = ::RunTimeTests)
- c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)]
- go c
-
- # Make sure we get the right answers
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute('content'))
- assert_equal({:controller => ::ContentController, :action => 'list'}, execute('content/list'))
- assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, execute('content/show/10'))
-
- assert_equal({:controller => ::Admin::UserController, :action => 'index'}, execute('admin/user'))
- assert_equal({:controller => ::Admin::UserController, :action => 'list'}, execute('admin/user/list'))
- assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => 'nseckar'}, execute('admin/user/show/nseckar'))
-
- assert_nil execute('content/show/10/20')
- assert_nil execute('food')
-
- if time
- source = "def self.execute(path)
- path = path.split('/') if path.is_a? String
- index = 0
- r = #{g.to_s}
- end"
- eval(source)
-
- GC.start
- n = 1000
- time = Benchmark.realtime do n.times {
- execute('content')
- execute('content/list')
- execute('content/show/10')
-
- execute('admin/user')
- execute('admin/user/list')
- execute('admin/user/show/nseckar')
-
- execute('admin/user/show/nseckar/dude')
- execute('admin/why/show/nseckar')
- execute('content/show/10/20')
- execute('food')
- } end
- time -= Benchmark.realtime do n.times { } end
-
-
- puts "\n\nRecognition:"
- per_url = time / (n * 10)
-
- puts "#{per_url * 1000} ms/url"
- puts "#{1 / per_url} urls/s\n\n"
- end
- end
-
- def test_default_route
- g.result :controller, "::ContentController", true
- g.constant_result :action, 'index'
-
- go []
-
- assert_nil execute('x')
- assert_nil execute('hello/world/how')
- assert_nil execute('hello/world/how/are')
- assert_nil execute('hello/world/how/are/you/today')
- assert_equal({:controller => ::ContentController, :action => 'index'}, execute([]))
- end
-end
-
-class GenerationTests < Test::Unit::TestCase
- attr_accessor :generator
- alias :g :generator
- def setup
- @generator = GenerationGenerator.new # ha!
- end
-
- def go(components)
- g.current = components.first
- g.after = components[1..-1] || []
- g.go
- end
-
- def execute(options, recall, show = false)
- source = "\n
-expire_on = ::ActionController::Routing.expiry_hash(options, recall)
-hash = merged = recall.merge(options)
-not_expired = true
-
-#{g.to_s}\n\n"
- puts source if show
- eval(source)
- end
-
- Static = ::ActionController::Routing::StaticComponent
- Dynamic = ::ActionController::Routing::DynamicComponent
- Path = ::ActionController::Routing::PathComponent
- Controller = ::ActionController::Routing::ControllerComponent
-
- def test_all_static_no_requirements
- c = [Static.new("hello"), Static.new("world")]
- go c
-
- assert_equal "/hello/world", execute({}, {})
- end
-
- def test_basic_dynamic
- c = [Static.new("hi"), Dynamic.new(:action)]
- go c
-
- assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'})
- assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'})
- assert_equal '/hi/list+people', execute({}, {:action => 'list people'})
- assert_nil execute({},{})
- end
-
- def test_dynamic_with_default
- c = [Static.new("hi"), Dynamic.new(:action, :default => 'index')]
- go c
-
- assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'})
- assert_equal '/hi/show', execute({:action => 'show'}, {:action => 'index'})
- assert_equal '/hi/list+people', execute({}, {:action => 'list people'})
- assert_equal '/hi', execute({}, {})
- end
-
- def test_dynamic_with_regexp_condition
- c = [Static.new("hi"), Dynamic.new(:action, :condition => /^[a-z]+$/)]
- go c
-
- assert_equal '/hi/index', execute({:action => 'index'}, {:action => 'index'})
- assert_nil execute({:action => 'fox5'}, {:action => 'index'})
- assert_nil execute({:action => 'something_is_up'}, {:action => 'index'})
- assert_nil execute({}, {:action => 'list people'})
- assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {})
- assert_nil execute({}, {})
- end
-
- def test_dynamic_with_default_and_regexp_condition
- c = [Static.new("hi"), Dynamic.new(:action, :default => 'index', :condition => /^[a-z]+$/)]
- go c
-
- assert_equal '/hi', execute({:action => 'index'}, {:action => 'index'})
- assert_nil execute({:action => 'fox5'}, {:action => 'index'})
- assert_nil execute({:action => 'something_is_up'}, {:action => 'index'})
- assert_nil execute({}, {:action => 'list people'})
- assert_equal '/hi/abunchofcharacter', execute({:action => 'abunchofcharacter'}, {})
- assert_equal '/hi', execute({}, {})
- end
-
- def test_path
- c = [Static.new("hi"), Path.new(:file)]
- go c
-
- assert_equal '/hi', execute({:file => []}, {})
- assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => %w(books agile_rails_dev.pdf)}, {})
- assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => %w(books development&whatever agile_rails_dev.pdf)}, {})
-
- assert_equal '/hi', execute({:file => ''}, {})
- assert_equal '/hi/books/agile_rails_dev.pdf', execute({:file => 'books/agile_rails_dev.pdf'}, {})
- assert_equal '/hi/books/development%26whatever/agile_rails_dev.pdf', execute({:file => 'books/development&whatever/agile_rails_dev.pdf'}, {})
- end
-
- def test_controller
- c = [Static.new("hi"), Controller.new(:controller)]
- go c
-
- assert_nil execute({}, {})
- assert_equal '/hi/content', execute({:controller => 'content'}, {})
- assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {})
- assert_equal '/hi/content', execute({}, {:controller => 'content'})
- assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'})
- end
-
- def test_controller_with_regexp
- c = [Static.new("hi"), Controller.new(:controller, :condition => /^admin\/.+$/)]
- go c
-
- assert_nil execute({}, {})
- assert_nil execute({:controller => 'content'}, {})
- assert_equal '/hi/admin/user', execute({:controller => 'admin/user'}, {})
- assert_nil execute({}, {:controller => 'content'})
- assert_equal '/hi/admin/user', execute({}, {:controller => 'admin/user'})
- end
-
- def test_standard_route(time = ::RunTimeTests)
- c = [Controller.new(:controller), Dynamic.new(:action, :default => 'index'), Dynamic.new(:id, :default => nil)]
- go c
-
- # Make sure we get the right answers
- assert_equal('/content', execute({:action => 'index'}, {:controller => 'content', :action => 'list'}))
- assert_equal('/content/list', execute({:action => 'list'}, {:controller => 'content', :action => 'index'}))
- assert_equal('/content/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'}))
-
- assert_equal('/admin/user', execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'}))
- assert_equal('/admin/user/list', execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'}))
- assert_equal('/admin/user/show/10', execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'}))
-
- if time
- GC.start
- n = 1000
- time = Benchmark.realtime do n.times {
- execute({:action => 'index'}, {:controller => 'content', :action => 'list'})
- execute({:action => 'list'}, {:controller => 'content', :action => 'index'})
- execute({:action => 'show', :id => '10'}, {:controller => 'content', :action => 'list'})
-
- execute({:action => 'index'}, {:controller => 'admin/user', :action => 'list'})
- execute({:action => 'list'}, {:controller => 'admin/user', :action => 'index'})
- execute({:action => 'show', :id => '10'}, {:controller => 'admin/user', :action => 'list'})
- } end
- time -= Benchmark.realtime do n.times { } end
-
- puts "\n\nGeneration:"
- per_url = time / (n * 6)
-
- puts "#{per_url * 1000} ms/url"
- puts "#{1 / per_url} urls/s\n\n"
- end
- end
-
- def test_default_route
- g.if(g.check_conditions(:controller => 'content', :action => 'welcome')) { go [] }
-
- assert_nil execute({:controller => 'foo', :action => 'welcome'}, {})
- assert_nil execute({:controller => 'content', :action => 'elcome'}, {})
- assert_nil execute({:action => 'elcome'}, {:controller => 'content'})
-
- assert_equal '/', execute({:controller => 'content', :action => 'welcome'}, {})
- assert_equal '/', execute({:action => 'welcome'}, {:controller => 'content'})
- assert_equal '/', execute({:action => 'welcome', :id => '10'}, {:controller => 'content'})
- end
-end
-
-class RouteTests < Test::Unit::TestCase
-
-
- def route(*args)
- @route = ::ActionController::Routing::Route.new(*args) unless args.empty?
- return @route
- end
-
- def rec(path, show = false)
- path = path.split('/') if path.is_a? String
- index = 0
- source = route.write_recognition.to_s
- puts "\n\n#{source}\n\n" if show
- r = eval(source)
- r ? r.symbolize_keys : r
- end
- def gen(options, recall = nil, show = false)
- recall ||= options.dup
-
- expire_on = ::ActionController::Routing.expiry_hash(options, recall)
- hash = merged = recall.merge(options)
- not_expired = true
-
- source = route.write_generation.to_s
- puts "\n\n#{source}\n\n" if show
- eval(source)
-
- end
-
- def test_static
- route 'hello/world', :known => 'known_value', :controller => 'content', :action => 'index'
-
- assert_nil rec('hello/turn')
- assert_nil rec('turn/world')
- assert_equal(
- {:known => 'known_value', :controller => ::ContentController, :action => 'index'},
- rec('hello/world')
- )
-
- assert_nil gen(:known => 'foo')
- assert_nil gen({})
- assert_equal '/hello/world', gen(:known => 'known_value', :controller => 'content', :action => 'index')
- assert_equal '/hello/world', gen(:known => 'known_value', :extra => 'hi', :controller => 'content', :action => 'index')
- assert_equal [:extra], route.extra_keys(:known => 'known_value', :extra => 'hi')
- end
-
- def test_dynamic
- route 'hello/:name', :controller => 'content', :action => 'show_person'
-
- assert_nil rec('hello')
- assert_nil rec('foo/bar')
- assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'rails'}, rec('hello/rails'))
- assert_equal({:controller => ::ContentController, :action => 'show_person', :name => 'Nicholas Seckar'}, rec('hello/Nicholas+Seckar'))
-
- assert_nil gen(:controller => 'content', :action => 'show_dude', :name => 'rails')
- assert_nil gen(:controller => 'content', :action => 'show_person')
- assert_nil gen(:controller => 'admin/user', :action => 'show_person', :name => 'rails')
- assert_equal '/hello/rails', gen(:controller => 'content', :action => 'show_person', :name => 'rails')
- assert_equal '/hello/Nicholas+Seckar', gen(:controller => 'content', :action => 'show_person', :name => 'Nicholas Seckar')
- end
-
- def test_typical
- route ':controller/:action/:id', :action => 'index', :id => nil
- assert_nil rec('hello')
- assert_nil rec('foo bar')
- assert_equal({:controller => ::ContentController, :action => 'index'}, rec('content'))
- assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user'))
-
- assert_equal({:controller => ::Admin::UserController, :action => 'index'}, rec('admin/user/index'))
- assert_equal({:controller => ::Admin::UserController, :action => 'list'}, rec('admin/user/list'))
- assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}, rec('admin/user/show/10'))
-
- assert_equal({:controller => ::ContentController, :action => 'list'}, rec('content/list'))
- assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}, rec('content/show/10'))
-
-
- assert_equal '/content', gen(:controller => 'content', :action => 'index')
- assert_equal '/content/list', gen(:controller => 'content', :action => 'list')
- assert_equal '/content/show/10', gen(:controller => 'content', :action => 'show', :id => '10')
-
- assert_equal '/admin/user', gen(:controller => 'admin/user', :action => 'index')
- assert_equal '/admin/user', gen(:controller => 'admin/user')
- assert_equal '/admin/user', gen({:controller => 'admin/user'}, {:controller => 'content', :action => 'list', :id => '10'})
- assert_equal '/admin/user/show/10', gen(:controller => 'admin/user', :action => 'show', :id => '10')
+ def warn(msg)
+ (@warn_output ||= []) << msg
end
end
-class RouteSetTests < Test::Unit::TestCase
+class LegacyRouteSetTests < Test::Unit::TestCase
attr_reader :rs
def setup
@rs = ::ActionController::Routing::RouteSet.new
+ ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed)
@rs.draw {|m| m.connect ':controller/:action/:id' }
- ::ActionController::Routing::NamedRoutes.clear
end
def test_default_setup
- assert_equal({:controller => ::ContentController, :action => 'index'}.stringify_keys, rs.recognize_path(%w(content)))
- assert_equal({:controller => ::ContentController, :action => 'list'}.stringify_keys, rs.recognize_path(%w(content list)))
- assert_equal({:controller => ::ContentController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(content show 10)))
+ assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/content"))
+ assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/content/list"))
+ assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/content/show/10"))
- assert_equal({:controller => ::Admin::UserController, :action => 'show', :id => '10'}.stringify_keys, rs.recognize_path(%w(admin user show 10)))
+ assert_equal({:controller => "admin/user", :action => 'show', :id => '10'}, rs.recognize_path("/admin/user/show/10"))
- assert_equal ['/admin/user/show/10', []], rs.generate({:controller => 'admin/user', :action => 'show', :id => 10})
+ assert_equal '/admin/user/show/10', rs.generate(:controller => 'admin/user', :action => 'show', :id => 10)
- assert_equal ['/admin/user/show', []], rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
- assert_equal ['/admin/user/list/10', []], rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ assert_equal '/admin/user/show', rs.generate({:action => 'show'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ assert_equal '/admin/user/list/10', rs.generate({}, {:controller => 'admin/user', :action => 'list', :id => '10'})
- assert_equal ['/admin/stuff', []], rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
- assert_equal ['/stuff', []], rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ assert_equal '/admin/stuff', rs.generate({:controller => 'stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
+ assert_equal '/stuff', rs.generate({:controller => '/stuff'}, {:controller => 'admin/user', :action => 'list', :id => '10'})
end
def test_ignores_leading_slash
@@ -655,12 +49,12 @@ class RouteSetTests < Test::Unit::TestCase
GC.start
rectime = Benchmark.realtime do
n.times do
- rs.recognize_path(%w(content))
- rs.recognize_path(%w(content list))
- rs.recognize_path(%w(content show 10))
- rs.recognize_path(%w(admin user))
- rs.recognize_path(%w(admin user list))
- rs.recognize_path(%w(admin user show 10))
+ rs.recognize_path("content")
+ rs.recognize_path("content/list")
+ rs.recognize_path("content/show/10")
+ rs.recognize_path("admin/user")
+ rs.recognize_path("admin/user/list")
+ rs.recognize_path("admin/user/show/10")
end
end
puts "\n\nRecognition (RouteSet):"
@@ -704,86 +98,74 @@ class RouteSetTests < Test::Unit::TestCase
end
end
- def test_route_generating_string_literal_in_comparison_warning
- old_stderr = $stderr
- $stderr = StringIO.new
- rs.draw do |map|
- map.connect 'subscriptions/:action/:subscription_type', :controller => "subscriptions"
- end
- assert_equal "", $stderr.string
- ensure
- $stderr = old_stderr
- end
-
def test_route_with_regexp_for_controller
rs.draw do |map|
map.connect ':controller/:admintoken/:action/:id', :controller => /admin\/.+/
map.connect ':controller/:action/:id'
end
- assert_equal({:controller => ::Admin::UserController, :admintoken => "foo", :action => "index"}.stringify_keys,
- rs.recognize_path(%w(admin user foo)))
- assert_equal({:controller => ::ContentController, :action => "foo"}.stringify_keys,
- rs.recognize_path(%w(content foo)))
- assert_equal ['/admin/user/foo', []], rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index")
- assert_equal ['/content/foo',[]], rs.generate(:controller => "content", :action => "foo")
+ assert_equal({:controller => "admin/user", :admintoken => "foo", :action => "index"},
+ rs.recognize_path("/admin/user/foo"))
+ assert_equal({:controller => "content", :action => "foo"}, rs.recognize_path("/content/foo"))
+ assert_equal '/admin/user/foo', rs.generate(:controller => "admin/user", :admintoken => "foo", :action => "index")
+ assert_equal '/content/foo', rs.generate(:controller => "content", :action => "foo")
end
def test_basic_named_route
- rs.home '', :controller => 'content', :action => 'list'
- x = setup_for_named_route
- assert_equal({:controller => '/content', :action => 'list'},
- x.new.send(:home_url))
+ rs.add_named_route :home, '', :controller => 'content', :action => 'list'
+ x = setup_for_named_route.new
+ assert_equal({:controller => 'content', :action => 'list', :use_route => :home},
+ x.send(:home_url))
end
def test_named_route_with_option
- rs.page 'page/:title', :controller => 'content', :action => 'show_page'
- x = setup_for_named_route
- assert_equal({:controller => '/content', :action => 'show_page', :title => 'new stuff'},
- x.new.send(:page_url, :title => 'new stuff'))
+ rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page'
+ x = setup_for_named_route.new
+ assert_equal({:controller => 'content', :action => 'show_page', :title => 'new stuff', :use_route => :page},
+ x.send(:page_url, :title => 'new stuff'))
end
def test_named_route_with_default
- rs.page 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
- x = setup_for_named_route
- assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutPage'},
- x.new.send(:page_url))
- assert_equal({:controller => '/content', :action => 'show_page', :title => 'AboutRails'},
- x.new.send(:page_url, :title => "AboutRails"))
+ rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage'
+ x = setup_for_named_route.new
+ assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutPage', :use_route => :page},
+ x.send(:page_url))
+ assert_equal({:controller => 'content', :action => 'show_page', :title => 'AboutRails', :use_route => :page},
+ x.send(:page_url, :title => "AboutRails"))
end
def setup_for_named_route
x = Class.new
x.send(:define_method, :url_for) {|x| x}
- x.send :include, ::ActionController::Routing::NamedRoutes
+ rs.named_routes.install(x)
x
end
def test_named_route_without_hash
rs.draw do |map|
- rs.normal ':controller/:action/:id'
+ map.normal ':controller/:action/:id'
end
end
def test_named_route_with_regexps
rs.draw do |map|
- rs.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show',
+ map.article 'page/:year/:month/:day/:title', :controller => 'page', :action => 'show',
:year => /^\d+$/, :month => /^\d+$/, :day => /^\d+$/
- rs.connect ':controller/:action/:id'
+ map.connect ':controller/:action/:id'
end
- x = setup_for_named_route
+ x = setup_for_named_route.new
assert_equal(
- {:controller => '/page', :action => 'show', :title => 'hi'},
- x.new.send(:article_url, :title => 'hi')
+ {:controller => 'page', :action => 'show', :title => 'hi', :use_route => :article},
+ x.send(:article_url, :title => 'hi')
)
assert_equal(
- {:controller => '/page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6},
- x.new.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
+ {:controller => 'page', :action => 'show', :title => 'hi', :day => 10, :year => 2005, :month => 6, :use_route => :article},
+ x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6)
)
end
def test_changing_controller
- assert_equal ['/admin/stuff/show/10', []], rs.generate(
+ assert_equal '/admin/stuff/show/10', rs.generate(
{:controller => 'stuff', :action => 'show', :id => 10},
{:controller => 'admin/user', :action => 'index'}
)
@@ -791,192 +173,189 @@ class RouteSetTests < Test::Unit::TestCase
def test_paths_escaped
rs.draw do |map|
- rs.path 'file/*path', :controller => 'content', :action => 'show_file'
- rs.connect ':controller/:action/:id'
+ map.path 'file/*path', :controller => 'content', :action => 'show_file'
+ map.connect ':controller/:action/:id'
end
- results = rs.recognize_path %w(file hello+world how+are+you%3F)
+ results = rs.recognize_path "/file/hello+world/how+are+you%3F"
assert results, "Recognition should have succeeded"
- assert_equal ['hello world', 'how are you?'], results['path']
+ assert_equal ['hello world', 'how are you?'], results[:path]
- results = rs.recognize_path %w(file)
+ results = rs.recognize_path "/file"
assert results, "Recognition should have succeeded"
- assert_equal [], results['path']
+ assert_equal [], results[:path]
end
def test_non_controllers_cannot_be_matched
- rs.draw do
- rs.connect ':controller/:action/:id'
+ rs.draw do |map|
+ map.connect ':controller/:action/:id'
end
- assert_nil rs.recognize_path(%w(not_a show 10)), "Shouldn't recognize non-controllers as controllers!"
+ assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") }
end
def test_paths_do_not_accept_defaults
assert_raises(ActionController::RoutingError) do
rs.draw do |map|
- rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
- rs.connect ':controller/:action/:id'
+ map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default)
+ map.connect ':controller/:action/:id'
end
end
rs.draw do |map|
- rs.path 'file/*path', :controller => 'content', :action => 'show_file', :path => []
- rs.connect ':controller/:action/:id'
+ map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => []
+ map.connect ':controller/:action/:id'
end
end
def test_dynamic_path_allowed
rs.draw do |map|
- rs.connect '*path', :controller => 'content', :action => 'show_file'
+ map.connect '*path', :controller => 'content', :action => 'show_file'
end
- assert_equal ['/pages/boo', []], rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo))
+ assert_equal '/pages/boo', rs.generate(:controller => 'content', :action => 'show_file', :path => %w(pages boo))
end
def test_backwards
rs.draw do |map|
- rs.connect 'page/:id/:action', :controller => 'pages', :action => 'show'
- rs.connect ':controller/:action/:id'
+ map.connect 'page/:id/:action', :controller => 'pages', :action => 'show'
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/page/20', []], rs.generate({:id => 20}, {:controller => 'pages'})
- assert_equal ['/page/20', []], rs.generate(:controller => 'pages', :id => 20, :action => 'show')
- assert_equal ['/pages/boo', []], rs.generate(:controller => 'pages', :action => 'boo')
+ assert_equal '/page/20', rs.generate({:id => 20}, {:controller => 'pages', :action => 'show'})
+ assert_equal '/page/20', rs.generate(:controller => 'pages', :id => 20, :action => 'show')
+ assert_equal '/pages/boo', rs.generate(:controller => 'pages', :action => 'boo')
end
def test_route_with_fixnum_default
rs.draw do |map|
- rs.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1
- rs.connect ':controller/:action/:id'
+ map.connect 'page/:id', :controller => 'content', :action => 'show_page', :id => 1
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page')
- assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 1)
- assert_equal ['/page', []], rs.generate(:controller => 'content', :action => 'show_page', :id => '1')
- assert_equal ['/page/10', []], rs.generate(:controller => 'content', :action => 'show_page', :id => 10)
-
- ctrl = ::ContentController
+ assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page')
+ assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => 1)
+ assert_equal '/page', rs.generate(:controller => 'content', :action => 'show_page', :id => '1')
+ assert_equal '/page/10', rs.generate(:controller => 'content', :action => 'show_page', :id => 10)
- assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => 1}, rs.recognize_path(%w(page)))
- assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '1'}, rs.recognize_path(%w(page 1)))
- assert_equal({'controller' => ctrl, 'action' => 'show_page', 'id' => '10'}, rs.recognize_path(%w(page 10)))
+ assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page"))
+ assert_equal({:controller => "content", :action => 'show_page', :id => '1'}, rs.recognize_path("/page/1"))
+ assert_equal({:controller => "content", :action => 'show_page', :id => '10'}, rs.recognize_path("/page/10"))
end
def test_action_expiry
- assert_equal ['/content', []], rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
+ assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
end
def test_recognition_with_uppercase_controller_name
- assert_equal({'controller' => ::ContentController, 'action' => 'index'}, rs.recognize_path(%w(Content)))
- assert_equal({'controller' => ::ContentController, 'action' => 'list'}, rs.recognize_path(%w(Content list)))
- assert_equal({'controller' => ::ContentController, 'action' => 'show', 'id' => '10'}, rs.recognize_path(%w(Content show 10)))
+ assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content"))
+ assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list"))
+ assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10"))
- assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin NewsFeed)))
- assert_equal({'controller' => ::Admin::NewsFeedController, 'action' => 'index'}, rs.recognize_path(%w(Admin News_Feed)))
+ # these used to work, before the routes rewrite, but support for this was pulled in the new version...
+ #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed"))
+ #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed"))
end
def test_both_requirement_and_optional
- rs.draw do
- rs.blog('test/:year', :controller => 'post', :action => 'show',
+ rs.draw do |map|
+ map.blog('test/:year', :controller => 'post', :action => 'show',
:defaults => { :year => nil },
:requirements => { :year => /\d{4}/ }
)
- rs.connect ':controller/:action/:id'
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show')
- assert_equal ['/test', []], rs.generate(:controller => 'post', :action => 'show', :year => nil)
+ assert_equal '/test', rs.generate(:controller => 'post', :action => 'show')
+ assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil)
- x = setup_for_named_route
- assert_equal({:controller => '/post', :action => 'show'},
- x.new.send(:blog_url))
+ x = setup_for_named_route.new
+ assert_equal({:controller => 'post', :action => 'show', :use_route => :blog},
+ x.send(:blog_url))
end
def test_set_to_nil_forgets
- rs.draw do
- rs.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil
- rs.connect ':controller/:action/:id'
+ rs.draw do |map|
+ map.connect 'pages/:year/:month/:day', :controller => 'content', :action => 'list_pages', :month => nil, :day => nil
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/pages/2005', []],
+ assert_equal '/pages/2005',
rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005)
- assert_equal ['/pages/2005/6', []],
+ assert_equal '/pages/2005/6',
rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6)
- assert_equal ['/pages/2005/6/12', []],
+ assert_equal '/pages/2005/6/12',
rs.generate(:controller => 'content', :action => 'list_pages', :year => 2005, :month => 6, :day => 12)
- assert_equal ['/pages/2005/6/4', []],
+ assert_equal '/pages/2005/6/4',
rs.generate({:day => 4}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
- assert_equal ['/pages/2005/6', []],
+ assert_equal '/pages/2005/6',
rs.generate({:day => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
- assert_equal ['/pages/2005', []],
+ assert_equal '/pages/2005',
rs.generate({:day => nil, :month => nil}, {:controller => 'content', :action => 'list_pages', :year => '2005', :month => '6', :day => '12'})
end
def test_url_with_no_action_specified
- rs.draw do
- rs.connect '', :controller => 'content'
- rs.connect ':controller/:action/:id'
+ rs.draw do |map|
+ map.connect '', :controller => 'content'
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index')
- assert_equal ['/', []], rs.generate(:controller => 'content')
+ assert_equal '/', rs.generate(:controller => 'content', :action => 'index')
+ assert_equal '/', rs.generate(:controller => 'content')
end
def test_named_url_with_no_action_specified
- rs.draw do
- rs.root '', :controller => 'content'
- rs.connect ':controller/:action/:id'
+ rs.draw do |map|
+ map.root '', :controller => 'content'
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/', []], rs.generate(:controller => 'content', :action => 'index')
- assert_equal ['/', []], rs.generate(:controller => 'content')
+ assert_equal '/', rs.generate(:controller => 'content', :action => 'index')
+ assert_equal '/', rs.generate(:controller => 'content')
- x = setup_for_named_route
- assert_equal({:controller => '/content', :action => 'index'},
- x.new.send(:root_url))
+ x = setup_for_named_route.new
+ assert_equal({:controller => 'content', :action => 'index', :use_route => :root},
+ x.send(:root_url))
end
def test_url_generated_when_forgetting_action
[{:controller => 'content', :action => 'index'}, {:controller => 'content'}].each do |hash|
- rs.draw do
- rs.root '', hash
- rs.connect ':controller/:action/:id'
+ rs.draw do |map|
+ map.root '', hash
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/', []], rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
- assert_equal ['/', []], rs.generate({:controller => 'content'})
- assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'})
+ assert_equal '/', rs.generate({:action => nil}, {:controller => 'content', :action => 'hello'})
+ assert_equal '/', rs.generate({:controller => 'content'})
+ assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'})
end
end
def test_named_route_method
- rs.draw do
- assert_raises(ArgumentError) { rs.categories 'categories', :controller => 'content', :action => 'categories' }
-
- rs.named_route :categories, 'categories', :controller => 'content', :action => 'categories'
- rs.connect ':controller/:action/:id'
+ rs.draw do |map|
+ map.categories 'categories', :controller => 'content', :action => 'categories'
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/categories', []], rs.generate(:controller => 'content', :action => 'categories')
- assert_equal ['/content/hi', []], rs.generate({:controller => 'content', :action => 'hi'})
+ assert_equal '/categories', rs.generate(:controller => 'content', :action => 'categories')
+ assert_equal '/content/hi', rs.generate({:controller => 'content', :action => 'hi'})
end
- def test_named_route_helper_array
+ def test_named_routes_array
test_named_route_method
- assert_equal [:categories_url, :hash_for_categories_url], ::ActionController::Routing::NamedRoutes::Helpers
+ assert_equal [:categories], rs.named_routes.names
end
def test_nil_defaults
- rs.draw do
- rs.connect 'journal',
+ rs.draw do |map|
+ map.connect 'journal',
:controller => 'content',
:action => 'list_journal',
:date => nil, :user_id => nil
- rs.connect ':controller/:action/:id'
+ map.connect ':controller/:action/:id'
end
- assert_equal ['/journal', []], rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil)
+ assert_equal '/journal', rs.generate(:controller => 'content', :action => 'list_journal', :date => nil, :user_id => nil)
end
def setup_request_method_routes_for(method)
@@ -985,10 +364,10 @@ class RouteSetTests < Test::Unit::TestCase
@request.request_uri = "/match"
rs.draw do |r|
- r.connect '/match', :controller => 'books', :action => 'get', :require => { :method => :get }
- r.connect '/match', :controller => 'books', :action => 'post', :require => { :method => :post }
- r.connect '/match', :controller => 'books', :action => 'put', :require => { :method => :put }
- r.connect '/match', :controller => 'books', :action => 'delete', :require => { :method => :delete }
+ r.connect '/match', :controller => 'books', :action => 'get', :conditions => { :method => :get }
+ r.connect '/match', :controller => 'books', :action => 'post', :conditions => { :method => :post }
+ r.connect '/match', :controller => 'books', :action => 'put', :conditions => { :method => :put }
+ r.connect '/match', :controller => 'books', :action => 'delete', :conditions => { :method => :delete }
end
end
@@ -1000,7 +379,7 @@ class RouteSetTests < Test::Unit::TestCase
setup_request_method_routes_for(request_method)
assert_nothing_raised { rs.recognize(@request) }
- assert_equal request_method.downcase, @request.path_parameters["action"]
+ assert_equal request_method.downcase, @request.path_parameters[:action]
ensure
Object.send(:remove_const, :BooksController) rescue nil
end
@@ -1017,29 +396,21 @@ class RouteSetTests < Test::Unit::TestCase
r.connect '/posts/:id', :controller => 'subpath_books', :action => "show"
end
- hash = rs.recognize_path %w(books 17;edit)
+ hash = rs.recognize_path "/books/17;edit"
assert_not_nil hash
- assert_equal %w(subpath_books 17 edit), [hash["controller"].controller_name, hash["id"], hash["action"]]
+ assert_equal %w(subpath_books 17 edit), [hash[:controller], hash[:id], hash[:action]]
- hash = rs.recognize_path %w(items 3;complete)
+ hash = rs.recognize_path "/items/3;complete"
assert_not_nil hash
- assert_equal %w(subpath_books 3 complete), [hash["controller"].controller_name, hash["id"], hash["action"]]
+ assert_equal %w(subpath_books 3 complete), [hash[:controller], hash[:id], hash[:action]]
- hash = rs.recognize_path %w(posts new;preview)
+ hash = rs.recognize_path "/posts/new;preview"
assert_not_nil hash
- assert_equal %w(subpath_books preview), [hash["controller"].controller_name, hash["action"]]
+ assert_equal %w(subpath_books preview), [hash[:controller], hash[:action]]
- hash = rs.recognize_path %w(posts 7)
+ hash = rs.recognize_path "/posts/7"
assert_not_nil hash
- assert_equal %w(subpath_books show 7), [hash["controller"].controller_name, hash["action"], hash["id"]]
-
- # for now, low-hanging fruit only. We don't allow subpath components anywhere
- # except at the end of the path
- assert_raises(ActionController::RoutingError) do
- rs.draw do |r|
- r.connect '/books;german/new', :controller => 'subpath_books', :action => "new"
- end
- end
+ assert_equal %w(subpath_books show 7), [hash[:controller], hash[:action], hash[:id]]
ensure
Object.send(:remove_const, :SubpathBooksController) rescue nil
end
@@ -1053,12 +424,892 @@ class RouteSetTests < Test::Unit::TestCase
r.connect '/posts/new;:action', :controller => 'subpath_books'
end
- assert_equal ["/books/7;edit", []], rs.generate(:controller => "subpath_books", :id => 7, :action => "edit")
- assert_equal ["/items/15;complete", []], rs.generate(:controller => "subpath_books", :id => 15, :action => "complete")
- assert_equal ["/posts/new;preview", []], rs.generate(:controller => "subpath_books", :action => "preview")
+ assert_equal "/books/7;edit", rs.generate(:controller => "subpath_books", :id => 7, :action => "edit")
+ assert_equal "/items/15;complete", rs.generate(:controller => "subpath_books", :id => 15, :action => "complete")
+ assert_equal "/posts/new;preview", rs.generate(:controller => "subpath_books", :action => "preview")
ensure
Object.send(:remove_const, :SubpathBooksController) rescue nil
end
end
+class SegmentTest < Test::Unit::TestCase
+
+ def test_first_segment_should_interpolate_for_structure
+ s = ROUTING::Segment.new
+ def s.interpolation_statement(array) 'hello' end
+ assert_equal 'hello', s.continue_string_structure([])
+ end
+
+ def test_interpolation_statement
+ s = ROUTING::StaticSegment.new
+ s.value = "Hello"
+ assert_equal "Hello", eval(s.interpolation_statement([]))
+ assert_equal "HelloHello", eval(s.interpolation_statement([s]))
+
+ s2 = ROUTING::StaticSegment.new
+ s2.value = "-"
+ assert_equal "Hello-Hello", eval(s.interpolation_statement([s, s2]))
+
+ s3 = ROUTING::StaticSegment.new
+ s3.value = "World"
+ assert_equal "Hello-World", eval(s3.interpolation_statement([s, s2]))
+ end
+
+end
+
+class StaticSegmentTest < Test::Unit::TestCase
+
+ def test_interpolation_chunk_should_respect_raw
+ s = ROUTING::StaticSegment.new
+ s.value = 'Hello/World'
+ assert ! s.raw?
+ assert_equal 'Hello/World', CGI.unescape(s.interpolation_chunk)
+
+ s.raw = true
+ assert s.raw?
+ assert_equal 'Hello/World', s.interpolation_chunk
+ end
+
+ def test_regexp_chunk_should_escape_specials
+ s = ROUTING::StaticSegment.new
+
+ s.value = 'Hello*World'
+ assert_equal 'Hello\*World', s.regexp_chunk
+
+ s.value = 'HelloWorld'
+ assert_equal 'HelloWorld', s.regexp_chunk
+ end
+
+ def test_regexp_chunk_should_add_question_mark_for_optionals
+ s = ROUTING::StaticSegment.new
+ s.value = "/"
+ s.is_optional = true
+ assert_equal "/?", s.regexp_chunk
+
+ s.value = "hello"
+ assert_equal "(?:hello)?", s.regexp_chunk
+ end
+
+end
+
+class DynamicSegmentTest < Test::Unit::TestCase
+
+ def segment
+ unless @segment
+ @segment = ROUTING::DynamicSegment.new
+ @segment.key = :a
+ end
+ @segment
+ end
+
+ def test_extract_value
+ s = ROUTING::DynamicSegment.new
+ s.key = :a
+
+ hash = {:a => '10', :b => '20'}
+ assert_equal '10', eval(s.extract_value)
+
+ hash = {:b => '20'}
+ assert_equal nil, eval(s.extract_value)
+
+ s.default = '20'
+ assert_equal '20', eval(s.extract_value)
+ end
+
+ def test_default_local_name
+ assert_equal 'a_value', segment.local_name,
+ "Unexpected name -- all value_check tests will fail!"
+ end
+
+ def test_presence_value_check
+ a_value = 10
+ assert eval(segment.value_check)
+ end
+
+ def test_regexp_value_check_rejects_nil
+ segment.regexp = /\d+/
+ a_value = nil
+ assert ! eval(segment.value_check)
+ end
+
+ def test_optional_regexp_value_check_should_accept_nil
+ segment.regexp = /\d+/
+ segment.is_optional = true
+ a_value = nil
+ assert eval(segment.value_check)
+ end
+
+ def test_regexp_value_check_rejects_no_match
+ segment.regexp = /\d+/
+
+ a_value = "Hello20World"
+ assert ! eval(segment.value_check)
+
+ a_value = "20Hi"
+ assert ! eval(segment.value_check)
+ end
+
+ def test_regexp_value_check_accepts_match
+ segment.regexp = /\d+/
+
+ a_value = "30"
+ assert eval(segment.value_check)
+ end
+
+ def test_value_check_fails_on_nil
+ a_value = nil
+ assert ! eval(segment.value_check)
+ end
+
+ def test_optional_value_needs_no_check
+ segment.is_optional = true
+ a_value = nil
+ assert_equal nil, segment.value_check
+ end
+
+ def test_regexp_value_check_should_accept_match_with_default
+ segment.regexp = /\d+/
+ segment.default = '200'
+
+ a_value = '100'
+ assert eval(segment.value_check)
+ end
+
+ def test_expiry_should_not_trigger_once_expired
+ not_expired = false
+ hash = merged = {:a => 2, :b => 3}
+ options = {:b => 3}
+ expire_on = Hash.new { raise 'No!!!' }
+
+ eval(segment.expiry_statement)
+ rescue RuntimeError
+ flunk "Expiry check should not have occured!"
+ end
+
+ def test_expiry_should_occur_according_to_expire_on
+ not_expired = true
+ hash = merged = {:a => 2, :b => 3}
+ options = {:b => 3}
+
+ expire_on = {:b => true, :a => false}
+ eval(segment.expiry_statement)
+ assert not_expired
+ assert_equal({:a => 2, :b => 3}, hash)
+
+ expire_on = {:b => true, :a => true}
+ eval(segment.expiry_statement)
+ assert ! not_expired
+ assert_equal({:b => 3}, hash)
+ end
+
+ def test_extraction_code_should_return_on_nil
+ hash = merged = {:b => 3}
+ options = {:b => 3}
+ a_value = nil
+
+ # Local jump because of return inside eval.
+ assert_raises(LocalJumpError) { eval(segment.extraction_code) }
+ end
+
+ def test_extraction_code_should_return_on_mismatch
+ segment.regexp = /\d+/
+ hash = merged = {:a => 'Hi', :b => '3'}
+ options = {:b => '3'}
+ a_value = nil
+
+ # Local jump because of return inside eval.
+ assert_raises(LocalJumpError) { eval(segment.extraction_code) }
+ end
+
+ def test_extraction_code_should_accept_value_and_set_local
+ hash = merged = {:a => 'Hi', :b => '3'}
+ options = {:b => '3'}
+ a_value = nil
+
+ eval(segment.extraction_code)
+ assert_equal 'Hi', a_value
+ end
+
+ def test_extraction_should_work_without_value_check
+ segment.default = 'hi'
+ hash = merged = {:b => '3'}
+ options = {:b => '3'}
+ a_value = nil
+
+ eval(segment.extraction_code)
+ assert_equal 'hi', a_value
+ end
+
+ def test_extraction_code_should_perform_expiry
+ not_expired = true
+ hash = merged = {:a => 'Hi', :b => '3'}
+ options = {:b => '3'}
+ expire_on = {:a => true}
+ a_value = nil
+
+ eval(segment.extraction_code)
+ assert_equal 'Hi', a_value
+ assert ! not_expired
+ assert_equal options, hash
+ end
+
+ def test_interpolation_chunk_should_replace_value
+ a_value = 'Hi'
+ assert_equal a_value, eval(%("#{segment.interpolation_chunk}"))
+ end
+
+ def test_value_regexp_should_be_nil_without_regexp
+ assert_equal nil, segment.value_regexp
+ end
+
+ def test_value_regexp_should_match_exacly
+ segment.regexp = /\d+/
+ assert_no_match segment.value_regexp, "Hello 10 World"
+ assert_no_match segment.value_regexp, "Hello 10"
+ assert_no_match segment.value_regexp, "10 World"
+ assert_match segment.value_regexp, "10"
+ end
+
+ def test_regexp_chunk_should_return_string
+ segment.regexp = /\d+/
+ assert_kind_of String, segment.regexp_chunk
+ end
+
+end
+
+class ControllerSegmentTest < Test::Unit::TestCase
+
+ def test_regexp_should_only_match_possible_controllers
+ ActionController::Routing.with_controllers %w(admin/accounts admin/users account pages) do
+ cs = ROUTING::ControllerSegment.new :controller
+ regexp = %r{\A#{cs.regexp_chunk}\Z}
+
+ ActionController::Routing.possible_controllers.each do |name|
+ assert_match regexp, name
+ assert_no_match regexp, "#{name}_fake"
+
+ match = regexp.match name
+ assert_equal name, match[1]
+ end
+ end
+ end
+
+end
+
+class RouteTest < Test::Unit::TestCase
+
+ def setup
+ @route = ROUTING::Route.new
+ end
+
+ def slash_segment(is_optional = false)
+ returning ROUTING::DividerSegment.new('/') do |s|
+ s.is_optional = is_optional
+ end
+ end
+
+ def default_route
+ unless @default_route
+ @default_route = ROUTING::Route.new
+
+ @default_route.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+
+ @default_route.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :controller
+
+ @default_route.segments << slash_segment(:optional)
+ @default_route.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :action
+ s.default = 'index'
+ s.is_optional = true
+
+ @default_route.segments << slash_segment(:optional)
+ @default_route.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :id
+ s.is_optional = true
+
+ @default_route.segments << slash_segment(:optional)
+ end
+ @default_route
+ end
+
+ def test_default_route_recognition
+ expected = {:controller => 'accounts', :action => 'show', :id => '10'}
+ assert_equal expected, default_route.recognize('/accounts/show/10')
+ assert_equal expected, default_route.recognize('/accounts/show/10/')
+
+ expected[:id] = 'jamis'
+ assert_equal expected, default_route.recognize('/accounts/show/jamis/')
+
+ expected.delete :id
+ assert_equal expected, default_route.recognize('/accounts/show')
+ assert_equal expected, default_route.recognize('/accounts/show/')
+
+ expected[:action] = 'index'
+ assert_equal expected, default_route.recognize('/accounts/')
+ assert_equal expected, default_route.recognize('/accounts')
+
+ assert_equal nil, default_route.recognize('/')
+ assert_equal nil, default_route.recognize('/accounts/how/goood/it/is/to/be/free')
+ end
+
+ def test_default_route_should_omit_default_action
+ o = {:controller => 'accounts', :action => 'index'}
+ assert_equal '/accounts', default_route.generate(o, o, {})
+ end
+
+ def test_default_route_should_include_default_action_when_id_present
+ o = {:controller => 'accounts', :action => 'index', :id => '20'}
+ assert_equal '/accounts/index/20', default_route.generate(o, o, {})
+ end
+
+ def test_default_route_should_work_with_action_but_no_id
+ o = {:controller => 'accounts', :action => 'list_all'}
+ assert_equal '/accounts/list_all', default_route.generate(o, o, {})
+ end
+
+ def test_parameter_shell
+ page_url = ROUTING::Route.new
+ page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/}
+ assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell)
+ end
+
+ def test_defaults
+ route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html"
+ assert_equal(
+ { :controller => "users", :action => "show", :format => "html" },
+ route.defaults)
+ end
+
+ def test_significant_keys_for_default_route
+ keys = default_route.significant_keys.sort_by {|k| k.to_s }
+ assert_equal [:action, :controller, :id], keys
+ end
+
+ def test_significant_keys
+ user_url = ROUTING::Route.new
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = 'user'
+
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+ s.is_optional = true
+
+ user_url.segments << (s = ROUTING::DynamicSegment.new)
+ s.key = :user
+
+ user_url.segments << (s = ROUTING::StaticSegment.new)
+ s.value = '/'
+ s.raw = true
+ s.is_optional = true
+
+ user_url.requirements = {:controller => 'users', :action => 'show'}
+
+ keys = user_url.significant_keys.sort_by { |k| k.to_s }
+ assert_equal [:action, :controller, :user], keys
+ end
+
+ def test_build_empty_query_string
+ assert_equal '', @route.build_query_string({})
+ end
+
+ def test_simple_build_query_string
+ assert_equal '?x=1&y=2', @route.build_query_string(:x => '1', :y => '2')
+ end
+
+ def test_convert_ints_build_query_string
+ assert_equal '?x=1&y=2', @route.build_query_string(:x => 1, :y => 2)
+ end
+
+ def test_escape_spaces_build_query_string
+ assert_equal '?x=hello+world&y=goodbye+world', @route.build_query_string(:x => 'hello world', :y => 'goodbye world')
+ end
+
+ def test_expand_array_build_query_string
+ assert_equal '?x[]=1&x[]=2', @route.build_query_string(:x => [1, 2])
+ end
+
+ def test_escape_spaces_build_query_string_selected_keys
+ assert_equal '?x=hello+world', @route.build_query_string({:x => 'hello world', :y => 'goodbye world'}, [:x])
+ end
+end
+
+class RouteBuilderTest < Test::Unit::TestCase
+
+ def builder
+ @bulider ||= ROUTING::RouteBuilder.new
+ end
+
+ def test_segment_for_static
+ segment, rest = builder.segment_for 'ulysses'
+ assert_equal '', rest
+ assert_kind_of ROUTING::StaticSegment, segment
+ assert_equal 'ulysses', segment.value
+ end
+
+ def test_segment_for_action
+ segment, rest = builder.segment_for ':action'
+ assert_equal '', rest
+ assert_kind_of ROUTING::DynamicSegment, segment
+ assert_equal :action, segment.key
+ assert_equal 'index', segment.default
+ end
+
+ def test_segment_for_dynamic
+ segment, rest = builder.segment_for ':login'
+ assert_equal '', rest
+ assert_kind_of ROUTING::DynamicSegment, segment
+ assert_equal :login, segment.key
+ assert_equal nil, segment.default
+ assert ! segment.optional?
+ end
+
+ def test_segment_for_with_rest
+ segment, rest = builder.segment_for ':login/:action'
+ assert_equal :login, segment.key
+ assert_equal '/:action', rest
+ segment, rest = builder.segment_for rest
+ assert_equal '/', segment.value
+ assert_equal ':action', rest
+ segment, rest = builder.segment_for rest
+ assert_equal :action, segment.key
+ assert_equal '', rest
+ end
+
+ def test_segments_for
+ segments = builder.segments_for_route_path '/:controller/:action/:id'
+
+ assert_kind_of ROUTING::DividerSegment, segments[0]
+ assert_equal '/', segments[2].value
+
+ assert_kind_of ROUTING::DynamicSegment, segments[1]
+ assert_equal :controller, segments[1].key
+
+ assert_kind_of ROUTING::DividerSegment, segments[2]
+ assert_equal '/', segments[2].value
+
+ assert_kind_of ROUTING::DynamicSegment, segments[3]
+ assert_equal :action, segments[3].key
+
+ assert_kind_of ROUTING::DividerSegment, segments[4]
+ assert_equal '/', segments[4].value
+
+ assert_kind_of ROUTING::DynamicSegment, segments[5]
+ assert_equal :id, segments[5].key
+ end
+
+ def test_segment_for_action
+ s, r = builder.segment_for(':action/something/else')
+ assert_equal '/something/else', r
+ assert_equal 'index', s.default
+ assert_equal :action, s.key
+ end
+
+ def test_action_default_should_not_trigger_on_prefix
+ s, r = builder.segment_for ':action_name/something/else'
+ assert_equal '/something/else', r
+ assert_equal :action_name, s.key
+ assert_equal nil, s.default
+ end
+
+ def test_divide_route_options
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults, requirements = builder.divide_route_options(segments,
+ :action => 'buy', :person => /\w+/, :car => /\w+/,
+ :defaults => {:person => nil, :car => nil}
+ )
+
+ assert_equal({:action => 'buy', :person => nil, :car => nil}, defaults)
+ assert_equal({:person => /\w+/, :car => /\w+/}, requirements)
+ end
+
+ def test_assign_route_options
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults = {:action => 'buy', :person => nil, :car => nil}
+ requirements = {:person => /\w+/, :car => /\w+/}
+
+ route_requirements = builder.assign_route_options(segments, defaults, requirements)
+ assert_equal({}, route_requirements)
+
+ assert_equal :action, segments[3].key
+ assert_equal 'buy', segments[3].default
+
+ assert_equal :person, segments[5].key
+ assert_equal %r/\w+/, segments[5].regexp
+ assert segments[5].optional?
+
+ assert_equal :car, segments[7].key
+ assert_equal %r/\w+/, segments[7].regexp
+ assert segments[7].optional?
+ end
+
+ def test_optional_segments_preceding_required_segments
+ segments = builder.segments_for_route_path '/cars/:action/:person/:car/'
+ defaults = {:action => 'buy', :person => nil, :car => "model-t"}
+ assert builder.assign_route_options(segments, defaults, {}).empty?
+
+ 0.upto(1) { |i| assert !segments[i].optional?, "segment #{i} is optional and it shouldn't be" }
+ assert segments[2].optional?
+
+ assert_equal nil, builder.warn_output # should only warn on the :person segment
+ end
+
+ def test_segmentation_of_semicolon_path
+ segments = builder.segments_for_route_path '/books/:id;:action'
+ defaults = { :action => 'show' }
+ assert builder.assign_route_options(segments, defaults, {}).empty?
+ segments.each do |segment|
+ assert ! segment.optional? || segment.key == :action
+ end
+ end
+
+ def test_segmentation_of_dot_path
+ segments = builder.segments_for_route_path '/books/:action.rss'
+ assert builder.assign_route_options(segments, {}, {}).empty?
+ assert_equal 6, segments.length # "/", "books", "/", ":action", ".", "rss"
+ assert !segments.any? { |seg| seg.optional? }
+ end
+
+ def test_segmentation_of_dynamic_dot_path
+ segments = builder.segments_for_route_path '/books/:action.:format'
+ assert builder.assign_route_options(segments, {}, {}).empty?
+ assert_equal 6, segments.length # "/", "books", "/", ":action", ".", ":format"
+ assert !segments.any? { |seg| seg.optional? }
+ assert_kind_of ROUTING::DynamicSegment, segments.last
+ end
+
+ def test_assignment_of_is_optional_when_default
+ segments = builder.segments_for_route_path '/books/:action.rss'
+ assert_equal segments[3].key, :action
+ segments[3].default = 'changes'
+ builder.ensure_required_segments(segments)
+ assert ! segments[3].optional?
+ end
+
+ def test_is_optional_is_assigned_to_default_segments
+ segments = builder.segments_for_route_path '/books/:action'
+ builder.assign_route_options(segments, {:action => 'index'}, {})
+
+ assert_equal segments[3].key, :action
+ assert segments[3].optional?
+ assert_kind_of ROUTING::DividerSegment, segments[2]
+ assert segments[2].optional?
+ end
+
+ # XXX is optional not being set right?
+ # /blah/:defaulted_segment <-- is the second slash optional? it should be.
+
+ def test_route_build
+ ActionController::Routing.with_controllers %w(users pages) do
+ r = builder.build '/:controller/:action/:id/', :action => nil
+
+ [0, 2, 4].each do |i|
+ assert_kind_of ROUTING::DividerSegment, r.segments[i]
+ assert_equal '/', r.segments[i].value
+ assert r.segments[i].optional? if i > 1
+ end
+
+ assert_kind_of ROUTING::DynamicSegment, r.segments[1]
+ assert_equal :controller, r.segments[1].key
+ assert_equal nil, r.segments[1].default
+
+ assert_kind_of ROUTING::DynamicSegment, r.segments[3]
+ assert_equal :action, r.segments[3].key
+ assert_equal 'index', r.segments[3].default
+
+ assert_kind_of ROUTING::DynamicSegment, r.segments[5]
+ assert_equal :id, r.segments[5].key
+ assert r.segments[5].optional?
+ end
+ end
+
+ def test_slashes_are_implied
+ routes = [
+ builder.build('/:controller/:action/:id/', :action => nil),
+ builder.build('/:controller/:action/:id', :action => nil),
+ builder.build(':controller/:action/:id', :action => nil),
+ builder.build('/:controller/:action/:id/', :action => nil)
+ ]
+ expected = routes.first.segments.length
+ routes.each_with_index do |route, i|
+ found = route.segments.length
+ assert_equal expected, found, "Route #{i + 1} has #{found} segments, expected #{expected}"
+ end
+ end
+
+end
+
+class RouteSetTest < Test::Unit::TestCase
+ class MockController
+ attr_accessor :routes
+
+ def initialize(routes)
+ self.routes = routes
+ end
+
+ def url_for(options)
+ path = routes.generate(options)
+ "http://named.route.test#{path}"
+ end
+ end
+
+ class MockRequest
+ attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method
+
+ def initialize(values={})
+ values.each { |key, value| send("#{key}=", value) }
+ if values[:host]
+ subdomain, self.domain = values[:host].split(/\./, 2)
+ self.subdomains = [subdomain]
+ end
+ end
+ end
+
+ def set
+ @set ||= ROUTING::RouteSet.new
+ end
+
+ def request
+ @request ||= MockRequest.new(:host => "named.routes.test", :method => :get)
+ end
+
+ def test_generate_extras
+ set.draw { |m| m.connect ':controller/:action/:id' }
+ path, extras = set.generate_extras(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal "/foo/bar/15", path
+ assert_equal %w(that this), extras.map(&:to_s).sort
+ end
+
+ def test_extra_keys
+ set.draw { |m| m.connect ':controller/:action/:id' }
+ extras = set.extra_keys(:controller => "foo", :action => "bar", :id => 15, :this => "hello", :that => "world")
+ assert_equal %w(that this), extras.map(&:to_s).sort
+ end
+
+ def test_draw
+ assert_equal 0, set.routes.size
+ set.draw do |map|
+ map.connect '/hello/world', :controller => 'a', :action => 'b'
+ end
+ assert_equal 1, set.routes.size
+ end
+
+ def test_named_draw
+ assert_equal 0, set.routes.size
+ set.draw do |map|
+ map.hello '/hello/world', :controller => 'a', :action => 'b'
+ end
+ assert_equal 1, set.routes.size
+ assert_equal set.routes.first, set.named_routes[:hello]
+ end
+
+ def test_later_named_routes_take_precedence
+ set.draw do |map|
+ map.hello '/hello/world', :controller => 'a', :action => 'b'
+ map.hello '/hello', :controller => 'a', :action => 'b'
+ end
+ assert_equal set.routes.last, set.named_routes[:hello]
+ end
+
+ def setup_named_route_test
+ set.draw do |map|
+ map.show '/people/:id', :controller => 'people', :action => 'show'
+ map.index '/people', :controller => 'people', :action => 'index'
+ map.multi '/people/go/:foo/:bar/joe/:id', :controller => 'people', :action => 'multi'
+ end
+
+ klass = Class.new(MockController)
+ set.named_routes.install(klass)
+ klass.new(set)
+ end
+
+ def test_named_route_hash_access_method
+ controller = setup_named_route_test
+
+ assert_equal(
+ { :controller => 'people', :action => 'show', :id => 5, :use_route => :show },
+ controller.send(:hash_for_show_url, :id => 5))
+
+ assert_equal(
+ { :controller => 'people', :action => 'index', :use_route => :index },
+ controller.send(:hash_for_index_url))
+ end
+
+ def test_named_route_url_method
+ controller = setup_named_route_test
+
+ assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5)
+ assert_equal "http://named.route.test/people", controller.send(:index_url)
+ end
+
+ def test_namd_route_url_method_with_ordered_parameters
+ controller = setup_named_route_test
+ assert_equal "http://named.route.test/people/go/7/hello/joe/5",
+ controller.send(:multi_url, 7, "hello", 5)
+ end
+
+ def test_draw_default_route
+ ActionController::Routing.with_controllers(['users']) do
+ set.draw do |map|
+ map.connect '/:controller/:action/:id'
+ end
+
+ assert_equal 1, set.routes.size
+ route = set.routes.first
+
+ assert route.segments.last.optional?
+
+ assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10)
+ assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10)
+
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10'))
+ assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/'))
+ end
+ end
+
+ def test_route_with_parameter_shell
+ ActionController::Routing.with_controllers(['users', 'pages']) do
+ set.draw do |map|
+ map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
+ map.connect '/:controller/:action/:id'
+ end
+
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages'))
+ assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index'))
+ assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list'))
+
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10'))
+ assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10'))
+ end
+ end
+
+ def test_recognize_with_conditions
+ Object.const_set(:PeopleController, Class.new)
+
+ set.draw do |map|
+ map.with_options(:controller => "people") do |people|
+ people.people "/people", :action => "index", :conditions => { :method => :get }
+ people.connect "/people", :action => "create", :conditions => { :method => :post }
+ people.person "/people/:id", :action => "show", :conditions => { :method => :get }
+ people.connect "/people/:id", :action => "update", :conditions => { :method => :put }
+ people.connect "/people/:id", :action => "destroy", :conditions => { :method => :delete }
+ end
+ end
+
+ request.path = "/people"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("index", request.path_parameters[:action])
+
+ request.method = :post
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("create", request.path_parameters[:action])
+
+ request.path = "/people/5"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("show", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ request.method = :put
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("update", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ request.method = :delete
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("destroy", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+ ensure
+ Object.send(:remove_const, :PeopleController)
+ end
+
+ def test_recognize_with_conditions_and_format
+ Object.const_set(:PeopleController, Class.new)
+
+ set.draw do |map|
+ map.with_options(:controller => "people") do |people|
+ people.person "/people/:id", :action => "show", :conditions => { :method => :get }
+ people.connect "/people/:id", :action => "update", :conditions => { :method => :put }
+ people.connect "/people/:id.:_format", :action => "show", :conditions => { :method => :get }
+ end
+ end
+
+ request.path = "/people/5"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("show", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+
+ request.method = :put
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("update", request.path_parameters[:action])
+
+ request.path = "/people/5.png"
+ request.method = :get
+ assert_nothing_raised { set.recognize(request) }
+ assert_equal("show", request.path_parameters[:action])
+ assert_equal("5", request.path_parameters[:id])
+ assert_equal("png", request.path_parameters[:_format])
+ ensure
+ Object.send(:remove_const, :PeopleController)
+ end
+
+ def test_generate_with_default_action
+ set.draw do |map|
+ map.connect "/people", :controller => "people"
+ map.connect "/people/list", :controller => "people", :action => "list"
+ end
+
+ url = set.generate(:controller => "people", :action => "list")
+ assert_equal "/people/list", url
+ end
+
+ def test_generate_finds_best_fit
+ set.draw do |map|
+ map.connect "/people", :controller => "people", :action => "index"
+ map.connect "/ws/people", :controller => "people", :action => "index", :ws => true
+ end
+
+ url = set.generate(:controller => "people", :action => "index", :ws => true)
+ assert_equal "/ws/people", url
+ end
+end
+
+class RoutingTest < Test::Unit::TestCase
+
+ def test_possible_controllers
+ true_load_paths = $LOAD_PATH.dup
+
+ ActionController::Routing.use_controllers! nil
+ Object.send(:const_set, :RAILS_ROOT, File.dirname(__FILE__) + '/controller_fixtures')
+
+ $LOAD_PATH.clear
+ $LOAD_PATH.concat [
+ RAILS_ROOT, RAILS_ROOT + '/app/controllers', RAILS_ROOT + '/vendor/plugins/bad_plugin/lib'
+ ]
+
+ assert_equal ["admin/user", "plugin", "user"], ActionController::Routing.possible_controllers.sort
+ ensure
+ if true_load_paths
+ $LOAD_PATH.clear
+ $LOAD_PATH.concat true_load_paths
+ end
+ Object.send(:remove_const, :RAILS_ROOT) rescue nil
+ end
+
+ def test_with_controllers
+ c = %w(admin/accounts admin/users account pages)
+ ActionController::Routing.with_controllers c do
+ assert_equal c, ActionController::Routing.possible_controllers
+ end
+ end
+
end
diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb
index 2c341e751b..58ed819e12 100644
--- a/actionpack/test/controller/test_test.rb
+++ b/actionpack/test/controller/test_test.rb
@@ -81,6 +81,7 @@ HTML
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
ActionController::Routing::Routes.reload
+ ActionController::Routing.use_controllers! %w(content admin/user)
end
def teardown
@@ -317,9 +318,9 @@ HTML
def test_array_path_parameter_handled_properly
with_routing do |set|
- set.draw do
- set.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params'
- set.connect ':controller/:action/:id'
+ set.draw do |map|
+ map.connect 'file/*path', :controller => 'test_test/test', :action => 'test_params'
+ map.connect ':controller/:action/:id'
end
get :test_params, :path => ['hello', 'world']
@@ -440,9 +441,9 @@ HTML
protected
def with_foo_routing
with_routing do |set|
- set.draw do
- set.generate_url 'foo', :controller => 'test'
- set.connect ':controller/:action/:id'
+ set.draw do |map|
+ map.generate_url 'foo', :controller => 'test'
+ map.connect ':controller/:action/:id'
end
yield set
end
diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb
index 1219abdd9f..57a38f2a35 100644
--- a/actionpack/test/controller/url_rewriter_test.rb
+++ b/actionpack/test/controller/url_rewriter_test.rb
@@ -6,23 +6,6 @@ class UrlRewriterTests < Test::Unit::TestCase
@params = {}
@rewriter = ActionController::UrlRewriter.new(@request, @params)
end
-
- def test_simple_build_query_string
- assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => '1', :y => '2')
- end
- def test_convert_ints_build_query_string
- assert_query_equal '?x=1&y=2', @rewriter.send(:build_query_string, :x => 1, :y => 2)
- end
- def test_escape_spaces_build_query_string
- assert_query_equal '?x=hello+world&y=goodbye+world', @rewriter.send(:build_query_string, :x => 'hello world', :y => 'goodbye world')
- end
- def test_expand_array_build_query_string
- assert_query_equal '?x[]=1&x[]=2', @rewriter.send(:build_query_string, :x => [1, 2])
- end
-
- def test_escape_spaces_build_query_string_selected_keys
- assert_query_equal '?x=hello+world', @rewriter.send(:build_query_string, {:x => 'hello world', :y => 'goodbye world'}, [:x])
- end
def test_overwrite_params
@params[:controller] = 'hi'
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index f5831d3352..25ba0e9620 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Minor tweak to dispatcher to use recognize instead of recognize!, as per the new routes. [Jamis Buck]
+
* Make "script/plugin install" work with svn+ssh URLs. [Sam Stephenson]
* Added lib/ to the directories that will get application docs generated [DHH]
diff --git a/railties/lib/dispatcher.rb b/railties/lib/dispatcher.rb
index 721a367f71..6290be3774 100644
--- a/railties/lib/dispatcher.rb
+++ b/railties/lib/dispatcher.rb
@@ -35,7 +35,7 @@ class Dispatcher
if cgi ||= new_cgi(output)
request, response = ActionController::CgiRequest.new(cgi, session_options), ActionController::CgiResponse.new(cgi)
prepare_application
- ActionController::Routing::Routes.recognize!(request).process(request, response).out(output)
+ ActionController::Routing::Routes.recognize(request).process(request, response).out(output)
end
rescue Object => exception
failsafe_response(output, '500 Internal Server Error', exception) do