aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/routing
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/routing')
-rw-r--r--actionpack/lib/action_controller/routing/builder.rb55
-rw-r--r--actionpack/lib/action_controller/routing/optimisations.rb10
-rw-r--r--actionpack/lib/action_controller/routing/recognition_optimisation.rb2
-rw-r--r--actionpack/lib/action_controller/routing/route.rb22
-rw-r--r--actionpack/lib/action_controller/routing/route_set.rb75
-rw-r--r--actionpack/lib/action_controller/routing/routing_ext.rb4
-rw-r--r--actionpack/lib/action_controller/routing/segments.rb59
7 files changed, 165 insertions, 62 deletions
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb
index 7b888fa8d2..44d759444a 100644
--- a/actionpack/lib/action_controller/routing/builder.rb
+++ b/actionpack/lib/action_controller/routing/builder.rb
@@ -1,23 +1,16 @@
module ActionController
module Routing
class RouteBuilder #:nodoc:
- attr_accessor :separators, :optional_separators
+ attr_reader :separators, :optional_separators
+ attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
def initialize
- self.separators = Routing::SEPARATORS
- self.optional_separators = %w( / )
- end
-
- def separator_pattern(inverted = false)
- "[#{'^' if inverted}#{Regexp.escape(separators.join)}]"
- end
-
- def interval_regexp
- Regexp.new "(.*?)(#{separators.source}|$)"
- end
+ @separators = Routing::SEPARATORS
+ @optional_separators = %w( / )
- def multiline_regexp?(expression)
- expression.options & Regexp::MULTILINE == Regexp::MULTILINE
+ @separator_regexp = /[#{Regexp.escape(separators.join)}]/
+ @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
+ @interval_regexp = /(.*?)(#{separator_regexp}|$)/
end
# Accepts a "route path" (a string defining a route), and returns the array
@@ -30,7 +23,7 @@ module ActionController
rest, segments = path, []
until rest.empty?
- segment, rest = segment_for rest
+ segment, rest = segment_for(rest)
segments << segment
end
segments
@@ -39,20 +32,22 @@ module ActionController
# 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 :controller then ControllerSegment.new(key)
- else DynamicSegment.new key
- end
- when /\A\*(\w+)/ then PathSegment.new($1.to_sym, :optional => true)
- when /\A\?(.*?)\?/
- StaticSegment.new($1, :optional => true)
- when /\A(#{separator_pattern(:inverted)}+)/ then StaticSegment.new($1)
- when Regexp.new(separator_pattern) then
- DividerSegment.new($&, :optional => (optional_separators.include? $&))
- end
+ segment =
+ case string
+ when /\A\.(:format)?\//
+ OptionalFormatSegment.new
+ when /\A:(\w+)/
+ key = $1.to_sym
+ key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
+ when /\A\*(\w+)/
+ PathSegment.new($1.to_sym, :optional => true)
+ when /\A\?(.*?)\?/
+ StaticSegment.new($1, :optional => true)
+ when nonseparator_regexp
+ StaticSegment.new($1)
+ when separator_regexp
+ DividerSegment.new($&, :optional => optional_separators.include?($&))
+ end
[segment, $~.post_match]
end
@@ -98,7 +93,7 @@ module ActionController
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
- if multiline_regexp?(requirement)
+ if requirement.multiline?
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
segment.regexp = requirement
diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb
index 0a87303bda..714cf97861 100644
--- a/actionpack/lib/action_controller/routing/optimisations.rb
+++ b/actionpack/lib/action_controller/routing/optimisations.rb
@@ -65,7 +65,7 @@ module ActionController
# rather than triggering the expensive logic in +url_for+.
class PositionalArguments < Optimiser
def guard_conditions
- number_of_arguments = route.segment_keys.size
+ number_of_arguments = route.required_segment_keys.size
# if they're using foo_url(:id=>2) it's one
# argument, but we don't want to generate /foos/id2
if number_of_arguments == 1
@@ -106,12 +106,8 @@ module ActionController
# argument
class PositionalArgumentsWithAdditionalParams < PositionalArguments
def guard_conditions
- [
- "args.size == #{route.segment_keys.size + 1}",
- "!args.last.has_key?(:anchor)",
- "!args.last.has_key?(:port)",
- "!args.last.has_key?(:host)"
- ]
+ ["args.size == #{route.segment_keys.size + 1}"] +
+ UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
end
# This case uses almost the same code as positional arguments,
diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
index 6c47ced6d1..3b98b16683 100644
--- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb
+++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb
@@ -148,7 +148,7 @@ module ActionController
end
nil
end
- }, __FILE__, __LINE__
+ }, '(recognize_optimized)', 1
end
def clear_recognize_optimized!
diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb
index 3b2cb28545..e2077edad8 100644
--- a/actionpack/lib/action_controller/routing/route.rb
+++ b/actionpack/lib/action_controller/routing/route.rb
@@ -35,6 +35,11 @@ module ActionController
segment.key if segment.respond_to? :key
end.compact
end
+
+ def required_segment_keys
+ required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
+ required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
+ end
# 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
@@ -122,6 +127,16 @@ module ActionController
super
end
+ def generate(options, hash, expire_on = {})
+ path, hash = generate_raw(options, hash, expire_on)
+ append_query_string(path, hash, extra_keys(options))
+ end
+
+ def generate_extras(options, hash, expire_on = {})
+ path, hash = generate_raw(options, hash, expire_on)
+ [path, extra_keys(options)]
+ end
+
private
def requirement_for(key)
return requirements[key] if requirements.key? key
@@ -150,11 +165,6 @@ module ActionController
# the query string. (Never use keys from the recalled request when building the
# query string.)
- method_decl = "def generate(#{args})\npath, hash = generate_raw(options, hash, expire_on)\nappend_query_string(path, hash, extra_keys(options))\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
-
- method_decl = "def generate_extras(#{args})\npath, hash = generate_raw(options, hash, expire_on)\n[path, extra_keys(options)]\nend"
- instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
raw_method
end
@@ -219,7 +229,7 @@ module ActionController
next_capture = 1
extraction = segments.collect do |segment|
x = segment.match_extraction(next_capture)
- next_capture += Regexp.new(segment.regexp_chunk).number_of_captures
+ next_capture += segment.number_of_captures
x
end
extraction.compact
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb
index ff448490e9..13646aef61 100644
--- a/actionpack/lib/action_controller/routing/route_set.rb
+++ b/actionpack/lib/action_controller/routing/route_set.rb
@@ -7,6 +7,8 @@ module ActionController
# Mapper instances have relatively few instance methods, in order to avoid
# clashes with named routes.
class Mapper #:doc:
+ include ActionController::Resources
+
def initialize(set) #:nodoc:
@set = set
end
@@ -136,9 +138,13 @@ module ActionController
end
end
+ def named_helper_module_eval(code, *args)
+ @module.module_eval(code, *args)
+ end
+
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
- @module.module_eval <<-end_eval # We use module_eval to avoid leaks
+ named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(options = nil)
options ? #{options.inspect}.merge(options) : #{options.inspect}
end
@@ -166,8 +172,9 @@ module ActionController
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
- @module.module_eval <<-end_eval # We use module_eval to avoid leaks
+ named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(*args)
+
#{generate_optimisation_block(route, kind)}
opts = if args.empty? || Hash === args.first
@@ -182,6 +189,14 @@ module ActionController
end
url_for(#{hash_access_method}(opts))
+
+ end
+ #Add an alias to support the now deprecated formatted_* URL.
+ def formatted_#{selector}(*args)
+ ActiveSupport::Deprecation.warn(
+ "formatted_#{selector}() has been deprecated. please pass format to the standard" +
+ "#{selector}() method instead.", caller)
+ #{selector}(*args)
end
protected :#{selector}
end_eval
@@ -189,9 +204,11 @@ module ActionController
end
end
- attr_accessor :routes, :named_routes, :configuration_file
+ attr_accessor :routes, :named_routes, :configuration_files
def initialize
+ self.configuration_files = []
+
self.routes = []
self.named_routes = NamedRouteCollection.new
@@ -205,7 +222,6 @@ module ActionController
end
def draw
- clear!
yield Mapper.new(self)
install_helpers
end
@@ -229,8 +245,22 @@ module ActionController
routes.empty?
end
+ def add_configuration_file(path)
+ self.configuration_files << path
+ end
+
+ # Deprecated accessor
+ def configuration_file=(path)
+ add_configuration_file(path)
+ end
+
+ # Deprecated accessor
+ def configuration_file
+ configuration_files
+ end
+
def load!
- Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
+ Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
clear!
load_routes!
end
@@ -239,24 +269,39 @@ module ActionController
alias reload! load!
def reload
- if @routes_last_modified && configuration_file
- mtime = File.stat(configuration_file).mtime
- # if it hasn't been changed, then just return
- return if mtime == @routes_last_modified
- # if it has changed then record the new time and fall to the load! below
- @routes_last_modified = mtime
+ if configuration_files.any? && @routes_last_modified
+ if routes_changed_at == @routes_last_modified
+ return # routes didn't change, don't reload
+ else
+ @routes_last_modified = routes_changed_at
+ end
end
+
load!
end
def load_routes!
- if configuration_file
- load configuration_file
- @routes_last_modified = File.stat(configuration_file).mtime
+ if configuration_files.any?
+ configuration_files.each { |config| load(config) }
+ @routes_last_modified = routes_changed_at
else
add_route ":controller/:action/:id"
end
end
+
+ def routes_changed_at
+ routes_changed_at = nil
+
+ configuration_files.each do |config|
+ config_changed_at = File.stat(config).mtime
+
+ if routes_changed_at.nil? || config_changed_at > routes_changed_at
+ routes_changed_at = config_changed_at
+ end
+ end
+
+ routes_changed_at
+ end
def add_route(path, options = {})
route = builder.build(path, options)
@@ -358,7 +403,7 @@ module ActionController
end
# don't use the recalled keys when determining which routes to check
- routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]
+ routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
routes.each do |route|
results = route.__send__(method, options, merged, expire_on)
diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb
index 5f4ba90d0c..4a82b2af5f 100644
--- a/actionpack/lib/action_controller/routing/routing_ext.rb
+++ b/actionpack/lib/action_controller/routing/routing_ext.rb
@@ -27,6 +27,10 @@ class Regexp #:nodoc:
Regexp.new("|#{source}").match('').captures.length
end
+ def multiline?
+ options & MULTILINE == MULTILINE
+ end
+
class << self
def optionalize(pattern)
case unoptionalize(pattern)
diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
index e5f174ae2c..5dda3d4d00 100644
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ b/actionpack/lib/action_controller/routing/segments.rb
@@ -13,6 +13,10 @@ module ActionController
@is_optional = false
end
+ def number_of_captures
+ Regexp.new(regexp_chunk).number_of_captures
+ end
+
def extraction_code
nil
end
@@ -84,6 +88,10 @@ module ActionController
optional? ? Regexp.optionalize(chunk) : chunk
end
+ def number_of_captures
+ 0
+ end
+
def build_pattern(pattern)
escaped = Regexp.escape(value)
if optional? && ! pattern.empty?
@@ -194,10 +202,16 @@ module ActionController
end
end
+ def number_of_captures
+ if regexp
+ regexp.number_of_captures + 1
+ else
+ 1
+ end
+ end
+
def build_pattern(pattern)
- chunk = regexp_chunk
- chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
- pattern = "#{chunk}#{pattern}"
+ pattern = "#{regexp_chunk}#{pattern}"
optional? ? Regexp.optionalize(pattern) : pattern
end
@@ -230,6 +244,10 @@ module ActionController
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
end
+ def number_of_captures
+ 1
+ end
+
# Don't URI.escape the controller name since it may contain slashes.
def interpolation_chunk(value_code = local_name)
"\#{#{value_code}.to_s}"
@@ -275,6 +293,10 @@ module ActionController
regexp || "(.*)"
end
+ def number_of_captures
+ regexp ? regexp.number_of_captures : 1
+ end
+
def optionality_implied?
true
end
@@ -286,5 +308,36 @@ module ActionController
end
end
end
+
+ # The OptionalFormatSegment allows for any resource route to have an optional
+ # :format, which decreases the amount of routes created by 50%.
+ class OptionalFormatSegment < DynamicSegment
+
+ def initialize(key = nil, options = {})
+ super(:format, {:optional => true}.merge(options))
+ end
+
+ def interpolation_chunk
+ "." + super
+ end
+
+ def regexp_chunk
+ '(\.[^/?\.]+)?'
+ end
+
+ def to_s
+ '(.:format)?'
+ end
+
+ #the value should not include the period (.)
+ def match_extraction(next_capture)
+ %[
+ if (m = match[#{next_capture}])
+ params[:#{key}] = URI.unescape(m.from(1))
+ end
+ ]
+ end
+ end
+
end
end