diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2005-02-15 01:45:35 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2005-02-15 01:45:35 +0000 |
commit | b1999be5a7efd67e2602c37ed898aa8433661863 (patch) | |
tree | 03bc833276075d802d0ce0ad261baed3d7232533 /actionpack/lib/action_controller | |
parent | 88a3343ed57c01ca358da8473d15fc4d2b4a5bff (diff) | |
download | rails-b1999be5a7efd67e2602c37ed898aa8433661863.tar.gz rails-b1999be5a7efd67e2602c37ed898aa8433661863.tar.bz2 rails-b1999be5a7efd67e2602c37ed898aa8433661863.zip |
A hopefully more successful attempt at the Routing branch merge
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@617 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r-- | actionpack/lib/action_controller/assertions/action_pack_assertions.rb | 32 | ||||
-rwxr-xr-x | actionpack/lib/action_controller/base.rb | 32 | ||||
-rw-r--r-- | actionpack/lib/action_controller/cgi_process.rb | 10 | ||||
-rw-r--r-- | actionpack/lib/action_controller/helpers.rb | 35 | ||||
-rwxr-xr-x | actionpack/lib/action_controller/request.rb | 16 | ||||
-rw-r--r-- | actionpack/lib/action_controller/rescue.rb | 18 | ||||
-rw-r--r-- | actionpack/lib/action_controller/routing.rb | 260 | ||||
-rw-r--r-- | actionpack/lib/action_controller/scaffolding.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/action_controller/templates/rescues/routing_error.rhtml | 8 | ||||
-rw-r--r-- | actionpack/lib/action_controller/test_process.rb | 22 | ||||
-rw-r--r-- | actionpack/lib/action_controller/url_rewriter.rb | 96 |
11 files changed, 421 insertions, 110 deletions
diff --git a/actionpack/lib/action_controller/assertions/action_pack_assertions.rb b/actionpack/lib/action_controller/assertions/action_pack_assertions.rb index c26941cd6b..a8819550e6 100644 --- a/actionpack/lib/action_controller/assertions/action_pack_assertions.rb +++ b/actionpack/lib/action_controller/assertions/action_pack_assertions.rb @@ -141,7 +141,7 @@ module Test #:nodoc: end end end - + # ensure our redirection url is an exact match def assert_redirect_url(url=nil, message=nil) assert_redirect(message) @@ -158,6 +158,36 @@ module Test #:nodoc: assert_block(msg) { response.redirect_url_match?(pattern) } end + # -- routing assertions -------------------------------------------------- + + # Asserts that the routing of the given path is handled correctly and that the parsed options match. + # Also verifies that the provided options can be used to generate the provided path. + def assert_routing(path, options, defaults={}, extras={}, message=nil) + defaults[:controller] ||= options[:controller] # Assume given controller, + request = ActionController::TestRequest.new({}, {}, nil) + request.path_parameters = defaults.clone + + ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty? # Load routes.rb if it hasn't been loaded. + + generated_path, found_extras = ActionController::Routing::Routes.generate(options, request) + generated_path = generated_path.join('/') + msg = build_message(message, "found extras <?>, not <?>", found_extras, extras) + assert_block(msg) { found_extras == extras } + + msg = build_message(message, "The generated path <?> did not match <?>", generated_path, path) + assert_block(msg) { path == generated_path } + + request = ActionController::TestRequest.new({}, {}, nil) + request.path = path + ActionController::Routing::Routes.recognize!(request) + + expected_options = options.clone + extras.each {|k,v| expected_options.delete k} + + msg = build_message(message, "The recognized options <?> did not match <?>", request.path_parameters, expected_options) + assert_block(msg) { request.path_parameters == expected_options } + end + # -- template assertions ------------------------------------------------ # ensure that a template object with the given name exists diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index cfcd46d985..5d7ccf72f1 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,5 +1,6 @@ require 'action_controller/request' require 'action_controller/response' +require 'action_controller/routing' require 'action_controller/url_rewriter' require 'action_controller/support/class_attribute_accessors' require 'action_controller/support/class_inheritable_attributes' @@ -13,6 +14,15 @@ module ActionController #:nodoc: end class MissingTemplate < ActionControllerError #:nodoc: end + class RoutingError < ActionControllerError + attr_reader :failures + def initialize(message, failures=[]) + super(message) + @failures = failures + end + end + class UnknownController < ActionControllerError #:nodoc: + end class UnknownAction < ActionControllerError #:nodoc: end class MissingFile < ActionControllerError #:nodoc: @@ -205,6 +215,12 @@ module ActionController #:nodoc: # should instead be implemented in the controller to determine when debugging screens should be shown. @@consider_all_requests_local = true cattr_accessor :consider_all_requests_local + + # Enable or disable the collection of failure information for RoutingErrors. + # This information can be extremely useful when tweaking custom routes, but is + # pointless once routes have been tested and verified. + @@debug_routes = true + cattr_accessor :debug_routes # Template root determines the base from which template references will be made. So a call to render("test/template") # will be converted to "#{template_root}/test/template.rhtml". @@ -261,6 +277,14 @@ module ActionController #:nodoc: def controller_name Inflector.underscore(controller_class_name.sub(/Controller/, "")) end + + # Convert the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat". + def controller_path + components = self.name.to_s.split('::').collect { |name| name.underscore } + components[-1] = $1 if /^(.*)_controller$/ =~ components[-1] + components.shift if components.first == 'controllers' # Transitional conditional to accomodate root Controllers module + components.join('/') + end end public @@ -337,10 +361,6 @@ module ActionController #:nodoc: end end - def module_name - @params["module"] - end - # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController". def controller_class_name self.class.controller_class_name @@ -594,7 +614,7 @@ module ActionController #:nodoc: end def initialize_current_url - @url = UrlRewriter.new(@request, controller_name, action_name) + @url = UrlRewriter.new(@request, @params.clone()) end def log_processing @@ -691,7 +711,7 @@ module ActionController #:nodoc: end def default_template_name(default_action_name = action_name) - module_name ? "#{module_name}/#{controller_name}/#{default_action_name}" : "#{controller_name}/#{default_action_name}" + "#{self.class.controller_path}/#{default_action_name}" end end end diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb index 92b19fbf18..c301b322e5 100644 --- a/actionpack/lib/action_controller/cgi_process.rb +++ b/actionpack/lib/action_controller/cgi_process.rb @@ -46,8 +46,16 @@ module ActionController #:nodoc: super() end + def query_string + return @cgi.query_string unless @cgi.query_string.nil? || @cgi.query_string.empty? + parts = env['REQUEST_URI'].split('?') + parts.shift + return parts.join('?') + end + def query_parameters - @cgi.query_string ? CGIMethods.parse_query_parameters(@cgi.query_string) : {} + qs = self.query_string + qs.empty? ? {} : CGIMethods.parse_query_parameters(query_string) end def request_parameters diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb index 1201d31946..a97fd93410 100644 --- a/actionpack/lib/action_controller/helpers.rb +++ b/actionpack/lib/action_controller/helpers.rb @@ -48,25 +48,22 @@ module ActionController #:nodoc: def helper(*args, &block) args.flatten.each do |arg| case arg - when Module - add_template_helper(arg) - when String, Symbol - file_name = Inflector.underscore(arg.to_s.downcase) + '_helper' - class_name = Inflector.camelize(file_name) - begin - require_dependency(file_name) - rescue LoadError => load_error - requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1] - if requiree == file_name - raise LoadError, "Missing helper file helpers/#{file_name}.rb" - else - raise LoadError, "Can't load file: #{requiree}" + when Module + add_template_helper(arg) + when String, Symbol + file_name = arg.to_s.underscore + '_helper' + class_name = file_name.camelize + + begin + require_dependency(file_name) + rescue LoadError => load_error + requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1] + raise LoadError, requiree == file_name ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" end - end - raise ArgumentError, "Missing #{class_name} module in helpers/#{file_name}.rb" unless Object.const_defined?(class_name) - add_template_helper(Object.const_get(class_name)) - else - raise ArgumentError, 'helper expects String, Symbol, or Module argument' + + add_template_helper(class_name.constantize) + else + raise ArgumentError, 'helper expects String, Symbol, or Module argument' end end @@ -95,7 +92,7 @@ module ActionController #:nodoc: def inherited(child) inherited_without_helper(child) begin - child.helper(child.controller_name) + child.helper(child.controller_path) rescue ArgumentError, LoadError # No default helper available for this controller end diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 3e2344c3cb..2e7957caf9 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -3,7 +3,8 @@ module ActionController class AbstractRequest # Returns both GET and POST parameters in a single hash. def parameters - @parameters ||= request_parameters.update(query_parameters) + # puts "#{request_parameters.inspect} | #{query_parameters.inspect} | #{path_parameters.inspect}" + @parameters ||= request_parameters.merge(query_parameters).merge(path_parameters).with_indifferent_access end def method @@ -73,7 +74,7 @@ module ActionController end def request_uri - env['REQUEST_URI'] + (%r{^\w+\://[^/]+(/.*|$)$} =~ env['REQUEST_URI']) ? $1 : env['REQUEST_URI'] # Remove domain, which webrick puts into the request_uri. end def protocol @@ -85,7 +86,7 @@ module ActionController end def path - request_uri ? request_uri.split('?').first : '' + path = request_uri ? request_uri.split('?').first : '' end def port @@ -100,7 +101,16 @@ module ActionController def host_with_port env['HTTP_HOST'] || host + port_string end + + def path_parameters=(parameters) + @path_parameters = parameters + @parameters = nil + end + def path_parameters + @path_parameters ||= {} + end + #-- # Must be implemented in the concrete request #++ diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index eb7f614de0..907cebc250 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -48,7 +48,11 @@ module ActionController #:nodoc: # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). def rescue_action_in_public(exception) #:doc: - render_text "<html><body><h1>Application error (Rails)</h1></body></html>" + case exception + when RoutingError, UnknownAction then + render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found") + else render_text "<html><body><h1>Application error (Rails)</h1></body></html>" + end end # Overwrite to expand the meaning of a local request in order to show local rescues on other occurences than @@ -66,7 +70,7 @@ module ActionController #:nodoc: @contents = @template.render_file(template_path_for_local_rescue(exception), false) @headers["Content-Type"] = "text/html" - render_file(rescues_path("layout"), "500 Internal Error") + render_file(rescues_path("layout"), response_code_for_rescue(exception)) end private @@ -110,13 +114,21 @@ module ActionController #:nodoc: rescues_path( case exception when MissingTemplate then "missing_template" + when RoutingError then "routing_error" when UnknownAction then "unknown_action" when ActionView::TemplateError then "template_error" - else "diagnostics" + else raise ;"diagnostics" end ) end + def response_code_for_rescue(exception) + case exception + when UnknownAction, RoutingError then "404 Page Not Found" + else "500 Internal Error" + end + end + def clean_backtrace(exception) exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line } end diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb new file mode 100644 index 0000000000..39ce487e62 --- /dev/null +++ b/actionpack/lib/action_controller/routing.rb @@ -0,0 +1,260 @@ +module ActionController + module Routing + ROUTE_FILE = defined?(RAILS_ROOT) ? File.expand_path(File.join(RAILS_ROOT, 'config', 'routes')) : nil + + class Route + attr_reader :defaults # The defaults hash + + def initialize(path, hash={}) + raise ArgumentError, "Second argument must be a hash!" unless hash.kind_of?(Hash) + @defaults = {} + @requirements = {} + self.items = path + hash.each do |k, v| + raise TypeError, "Hash may only contain symbols!" unless k.kind_of? Symbol + (@items.include?(k) ? @defaults : @requirements)[k] = v + end + + # Add in defaults for :action and :id. + [[:action, 'index'], [:id, nil]].each do |name, default| + @defaults[name] = default if @items.include?(name) && ! (@requirements.key?(name) || @defaults.key?(name)) + end + end + + # Generate a URL given the provided options. + # All values in options should be symbols. + # Returns the path and the unused names in a 2 element array. + # If generation fails, [nil, nil] is returned + # Generation can fail because of a missing value, or because an equality check fails. + # + # Generate urls will be as short as possible. If the last component of a url is equal to the default value, + # then that component is removed. This is applied as many times as possible. So, your index controller's + # index action will generate [] + def generate(options, defaults={}) + non_matching = @requirements.inject([]) {|a, (k, v)| ((options[k] || defaults[k]) == v) ? a : a << k} + return nil, "Options mismatch requirements: #{non_matching.join ', '}" unless non_matching.empty? + + used_names = @requirements.inject({}) {|hash, (k, v)| hash[k] = true; hash} + components = @items.collect do |item| + if item.kind_of? Symbol + used_names[item] = true + value = options[item] || defaults[item] || @defaults[item] + return nil, "#{item.inspect} was not given and has no default." if value.nil? && ! (@defaults.key?(item) && @defaults[item].nil?) # Don't leave if nil value. + defaults = {} unless defaults == {} || value == defaults[item] # Stop using defaults if this component isn't the same as the default. + value + else item + end + end + + @items.reverse_each do |item| # Remove default components from the end of the generated url. + break unless item.kind_of?(Symbol) && @defaults[item] == components.last + components.pop + end + + # If we have any nil components then we can't proceed. + # This might need to be changed. In some cases we may be able to return all componets after nil as extras. + missing = []; components.each_with_index {|c, i| missing << @items[i] if c.nil?} + return nil, "No values provided for component#{'s' if missing.length > 1} #{missing.join ', '} but values are required due to use of later components" unless missing.empty? # how wide is your screen? + + unused = (options.keys - used_names.keys).inject({}) do |unused, key| + unused[key] = options[key] if options[key] != @defaults[key] + unused + end + + components.collect! {|c| c.to_s} + components.unshift(components.shift + '/') if components.length == 1 && @items.first == :controller # Add '/' to controllers + + return components, unused + end + + # Recognize the provided path, returning a hash of recognized values, or [nil, reason] if the path isn't recognized. + # The path should be a list of component strings. + # Options is a hash of the ?k=v pairs + def recognize(components, options={}) + options = options.clone + components = components.clone + controller_class = nil + + @items.each do |item| + if item == :controller # Special case for controller + if components.empty? && @defaults[:controller] + controller_class, leftover = eat_path_to_controller(@defaults[:controller].split('/')) + raise RoutingError, "Default controller does not exist: #{@defaults[:controller]}" if controller_class.nil? || leftover.empty? == false + else + controller_class, remaining_components = eat_path_to_controller(components) + return nil, "No controller found at subpath #{components.join('/')}" if controller_class.nil? + components = remaining_components + end + options[:controller] = controller_class.controller_path + elsif item.kind_of? Symbol + value = components.shift || @defaults[item] + return nil, "No value or default for parameter #{item.inspect}" if value.nil? && ! (@defaults.key?(item) && @defaults[item].nil?) + options[item] = value + else + return nil, "No value available for component #{item.inspect}" if components.empty? + component = components.shift + return nil, "Value for component #{item.inspect} doesn't match #{component}" if component != item + end + end + + if controller_class.nil? && @requirements[:controller] # Load a default controller + controller_class, extras = eat_path_to_controller(@requirements[:controller].split('/')) + raise RoutingError, "Illegal controller path for route default: #{@requirements[:controller]}" unless controller_class && extras.empty? + options[:controller] = controller_class.controller_path + end + options = @requirements.merge(options) + + return nil, "Route recognition didn't find a controller class!" unless controller_class + return nil, "Unused components were left: #{components.join '/'}" unless components.empty? + options.delete_if {|k, v| v.nil?} # Remove nil values. + return controller_class, options + end + + def inspect + when_str = @requirements.empty? ? "" : " when #{@requirements.inspect}" + default_str = @defaults.empty? ? "" : " || #{@defaults.inspect}" + "<#{self.class.to_s} #{@items.collect{|c| c.kind_of?(String) ? c : c.inspect}.join('/').inspect}#{default_str}#{when_str}>" + end + + protected + # Find the controller given a list of path components. + # Return the controller class and the unused path components. + def eat_path_to_controller(path) + path.inject([Controllers, 1]) do |(mod, length), name| + name = name.camelize + controller_name = name + "Controller" + return mod.const_get(controller_name), path[length..-1] if mod.const_available? controller_name + return nil, nil unless mod.const_available? name + [mod.const_get(name), length + 1] + end + return nil, nil # Path ended, but no controller found. + end + + def items=(path) + items = path.split('/').collect {|c| (/^:(\w+)$/ =~ c) ? $1.intern : c} if path.kind_of?(String) # split and convert ':xyz' to symbols + items.shift if items.first == "" + items.pop if items.last == "" + @items = items + + # Verify uniqueness of each component. + @items.inject({}) do |seen, item| + if item.kind_of? Symbol + raise ArgumentError, "Illegal route path -- duplicate item #{item}\n #{path.inspect}" if seen.key? item + seen[item] = true + end + seen + end + end + end + + class RouteSet + def initialize + @routes = [] + end + + def add_route(route) + raise TypeError, "#{route.inspect} is not a Route instance!" unless route.kind_of?(Route) + @routes << route + end + def empty? + @routes.empty? + end + def each + @routes.each {|route| yield route} + end + + # Generate a path for the provided options + # Returns the path as an array of components and a hash of unused names + # Raises RoutingError if not route can handle the provided components. + # + # Note that we don't return the first generated path. We do this so that when a route + # generates a path from a subset of the available options we can keep looking for a + # route which can generate a path that uses more options. + # Note that we *do* return immediately if + def generate(options, request) + raise RoutingError, "There are no routes defined!" if @routes.empty? + options = options.symbolize_keys + defaults = request.path_parameters.symbolize_keys + expand_controller_path!(options, defaults) + + failures = [] + selected = nil + self.each do |route| + path, unused = route.generate(options, defaults) + if path.nil? + failures << [route, unused] if ActionController::Base.debug_routes + else + return path, unused if unused.empty? # Found a perfect route -- we're finished. + if selected.nil? || unused.length < selected.last.length + failures << [selected.first, "A better url than #{selected[1]} was found."] if selected + selected = [route, path, unused] + end + end + end + + return selected[1..-1] unless selected.nil? + raise RoutingError.new("Generation failure: No route for url_options #{options.inspect}, defaults: #{defaults.inspect}", failures) + end + + # Recognize the provided path. + # Raise RoutingError if the path can't be recognized. + def recognize!(request) + path = ((%r{^/?(.*)/?$} =~ request.path) ? $1 : request.path).split('/') + raise RoutingError, "There are no routes defined!" if @routes.empty? + + failures = [] + self.each do |route| + controller, options = route.recognize(path) + if controller.nil? + failures << [route, options] if ActionController::Base.debug_routes + else + options.each {|k, v| request.path_parameters[k] = CGI.unescape(v)} + return controller + end + end + + raise RoutingError.new("No route for path: #{path.join('/').inspect}", failures) + end + + def expand_controller_path!(options, defaults) + if options[:controller] + if /^\// =~ options[:controller] + options[:controller] = options[:controller][1..-1] + defaults.clear # Sending to absolute controller implies fresh defaults + else + relative_to = defaults[:controller] ? defaults[:controller].split('/')[0..-2].join('/') : '' + options[:controller] = relative_to.empty? ? options[:controller] : "#{relative_to}/#{options[:controller]}" + end + else + options[:controller] = defaults[:controller] + end + end + + def route(*args) + add_route(Route.new(*args)) + end + alias :connect :route + + def reload + begin require_dependency(ROUTE_FILE) + rescue LoadError, ScriptError => e + raise RoutingError, "Cannot load config/routes.rb:\n #{e.message}" + ensure # Ensure that there is at least one route: + connect(':controller/:action/:id', :action => 'index', :id => nil) if @routes.empty? + end + end + + def draw + @routes.clear + yield self + end + end + + def self.draw(*args, &block) + Routes.draw(*args) {|*args| block.call(*args)} + end + + Routes = RouteSet.new + #Routes.reload # Do this here, so that server will die on load if SyntaxError or whatnot. + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/scaffolding.rb b/actionpack/lib/action_controller/scaffolding.rb index 9c1311efa3..140df73972 100644 --- a/actionpack/lib/action_controller/scaffolding.rb +++ b/actionpack/lib/action_controller/scaffolding.rb @@ -149,7 +149,7 @@ module ActionController private def render#{suffix}_scaffold(action = caller_method_name(caller)) - if template_exists?("\#{controller_name}/\#{action}") + if template_exists?("\#{self.class.controller_path}/\#{action}") render_action(action) else @scaffold_class = #{class_name} diff --git a/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml b/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml new file mode 100644 index 0000000000..82c01e10c9 --- /dev/null +++ b/actionpack/lib/action_controller/templates/rescues/routing_error.rhtml @@ -0,0 +1,8 @@ +<h1>Routing Error</h1> +<p><%=h @exception.message %></p> +<% unless @exception.failures.empty? %><p> + <h2>Failure reasons:</h2> + <% @exception.failures.each do |route, reason| %> + <%=h route.inspect.gsub('\\', '') %> failed because <%=h reason.downcase %><br /> + <% end %> +</p><% end %> diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index d4dfe7933d..3223da198c 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -31,8 +31,8 @@ module ActionController #:nodoc: class TestRequest < AbstractRequest #:nodoc: attr_accessor :cookies - attr_accessor :query_parameters, :request_parameters, :session, :env - attr_accessor :host, :path, :request_uri, :remote_addr + attr_accessor :query_parameters, :request_parameters, :path, :session, :env + attr_accessor :host, :remote_addr def initialize(query_parameters = nil, request_parameters = nil, session = nil) @query_parameters = query_parameters || {} @@ -58,11 +58,28 @@ module ActionController #:nodoc: @parameters = nil end + # Used to check AbstractRequest's request_uri functionality. + # Disables the use of @path and @request_uri so superclass can handle those. + def set_REQUEST_URI(value) + @env["REQUEST_URI"] = value + @request_uri = nil + @path = nil + end + def request_uri=(uri) @request_uri = uri @path = uri.split("?").first end + def request_uri + @request_uri || super() + end + + def path + @path || super() + end + + private def initialize_containers @env, @cookies = {}, {} @@ -237,6 +254,7 @@ module Test def process(action, parameters = nil, session = nil) @request.env['REQUEST_METHOD'] ||= "GET" @request.action = action.to_s + @request.path_parameters = { :controller => @controller.class.controller_path } @request.parameters.update(parameters) unless parameters.nil? @request.session = ActionController::TestSession.new(session) unless session.nil? @controller.process(@request, @response) diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 3451e6acc9..a1b1a2a57b 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -1,41 +1,29 @@ module ActionController # Rewrites URLs for Base.redirect_to and Base.url_for in the controller. + class UrlRewriter #:nodoc: - VALID_OPTIONS = [:action, :action_prefix, :action_suffix, :application_prefix, :module, :controller, :controller_prefix, :anchor, :params, :path_params, :id, :only_path, :overwrite_params, :host, :protocol ] - - def initialize(request, controller, action) - @request, @controller, @action = request, controller, action + RESERVED_OPTIONS = [:anchor, :params, :path_params, :only_path, :host, :protocol] + def initialize(request, parameters) + @request, @parameters = request, parameters @rewritten_path = @request.path ? @request.path.dup : "" end def rewrite(options = {}) - validate_options(VALID_OPTIONS, options.keys) - - rewrite_url( - rewrite_path(@rewritten_path, resolve_aliases(options)), - options - ) - end - - def to_s - to_str + rewrite_url(rewrite_path(options), options) end def to_str - "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@controller}, #{@action}, #{@request.parameters.inspect}" + "#{@request.protocol}, #{@request.host_with_port}, #{@request.path}, #{@parameters[:controller]}, #{@parameters[:action]}, #{@request.parameters.inspect}" end + alias_method :to_s, :to_str + private def validate_options(valid_option_keys, supplied_option_keys) unknown_option_keys = supplied_option_keys - valid_option_keys raise(ActionController::ActionControllerError, "Unknown options: #{unknown_option_keys}") unless unknown_option_keys.empty? end - - def resolve_aliases(options) - options[:controller_prefix] = options[:module] unless options[:module].nil? - options - end - + def rewrite_url(path, options) rewritten_url = "" rewritten_url << (options[:protocol] || @request.protocol) unless options[:only_path] @@ -45,15 +33,18 @@ module ActionController rewritten_url << path rewritten_url << build_query_string(new_parameters(options)) if options[:params] || options[:overwrite_params] rewritten_url << "##{options[:anchor]}" if options[:anchor] + return rewritten_url end - def rewrite_path(path, options) - include_id_in_path_params(options) - - path = rewrite_action(path, options) if options[:action] || options[:action_prefix] - path = rewrite_path_params(path, options) if options[:path_params] - path = rewrite_controller(path, options) if options[:controller] || options[:controller_prefix] + def rewrite_path(options) + options = options.symbolize_keys + RESERVED_OPTIONS.each {|k| options.delete k} + + path, extras = Routing::Routes.generate(options, @request) + path = "/#{path.join('/')}" + path += build_query_string(extras) + return path end @@ -76,49 +67,6 @@ module ActionController end end - def rewrite_action(path, options) - # This regex assumes that "index" actions won't be included in the URL - all, controller_prefix, action_prefix, action_suffix = - /^\/(.*)#{@controller}\/(.*)#{@action == "index" ? "" : @action}(.*)/.match(path).to_a - - if @action == "index" - if action_prefix == "index" - # we broke the parsing assumption that this would be excluded, so - # don't tell action_name about our little boo-boo - path = path.sub(action_prefix, action_name(options, nil)) - elsif action_prefix && !action_prefix.empty? - path = path.sub(%r(/#{action_prefix}/?), "/" + action_name(options, action_prefix)) - else - path = path.sub(%r(#{@controller}/?$), @controller + "/" + action_name(options)) # " ruby-mode - end - else - path = path.sub( - @controller + "/" + (action_prefix || "") + @action + (action_suffix || ""), - @controller + "/" + action_name(options, action_prefix) - ) - end - - if options[:controller_prefix] && !options[:controller] - ensure_slash_suffix(options, :controller_prefix) - if controller_prefix - path = path.sub(controller_prefix, options[:controller_prefix]) - else - path = options[:controller_prefix] + path - end - end - - return path - end - - def rewrite_controller(path, options) - all, controller_prefix = /^\/(.*?)#{@controller}/.match(path).to_a - path = "/" - path << controller_name(options, controller_prefix) - path << action_name(options) if options[:action] - path << path_params_in_list(options) if options[:path_params] - return path - end - def action_name(options, action_prefix = nil, action_suffix = nil) ensure_slash_suffix(options, :action_prefix) ensure_slash_prefix(options, :action_suffix) @@ -174,16 +122,16 @@ module ActionController def build_query_string(hash) elements = [] query_string = "" - + hash.each do |key, value| + key = key.to_s key = CGI.escape key key += '[]' if value.class == Array value = [ value ] unless value.class == Array value.each { |val| elements << "#{key}=#{CGI.escape(val.to_s)}" } - end - - unless elements.empty? then query_string << ("?" + elements.join("&")) end + end + query_string << ("?" + elements.join("&")) unless elements.empty? return query_string end end |