diff options
Diffstat (limited to 'actionpack/lib/action_controller/routing')
5 files changed, 111 insertions, 27 deletions
diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index d4e501e780..44d759444a 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -34,6 +34,8 @@ module ActionController def segment_for(string) segment = case string + when /\A\.(:format)?\// + OptionalFormatSegment.new when /\A:(\w+)/ key = $1.to_sym key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key) 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/route.rb b/actionpack/lib/action_controller/routing/route.rb index a610ec7e84..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 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/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index f6b03edcca..5dda3d4d00 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -308,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 |