diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2005-02-15 00:51:02 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2005-02-15 00:51:02 +0000 |
commit | c844755e5a0c3d4edfcc78f9c30ef91fa0de550a (patch) | |
tree | 4cf4890fc5af5f58dd0a6a19c0a6fea6ed39a1df /actionpack | |
parent | a3298e5efdf33398b49933323ea3fef7ff4e9a9c (diff) | |
download | rails-c844755e5a0c3d4edfcc78f9c30ef91fa0de550a.tar.gz rails-c844755e5a0c3d4edfcc78f9c30ef91fa0de550a.tar.bz2 rails-c844755e5a0c3d4edfcc78f9c30ef91fa0de550a.zip |
Merged back the Routing branch
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@614 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack')
19 files changed, 694 insertions, 145 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..7d27240244 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 + + 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(defaults.merge(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..a30f3b94d7 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -13,6 +13,13 @@ 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 UnknownAction < ActionControllerError #:nodoc: end class MissingFile < ActionControllerError #:nodoc: @@ -205,6 +212,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 +274,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 +358,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 @@ -691,7 +708,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..2ac6081a96 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -3,7 +3,7 @@ module ActionController class AbstractRequest # Returns both GET and POST parameters in a single hash. def parameters - @parameters ||= request_parameters.update(query_parameters) + @parameters ||= request_parameters.merge(query_parameters).merge(path_parameters).with_indifferent_access end def method @@ -73,7 +73,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 +85,7 @@ module ActionController end def path - request_uri ? request_uri.split('?').first : '' + path = request_uri ? request_uri.split('?').first : '' end def port @@ -100,7 +100,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..9eb64ba7cc 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 @@ -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/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..3364262e4c 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -1,10 +1,9 @@ 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 @@ -22,7 +21,7 @@ module ActionController 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 private @@ -48,12 +47,14 @@ module ActionController 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 diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index f771f0a826..bbb38778b7 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -60,7 +60,7 @@ module ActionView if partial_path.include?('/') return File.dirname(partial_path), File.basename(partial_path) else - return controller.send(:controller_name), partial_path + return controller.class.controller_path, partial_path end end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 3f9dafacd6..2b5b1cae9f 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -28,10 +28,6 @@ class CookieTest < Test::Unit::TestCase render_text "hello world" end - def access_frozen_cookies - @cookies["wont"] = "work" - end - def rescue_action(e) raise end end @@ -67,11 +63,6 @@ class CookieTest < Test::Unit::TestCase assert_equal 2, process_request.headers["cookie"].size end - def test_setting_cookie_on_frozen_instance_variable - @request.action = "access_frozen_cookies" - assert_raises(TypeError) { process_request } - end - private def process_request TestController.process(@request, @response) diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index b824e40125..0a9840f705 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -1,20 +1,30 @@ require File.dirname(__FILE__) + '/../abstract_unit' +$:.unshift(File.dirname(__FILE__) + '/../fixtures/helpers') -class HelperTest < Test::Unit::TestCase - HELPER_PATHS = %w(/../fixtures/helpers) +class TestController < ActionController::Base + attr_accessor :delegate_attr + def delegate_method() end + def rescue_action(e) raise end +end + +module Fun + class GamesController < ActionController::Base + def render_hello_world + render_template "hello: <%= stratego %>" + end - class TestController < ActionController::Base - attr_accessor :delegate_attr - def delegate_method() end def rescue_action(e) raise end end +end - module LocalAbcHelper - def a() end - def b() end - def c() end - end +module LocalAbcHelper + def a() end + def b() end + def c() end +end +class HelperTest < Test::Unit::TestCase + HELPER_PATHS = %w(/../fixtures/helpers) def setup # Increment symbol counter. @@ -102,6 +112,13 @@ class HelperTest < Test::Unit::TestCase assert template_methods.include?('delegate_attr=') end + def test_helper_for_nested_controller + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @request.action = "render_hello_world" + + assert_equal "hello: Iz guuut!", Fun::GamesController.process(@request, @response).body + end private def helper_methods; TestHelper.instance_methods end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index f983960e2e..afffd15793 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -2,86 +2,91 @@ require File.dirname(__FILE__) + '/../abstract_unit' Customer = Struct.new("Customer", :name) -class RenderTest < Test::Unit::TestCase - class TestController < ActionController::Base - layout :determine_layout - +module Fun + class GamesController < ActionController::Base def hello_world end + end +end - def render_hello_world - render "test/hello_world" - end - - def render_hello_world_from_variable - @person = "david" - render_text "hello #{@person}" - end - def render_action_hello_world - render_action "hello_world" - end - - def render_text_hello_world - render_text "hello world" - end +class TestController < ActionController::Base + layout :determine_layout - def render_custom_code - render_text "hello world", "404 Moved" - end - - def render_xml_hello - @name = "David" - render "test/hello" - end + def hello_world + end - def greeting - # let's just rely on the template - end + def render_hello_world + render "test/hello_world" + end - def layout_test - render_action "hello_world" - end - - def builder_layout_test - render_action "hello" - end + def render_hello_world_from_variable + @person = "david" + render_text "hello #{@person}" + end - def partials_list - @customers = [ Customer.new("david"), Customer.new("mary") ] - render_action "list" - end + def render_action_hello_world + render_action "hello_world" + end + + def render_text_hello_world + render_text "hello world" + end - def modgreet - end + def render_custom_code + render_text "hello world", "404 Moved" + end + + def render_xml_hello + @name = "David" + render "test/hello" + end - def rescue_action(e) raise end - - private - def determine_layout - case action_name - when "layout_test": "layouts/standard" - when "builder_layout_test": "layouts/builder" - end - end + def greeting + # let's just rely on the template end - TestController.template_root = File.dirname(__FILE__) + "/../fixtures/" + def layout_test + render_action "hello_world" + end - class TestLayoutController < ActionController::Base - layout "layouts/standard" - - def hello_world - end + def builder_layout_test + render_action "hello" + end + + def partials_list + @customers = [ Customer.new("david"), Customer.new("mary") ] + render_action "list" + end + + def rescue_action(e) raise end - def hello_world_outside_layout + private + def determine_layout + case action_name + when "layout_test": "layouts/standard" + when "builder_layout_test": "layouts/builder" + end end +end - def rescue_action(e) - raise unless ActionController::MissingTemplate === e - end +TestController.template_root = File.dirname(__FILE__) + "/../fixtures/" + +class TestLayoutController < ActionController::Base + layout "layouts/standard" + + def hello_world end + + def hello_world_outside_layout + end + + def rescue_action(e) + raise unless ActionController::MissingTemplate === e + end +end +class RenderTest < Test::Unit::TestCase def setup @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @@ -170,10 +175,9 @@ class RenderTest < Test::Unit::TestCase assert_equal "Hello: davidHello: mary", process_request.body end - def test_module_rendering - @request.action = "modgreet" - @request.parameters["module"] = "scope" - assert_equal "<p>Beautiful modules!</p>", process_request.body + def test_nested_rendering + @request.action = "hello_world" + assert_equal "Living in a nested world", Fun::GamesController.process(@request, @response).body end private diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index ebab660eab..c31cdd460b 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -31,6 +31,28 @@ class RequestTest < Test::Unit::TestCase @request.port = 8080 assert_equal ":8080", @request.port_string end + + def test_request_uri + @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1" + assert_equal "/path/of/some/uri?mapped=1", @request.request_uri + assert_equal "/path/of/some/uri", @request.path + + @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri" + assert_equal "/path/of/some/uri", @request.request_uri + assert_equal "/path/of/some/uri", @request.path + + @request.set_REQUEST_URI "/path/of/some/uri" + assert_equal "/path/of/some/uri", @request.request_uri + assert_equal "/path/of/some/uri", @request.path + + @request.set_REQUEST_URI "/" + assert_equal "/", @request.request_uri + assert_equal "/", @request.path + + @request.set_REQUEST_URI "/?m=b" + assert_equal "/?m=b", @request.request_uri + assert_equal "/", @request.path + end def test_host_with_port @request.env['HTTP_HOST'] = "rubyonrails.org:8080" diff --git a/actionpack/test/controller/routing_tests.rb b/actionpack/test/controller/routing_tests.rb new file mode 100644 index 0000000000..307881554f --- /dev/null +++ b/actionpack/test/controller/routing_tests.rb @@ -0,0 +1,401 @@ +# Code Generated by ZenTest v. 2.3.0 +# Couldn't find class for name Routing +# classname: asrt / meth = ratio% +# ActionController::Routing::RouteSet: 0 / 16 = 0.00% +# ActionController::Routing::RailsRoute: 0 / 4 = 0.00% +# ActionController::Routing::Route: 0 / 8 = 0.00% + +RAILS_ROOT = "" +require File.dirname(__FILE__) + '/../abstract_unit' +require 'test/unit' +require 'cgi' + +class FakeController + attr_reader :controller_path + attr_reader :name + def initialize(name, controller_path) + @name = name + @controller_path = controller_path + end + def kind_of?(x) + x === Class || x == FakeController + end +end + +module Controllers + module Admin + UserController = FakeController.new 'Admin::UserController', 'admin/user' + AccessController = FakeController.new 'Admin::AccessController', 'admin/access' + end + module Editing + PageController = FakeController.new 'Editing::PageController', 'editing/page' + ImageController = FakeController.new 'Editing::ImageController', 'editing/image' + end + module User + NewsController = FakeController.new 'User::NewsController', 'user/news' + PaymentController = FakeController.new 'User::PaymentController', 'user/payment' + end + ContentController = FakeController.new 'ContentController', 'content' + ResourceController = FakeController.new 'ResourceController', 'resource' +end + +# Extend the modules with the required methods... +[Controllers, Controllers::Admin, Controllers::Editing, Controllers::User].each do |mod| + mod.instance_eval('alias :const_available? :const_defined?') + mod.constants.each {|k| Object.const_set(k, mod.const_get(k))} # export the modules & controller classes. +end + + +class RouteTests < Test::Unit::TestCase + def route(*args) + return @route if @route && (args.empty? || @args == args) + @args = args + @route = ActionController::Routing::Route.new(*args) + return @route + end + + def setup + self.route '/:controller/:action/:id' + @defaults = {:controller => 'content', :action => 'show', :id => '314'} + end + + # Don't put a leading / on the url. + # Make sure the controller is one from the above fake Controllers module. + def verify_recognize(url, expected_options, reason='') + url = url.split('/') if url.kind_of? String + reason = ": #{reason}" unless reason.empty? + controller_class, options = @route.recognize(url) + assert_not_equal nil, controller_class, "#{@route.inspect} didn't recognize #{url}#{reason}\n #{options}" + assert_equal expected_options, options, "#{@route.inspect} produced wrong options for #{url}#{reason}" + end + + # The expected url should not have a leading / + # You can use @defaults if you want a set of plausible defaults + def verify_generate(expected_url, expected_extras, options, defaults, reason='') + reason = "#{reason}: " unless reason.empty? + components, extras = @route.generate(options, defaults) + assert_not_equal nil, components, "#{reason}#{@route.inspect} didn't generate for \n options = #{options.inspect}\n defaults = #{defaults.inspect}\n #{extras}" + assert_equal expected_extras, extras, "#{reason} #{@route.inspect}.generate: incorrect extra's" + assert_equal expected_url, components.join('/'), "#{reason} #{@route.inspect}.generate: incorrect url" + end + + def test_recognize_default_unnested_with_action_and_id + verify_recognize('content/action/id', {:controller => 'content', :action => 'action', :id => 'id'}) + verify_recognize('content/show/10', {:controller => 'content', :action => 'show', :id => '10'}) + end + def test_generate_default_unnested_with_action_and_id_no_extras + verify_generate('content/action/id', {}, {:controller => 'content', :action => 'action', :id => 'id'}, @defaults) + verify_generate('content/show/10', {}, {:controller => 'content', :action => 'show', :id => '10'}, @defaults) + end + def test_generate_default_unnested_with_action_and_id + verify_generate('content/action/id', {:a => 'a'}, {:controller => 'content', :action => 'action', :id => 'id', :a => 'a'}, @defaults) + verify_generate('content/show/10', {:a => 'a'}, {:controller => 'content', :action => 'show', :id => '10', :a => 'a'}, @defaults) + end + + # Note that we can't put tests here for proper relative controller handline + # because that is handled by RouteSet. + def test_recognize_default_nested_with_action_and_id + verify_recognize('admin/user/action/id', {:controller => 'admin/user', :action => 'action', :id => 'id'}) + verify_recognize('admin/user/show/10', {:controller => 'admin/user', :action => 'show', :id => '10'}) + end + def test_generate_default_nested_with_action_and_id_no_extras + verify_generate('admin/user/action/id', {}, {:controller => 'admin/user', :action => 'action', :id => 'id'}, @defaults) + verify_generate('admin/user/show/10', {}, {:controller => 'admin/user', :action => 'show', :id => '10'}, @defaults) + end + def test_generate_default_nested_with_action_and_id_relative_to_root + verify_generate('admin/user/action/id', {:a => 'a'}, {:controller => 'admin/user', :action => 'action', :id => 'id', :a => 'a'}, @defaults) + verify_generate('admin/user/show/10', {:a => 'a'}, {:controller => 'admin/user', :action => 'show', :id => '10', :a => 'a'}, @defaults) + end + + def test_recognize_default_nested_with_action + verify_recognize('admin/user/action', {:controller => 'admin/user', :action => 'action'}) + verify_recognize('admin/user/show', {:controller => 'admin/user', :action => 'show'}) + end + def test_generate_default_nested_with_action_no_extras + verify_generate('admin/user/action', {}, {:controller => 'admin/user', :action => 'action'}, @defaults) + verify_generate('admin/user/show', {}, {:controller => 'admin/user', :action => 'show'}, @defaults) + end + def test_generate_default_nested_with_action + verify_generate('admin/user/action', {:a => 'a'}, {:controller => 'admin/user', :action => 'action', :a => 'a'}, @defaults) + verify_generate('admin/user/show', {:a => 'a'}, {:controller => 'admin/user', :action => 'show', :a => 'a'}, @defaults) + end + + def test_recognize_default_nested_with_id_and_index + verify_recognize('admin/user/index/hello', {:controller => 'admin/user', :id => 'hello', :action => 'index'}) + verify_recognize('admin/user/index/10', {:controller => 'admin/user', :id => "10", :action => 'index'}) + end + def test_generate_default_nested_with_id_no_extras + verify_generate('admin/user/index/hello', {}, {:controller => 'admin/user', :id => 'hello'}, @defaults) + verify_generate('admin/user/index/10', {}, {:controller => 'admin/user', :id => 10}, @defaults) + end + def test_generate_default_nested_with_id + verify_generate('admin/user/index/hello', {:a => 'a'}, {:controller => 'admin/user', :id => 'hello', :a => 'a'}, @defaults) + verify_generate('admin/user/index/10', {:a => 'a'}, {:controller => 'admin/user', :id => 10, :a => 'a'}, @defaults) + end + + def test_recognize_default_nested + verify_recognize('admin/user', {:controller => 'admin/user', :action => 'index'}) + verify_recognize('admin/user', {:controller => 'admin/user', :action => 'index'}) + end + def test_generate_default_nested_no_extras + verify_generate('admin/user/', {}, {:controller => 'admin/user'}, @defaults) + verify_generate('admin/user/', {}, {:controller => 'admin/user'}, @defaults) + end + def test_generate_default_nested + verify_generate('admin/user/', {:a => 'a'}, {:controller => 'admin/user', :a => 'a'}, @defaults) + verify_generate('admin/user/', {:a => 'a'}, {:controller => 'admin/user', :a => 'a'}, @defaults) + end + + # Test generate with a default controller set. + def test_generate_default_controller + route '/:controller/:action/:id', :action => 'index', :id => nil, :controller => 'content' + @defaults[:controller] = 'resource' + + verify_generate('', {}, {:controller => 'content'}, @defaults) + verify_generate('', {}, {:controller => 'content', :action => 'index'}, @defaults) + verify_generate('content/not-index', {}, {:controller => 'content', :action => 'not-index'}, @defaults) + verify_generate('content/index/10', {}, {:controller => 'content', :id => 10}, @defaults) + verify_generate('content/index/hi', {}, {:controller => 'content', :action => 'index', :id => 'hi'}, @defaults) + verify_generate('', {:a => 'a'}, {:controller => 'content', :a => 'a'}, @defaults) + verify_generate('', {:a => 'a'}, {:controller => 'content', :a => 'a'}, @defaults) + + # Call some other generator tests + test_generate_default_unnested_with_action_and_id + test_generate_default_nested_with_action_and_id_no_extras + test_generate_default_nested_with_id + test_generate_default_nested_with_id_no_extras + end + + # Test generate with a default controller set. + def test_generate_default_controller + route '/:controller/:action/:id', :action => 'index', :id => nil, :controller => 'content' + @defaults[:controller] = 'resource' + verify_recognize('', {:controller => 'content', :action => 'index'}) + verify_recognize('content', {:controller => 'content', :action => 'index'}) + verify_recognize('content/index', {:controller => 'content', :action => 'index'}) + verify_recognize('content/index/10', {:controller => 'content', :action => 'index', :id => '10'}) + end + # Make sure generation & recognition don't happen in some cases: + def test_no_generate_on_no_options + assert_equal nil, @route.generate({}, {})[0] + end + def test_requirements + route 'some_static/route', :controller => 'content' + assert_equal nil, @route.generate({}, {})[0] + assert_equal nil, @route.generate({:controller => "dog"}, {})[0] + assert_equal nil, @route.recognize([])[0] + assert_equal nil, @route.recognize(%w{some_static route with more than expected})[0] + end + + def test_basecamp + route 'clients/', :controller => 'content' + verify_generate('clients', {}, {:controller => 'content'}, {}) # Would like to have clients/ + verify_generate('clients', {}, {:controller => 'content'}, @defaults) + end + + def test_basecamp2 + route 'clients/:client_name/:project_name/', :controller => 'content', :action => 'start_page_redirect' + verify_recognize('clients/projects/2', {:controller => 'content', :client_name => 'projects', :project_name => '2', :action => 'start_page_redirect'}) + end + + def test_xal_style_dates + route 'articles/:category/:year/:month/:day', :controller => 'content', :action => 'list_articles', :category => 'all', :year => nil, :month => nil, :day =>nil + verify_recognize('articles', {:controller => 'content', :action => 'list_articles', :category => 'all'}) + verify_recognize('articles/porn', {:controller => 'content', :action => 'list_articles', :category => 'porn'}) + verify_recognize('articles/news/2005/08', {:controller => 'content', :action => 'list_articles', :category => 'news', :year => '2005', :month => '08'}) + verify_recognize('articles/news/2005/08/04', {:controller => 'content', :action => 'list_articles', :category => 'news', :year => '2005', :month => '08', :day => '04'}) + assert_equal nil, @route.recognize(%w{articles too many components are here})[0] + assert_equal nil, @route.recognize('')[0] + + verify_generate('articles', {}, {:controller => 'content', :action => 'list_articles'}, @defaults) + verify_generate('articles', {}, {:controller => 'content', :action => 'list_articles', :category => 'all'}, @defaults) + verify_generate('articles/news', {}, {:controller => 'content', :action => 'list_articles', :category => 'news'}, @defaults) + verify_generate('articles/news/2005', {}, {:controller => 'content', :action => 'list_articles', :category => 'news', :year => '2005'}, @defaults) + verify_generate('articles/news/2005/05', {}, {:controller => 'content', :action => 'list_articles', :category => 'news', :year => '2005', :month => '05'}, @defaults) + verify_generate('articles/news/2005/05/16', {}, {:controller => 'content', :action => 'list_articles', :category => 'news', :year => '2005', :month => '05', :day => '16'}, @defaults) + + assert_equal nil, @route.generate({:controller => 'content', :action => 'list_articles', :day => '2'}, @defaults)[0] + # The above case should fail because a nil value cannot be present in a path. + # In other words, since :day is given, :month and :year must be given too. + end + + + def test_no_controller + route 'some/:special/:route', :controller => 'a/missing/controller', :action => 'anything' + assert_raises(ActionController::RoutingError, "Should raise due to nonexistant controller") {@route.recognize(%w{some matching path})} + end + def test_bad_controller_path + assert_equal nil, @route.recognize(%w{no such controller fake_action id})[0] + end + def test_too_short_path + assert_equal nil, @route.recognize([])[0] + route 'some/static/route', :controller => 'content', :action => 'show' + assert_equal nil, route.recognize([])[0] + end + def test_too_long_path + assert_equal nil, @route.recognize(%w{content action id some extra components})[0] + end + def test_incorrect_static_component + route 'some/static/route', :controller => 'content', :action => 'show' + assert_equal nil, route.recognize(%w{an non_matching path})[0] + end + def test_no_controller_defined + route 'some/:path/:without/a/controller' + assert_equal nil, route.recognize(%w{some matching path a controller})[0] + end + + def test_mismatching_requirements + route 'some/path', :controller => 'content', :action => 'fish' + assert_equal nil, route.generate({:controller => 'admin/user', :action => 'list'})[0] + assert_equal nil, route.generate({:controller => 'content', :action => 'list'})[0] + assert_equal nil, route.generate({:controller => 'admin/user', :action => 'fish'})[0] + end + + def test_missing_value_for_generate + assert_equal nil, route.generate({})[0] # :controller is missing + end + def test_nils_inside_generated_path + route 'show/:year/:month/:day', :month => nil, :day => nil, :controller => 'content', :action => 'by_date' + assert_equal nil, route.generate({:year => 2005, :day => 10})[0] + end + + def test_expand_controller_path_non_nested_no_leftover + controller, leftovers = @route.send :eat_path_to_controller, %w{content} + assert_equal Controllers::ContentController, controller + assert_equal [], leftovers + end + def test_expand_controller_path_non_nested_with_leftover + controller, leftovers = @route.send :eat_path_to_controller, %w{content action id} + assert_equal Controllers::ContentController, controller + assert_equal %w{action id}, leftovers + end + def test_expand_controller_path_nested_no_leftover + controller, leftovers = @route.send :eat_path_to_controller, %w{admin user} + assert_equal Controllers::Admin::UserController, controller + assert_equal [], leftovers + end + def test_expand_controller_path_nested_no_leftover + controller, leftovers = @route.send :eat_path_to_controller, %w{admin user action id} + assert_equal Controllers::Admin::UserController, controller + assert_equal %w{action id}, leftovers + end +end + +class RouteSetTests < Test::Unit::TestCase + def setup + @set = ActionController::Routing::RouteSet.new + @rails_route = ActionController::Routing::Route.new '/:controller/:action/:id', :action => 'index', :id => nil + @request = ActionController::TestRequest.new({}, {}, nil) + end + def test_emptyness + assert_equal true, @set.empty?, "New RouteSets should respond to empty? with true." + @set.each { flunk "New RouteSets should be empty." } + end + def test_add_illegal_route + assert_raises(TypeError) {@set.add_route "I'm not actually a route."} + end + def test_add_normal_route + @set.add_route @rails_route + seen = false + @set.each do |route| + assert_equal @rails_route, route + flunk("Each should have yielded only a single route!") if seen + seen = true + end + end + + def test_expand_controller_path_non_relative + defaults = {:controller => 'admin/user', :action => 'list'} + options = {:controller => '/content'} + @set.expand_controller_path!(options, defaults) + assert_equal({:controller => 'content'}, options) + end + def test_expand_controller_path_relative_to_nested + defaults = {:controller => 'admin/user', :action => 'list'} + options = {:controller => 'access'} + @set.expand_controller_path!(options, defaults) + assert_equal({:controller => 'admin/access'}, options) + end + def test_expand_controller_path_relative_to_root + defaults = {:controller => 'content', :action => 'list'} + options = {:controller => 'resource'} + @set.expand_controller_path!(options, defaults) + assert_equal({:controller => 'resource'}, options) + end + def test_expand_controller_path_into_module + defaults = {:controller => 'content', :action => 'list'} + options = {:controller => 'admin/user'} + @set.expand_controller_path!(options, defaults) + assert_equal({:controller => 'admin/user'}, options) + end + def test_expand_controller_path_switch_module_with_absolute + defaults = {:controller => 'user/news', :action => 'list'} + options = {:controller => '/admin/user'} + @set.expand_controller_path!(options, defaults) + assert_equal({:controller => 'admin/user'}, options) + end + def test_expand_controller_no_default + options = {:controller => 'content'} + @set.expand_controller_path!(options, {}) + assert_equal({:controller => 'content'}, options) + end + + # Don't put a leading / on the url. + # Make sure the controller is one from the above fake Controllers module. + def verify_recognize(expected_controller, expected_path_parameters=nil, path=nil) + @set.add_route(@rails_route) if @set.empty? + @request.path = path if path + controller = @set.recognize!(@request) + assert_equal expected_controller, controller + assert_equal expected_path_parameters, @request.path_parameters if expected_path_parameters + end + + # The expected url should not have a leading / + # You can use @defaults if you want a set of plausible defaults + def verify_generate(expected_url, options, expected_extras={}) + @set.add_route(@rails_route) if @set.empty? + components, extras = @set.generate(options, @request) + assert_equal expected_extras, extras, "#incorrect extra's" + assert_equal expected_url, components.join('/'), "incorrect url" + end + def typical_request + @request.path_parameters = {:controller => 'content', :action => 'show', :id => '10'} + end + def typical_nested_request + @request.path_parameters = {:controller => 'admin/user', :action => 'grant', :id => '02seckar'} + end + + def test_generate_typical_controller_action_path + typical_request + verify_generate('content/list', {:controller => 'content', :action => 'list'}) + end + def test_generate_typical_controller_index_path_explicit_index + typical_request + verify_generate('content/', {:controller => 'content', :action => 'index'}) + end + def test_generate_typical_controller_index_path_explicit_index + typical_request + verify_generate('content/', {:controller => 'content', :action => 'index'}) + end + def test_generate_typical_controller_index_path_implicit_index + typical_request + @request.path_parameters[:controller] = 'resource' + verify_generate('content/', {:controller => 'content'}) + end + + def test_generate_no_perfect_route + typical_request + verify_generate('admin/user/show/43seckar', {:controller => 'admin/user', :action => 'show', :id => '43seckar', :likes_fishing => 'fuzzy(0.3)'}, {:likes_fishing => 'fuzzy(0.3)'}) + end + + def test_generate_no_match + @set.add_route(@rails_route) + @request.path_parameters = {} + assert_raises(ActionController::RoutingError) {@set.generate({}, @request)} + end + + + def test_encoded_strings + verify_recognize(Controllers::Admin::UserController, {:controller => 'admin/user', :action => 'info', :id => "Nicholas Seckar"}, path='/admin/user/info/Nicholas%20Seckar') + end +end diff --git a/actionpack/test/controller/url_test.rb b/actionpack/test/controller/url_obsolete.rb index f7f08a2fe5..4b6544dbf7 100644 --- a/actionpack/test/controller/url_test.rb +++ b/actionpack/test/controller/url_obsolete.rb @@ -1,7 +1,7 @@ require File.dirname(__FILE__) + '/../abstract_unit' require 'action_controller/url_rewriter' -MockRequest = Struct.new("MockRequest", :protocol, :host, :port, :path, :parameters) +MockRequest = Struct.new("MockRequest", :protocol, :host, :port, :path, :parameters, :path_parameters) class MockRequest def host_with_port if (protocol == "http://" && port == 80) || (protocol == "https://" && port == 443) @@ -16,14 +16,24 @@ class UrlMockFactory def self.create(path, parameters) ActionController::UrlRewriter.new( MockRequest.new("http://", "example.com", 80, path, parameters), - parameters["controller"], parameters["action"] + parameters ) end end +# old-style support for .new +module ActionController + class UrlRewriter + def self.old_new(request, controller, action) + request.parameters[:controller] = controller + request.parameters[:action] = action + return new(request, request.parameters) + end + end +end class UrlTest < Test::Unit::TestCase def setup - @library_url = ActionController::UrlRewriter.new(MockRequest.new( + @library_url = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, @@ -31,7 +41,7 @@ class UrlTest < Test::Unit::TestCase { "type" => "ISBN", "code" => "0743536703" } ), "books", "show") - @library_url_using_module = ActionController::UrlRewriter.new(MockRequest.new( + @library_url_using_module = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, @@ -39,7 +49,7 @@ class UrlTest < Test::Unit::TestCase { "type" => "ISBN", "code" => "0743536703", "module" => "library" } ), "books", "show") - @library_url_on_index = ActionController::UrlRewriter.new(MockRequest.new( + @library_url_on_index = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, @@ -48,27 +58,27 @@ class UrlTest < Test::Unit::TestCase ), "books", "index") @clean_urls = [ - ActionController::UrlRewriter.new(MockRequest.new( + ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, "/identity/", {} ), "identity", "index"), - ActionController::UrlRewriter.new(MockRequest.new( + ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, "/identity", {} ), "identity", "index") ] - @clean_url_with_id = ActionController::UrlRewriter.new(MockRequest.new( + @clean_url_with_id = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, "/identity/show/5", { "id" => "5" } ), "identity", "show") - @clean_url_with_same_action_and_controller_name = ActionController::UrlRewriter.new(MockRequest.new( + @clean_url_with_same_action_and_controller_name = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, "/login/login", { } ), "login", "login") - @clean_url_with_same_action_and_controller_and_module_name = ActionController::UrlRewriter.new(MockRequest.new( + @clean_url_with_same_action_and_controller_and_module_name = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, "/login/login/login", { "module" => "login" } ), "login", "login") - @clean_url_with_id_as_char = ActionController::UrlRewriter.new(MockRequest.new( + @clean_url_with_id_as_char = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 80, "/teachers/show/t", { "id" => "t" } ), "teachers", "show") end @@ -386,7 +396,7 @@ class UrlTest < Test::Unit::TestCase end def test_from_another_port - @library_url = ActionController::UrlRewriter.new(MockRequest.new( + @library_url = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "www.singlefile.com", 8080, @@ -403,7 +413,7 @@ class UrlTest < Test::Unit::TestCase end def test_basecamp - basecamp_url = ActionController::UrlRewriter.new(MockRequest.new( + basecamp_url = ActionController::UrlRewriter.old_new(MockRequest.new( "http://", "projects.basecamp", 80, @@ -418,7 +428,7 @@ class UrlTest < Test::Unit::TestCase end def test_on_explicit_index_page # My index page is very modest, thank you... - url = ActionController::UrlRewriter.new( + url = ActionController::UrlRewriter.old_new( MockRequest.new( "http://", "example.com", 80, "/controller/index", {"controller"=>"controller", "action"=>"index"} diff --git a/actionpack/test/fixtures/fun/games/hello_world.rhtml b/actionpack/test/fixtures/fun/games/hello_world.rhtml new file mode 100644 index 0000000000..1ebfbe2539 --- /dev/null +++ b/actionpack/test/fixtures/fun/games/hello_world.rhtml @@ -0,0 +1 @@ +Living in a nested world
\ No newline at end of file diff --git a/actionpack/test/fixtures/helpers/fun/games_helper.rb b/actionpack/test/fixtures/helpers/fun/games_helper.rb new file mode 100644 index 0000000000..bf60d9db0c --- /dev/null +++ b/actionpack/test/fixtures/helpers/fun/games_helper.rb @@ -0,0 +1,3 @@ +module Fun::GamesHelper + def stratego() "Iz guuut!" end +end
\ No newline at end of file |