From a2637e9f1fba92bc0b8dbf461ce9f4f8ffb4cfaa Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 12 Mar 2009 13:19:13 -0600 Subject: Try to build a new AC::Base on top of AbstractController --- actionpack/lib/action_controller/new_base.rb | 5 + actionpack/lib/action_controller/new_base/base.rb | 26 ++ .../lib/action_controller/new_base/hide_actions.rb | 28 ++ .../lib/action_controller/new_base/url_for.rb | 40 +++ actionpack/test/new_base/base_test.rb | 317 +++++++++++++++++++++ 5 files changed, 416 insertions(+) create mode 100644 actionpack/lib/action_controller/new_base.rb create mode 100644 actionpack/lib/action_controller/new_base/base.rb create mode 100644 actionpack/lib/action_controller/new_base/hide_actions.rb create mode 100644 actionpack/lib/action_controller/new_base/url_for.rb create mode 100644 actionpack/test/new_base/base_test.rb diff --git a/actionpack/lib/action_controller/new_base.rb b/actionpack/lib/action_controller/new_base.rb new file mode 100644 index 0000000000..2cef221de3 --- /dev/null +++ b/actionpack/lib/action_controller/new_base.rb @@ -0,0 +1,5 @@ +module ActionController + autoload :AbstractBase, "action_controller/new_base/base" + autoload :HideActions, "action_controller/new_base/hide_actions" + autoload :UrlFor, "action_controller/new_base/url_for" +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/new_base/base.rb b/actionpack/lib/action_controller/new_base/base.rb new file mode 100644 index 0000000000..ebe7c8dda6 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/base.rb @@ -0,0 +1,26 @@ +module ActionController + class AbstractBase < AbstractController::Base + attr_internal :request, :response, :params + + def self.controller_name + @controller_name ||= controller_path.split("/").last + end + + def controller_name() self.class.controller_name end + + def self.controller_path + @controller_path ||= self.name.sub(/Controller$/, '').underscore + end + + def controller_path() self.class.controller_path end + + def self.action_methods + @action_names ||= Set.new(self.public_instance_methods - self::CORE_METHODS) + end + + def self.action_names() action_methods end + + def action_methods() self.class.action_names end + def action_names() action_methods end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/new_base/hide_actions.rb b/actionpack/lib/action_controller/new_base/hide_actions.rb new file mode 100644 index 0000000000..9847d7b086 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/hide_actions.rb @@ -0,0 +1,28 @@ +module ActionController + module HideActions + def self.included(klass) + klass.class_eval do + extend ClassMethods + extlib_inheritable_accessor :hidden_actions + self.hidden_actions ||= Set.new + end + end + + def action_methods() self.class.action_names end + def action_names() action_methods end + + module ClassMethods + def hide_action(*args) + args.each do |arg| + self.hidden_actions << arg.to_s + end + end + + def action_methods + @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)}) + end + + def self.action_names() action_methods end + end + end +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/new_base/url_for.rb b/actionpack/lib/action_controller/new_base/url_for.rb new file mode 100644 index 0000000000..af5b21012b --- /dev/null +++ b/actionpack/lib/action_controller/new_base/url_for.rb @@ -0,0 +1,40 @@ +module ActionController + module UrlFor + def initialize_current_url + @url = UrlRewriter.new(request, params.clone) + end + + # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in + # the form of a hash, just like the one you would use for url_for directly. Example: + # + # def default_url_options(options) + # { :project => @project.active? ? @project.url_name : "unknown" } + # end + # + # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the + # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set + # by this method. + def default_url_options(options = nil) + end + + def rewrite_options(options) #:nodoc: + if defaults = default_url_options(options) + defaults.merge(options) + else + options + end + end + + def url_for(options = {}) + options ||= {} + case options + when String + options + when Hash + @url.rewrite(rewrite_options(options)) + else + polymorphic_url(options) + end + end + end +end \ No newline at end of file diff --git a/actionpack/test/new_base/base_test.rb b/actionpack/test/new_base/base_test.rb new file mode 100644 index 0000000000..7ac5eac3c5 --- /dev/null +++ b/actionpack/test/new_base/base_test.rb @@ -0,0 +1,317 @@ +$:.unshift(File.dirname(__FILE__) + '/../../lib') +$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib') + +require 'test/unit' +require 'active_support' +require 'active_support/test_case' +require 'action_controller' +require 'action_view/base' + +begin + require 'ruby-debug' + Debugger.settings[:autoeval] = true + Debugger.start +rescue LoadError + # Debugging disabled. `gem install ruby-debug` to enable. +end + +require 'action_controller/abstract' +require 'action_controller/new_base' +require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late + +require 'rubygems' +require 'rack/test' + +module ActionController + module TestProcess + def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') + # Sanity check for required instance variables so we can give an + # understandable error message. + %w(@controller @request @response).each do |iv_name| + if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? + raise "#{iv_name} is nil: make sure you set it in your test's setup method." + end + end + + @request.recycle! + @response.recycle! + + @html_document = nil + @request.env['REQUEST_METHOD'] = http_method + + @request.action = action.to_s + + parameters ||= {} + @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters) + + @request.session = ActionController::TestSession.new(session) unless session.nil? + @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash + build_request_uri(action, parameters) + + # Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest + @controller.request = @request + @controller.response = @response + @controller.process(action) + end + end + + class Base2 < AbstractBase + include AbstractController::Callbacks + include AbstractController::Renderer + include AbstractController::Helpers + include AbstractController::Layouts + include AbstractController::Logger + + include ActionController::HideActions + include ActionController::UrlFor + + CORE_METHODS = self.public_instance_methods + end +end + +# Provide some controller to run the tests on. +module Submodule + class ContainedEmptyController < ActionController::Base2 + end + class ContainedNonEmptyController < ActionController::Base2 + def public_action + render :nothing => true + end + + hide_action :hidden_action + def hidden_action + raise "Noooo!" + end + + def another_hidden_action + end + hide_action :another_hidden_action + end + class SubclassedController < ContainedNonEmptyController + hide_action :public_action # Hiding it here should not affect the superclass. + end +end +class EmptyController < ActionController::Base2 +end +class NonEmptyController < ActionController::Base2 + def public_action + end + + hide_action :hidden_action + def hidden_action + end +end + +class MethodMissingController < ActionController::Base + + hide_action :shouldnt_be_called + def shouldnt_be_called + raise "NO WAY!" + end + +protected + + def method_missing(selector) + render :text => selector.to_s + end + +end + +class DefaultUrlOptionsController < ActionController::Base2 + def default_url_options_action + end + + def default_url_options(options = nil) + { :host => 'www.override.com', :action => 'new', :bacon => 'chunky' } + end +end + +class ControllerClassTests < Test::Unit::TestCase + def test_controller_path + assert_equal 'empty', EmptyController.controller_path + assert_equal EmptyController.controller_path, EmptyController.new.controller_path + assert_equal 'submodule/contained_empty', Submodule::ContainedEmptyController.controller_path + assert_equal Submodule::ContainedEmptyController.controller_path, Submodule::ContainedEmptyController.new.controller_path + end + def test_controller_name + assert_equal 'empty', EmptyController.controller_name + assert_equal 'contained_empty', Submodule::ContainedEmptyController.controller_name + end +end + +class ControllerInstanceTests < Test::Unit::TestCase + def setup + @empty = EmptyController.new + @contained = Submodule::ContainedEmptyController.new + @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new] + + @non_empty_controllers = [NonEmptyController.new, + Submodule::ContainedNonEmptyController.new] + end + + def test_action_methods + @empty_controllers.each do |c| + hide_mocha_methods_from_controller(c) + assert_equal Set.new, c.__send__(:action_methods), "#{c.controller_path} should be empty!" + end + @non_empty_controllers.each do |c| + hide_mocha_methods_from_controller(c) + assert_equal Set.new(%w(public_action)), c.__send__(:action_methods), "#{c.controller_path} should not be empty!" + end + end + + protected + # Mocha adds some public instance methods to Object that would be + # considered actions, so explicitly hide_action them. + def hide_mocha_methods_from_controller(controller) + mocha_methods = [ + :expects, :mocha, :mocha_inspect, :reset_mocha, :stubba_object, + :stubba_method, :stubs, :verify, :__metaclass__, :__is_a__, :to_matcher, + ] + controller.class.__send__(:hide_action, *mocha_methods) + end +end + + +class PerformActionTest < ActiveSupport::TestCase + class MockLogger + attr_reader :logged + + def initialize + @logged = [] + end + + def method_missing(method, *args) + @logged << args.first + end + end + + def use_controller(controller_class) + @controller = controller_class.new + + # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get + # a more accurate simulation of what happens in "real life". + @controller.logger = Logger.new(nil) + + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @request.host = "www.nxtangle.com" + + rescue_action_in_public! + end + + attr_accessor :app + include Rack::Test::Methods + + def with_routing + real_routes = ActionController::Routing::Routes + ActionController::Routing.module_eval { remove_const :Routes } + + temporary_routes = ActionController::Routing::RouteSet.new + ActionController::Routing.module_eval { const_set :Routes, temporary_routes } + + yield temporary_routes + ensure + if ActionController::Routing.const_defined? :Routes + ActionController::Routing.module_eval { remove_const :Routes } + end + ActionController::Routing.const_set(:Routes, real_routes) if real_routes + end + + def test_get_on_priv_should_show_selector + ActionController::Base.session_options[:key] = "abc" + ActionController::Base.session_options[:secret] = ("*" * 30) + + with_routing do |set| + set.draw do |map| + map.connect ':controller/:action' + end + + @app = ActionController::Dispatcher.new + + resp = get "/method_missing/shouldnt_be_called" + assert_equal 'shouldnt_be_called', resp.body + end + + # use_controller MethodMissingController + # get :shouldnt_be_called + # assert_response :success + # assert_equal 'shouldnt_be_called', @response.body + end + + def test_method_missing_is_not_an_action_name + use_controller MethodMissingController + assert ! @controller.__send__(:action_methods).include?('method_missing') + + get :method_missing + assert_response :success + assert_equal 'method_missing', @response.body + end + + def test_get_on_hidden_should_fail + use_controller NonEmptyController + get :hidden_action + assert_response 404 + + get :another_hidden_action + assert_response 404 + end + + def test_namespaced_action_should_log_module_name + use_controller Submodule::ContainedNonEmptyController + @controller.logger = MockLogger.new + get :public_action + assert_match /Processing\sSubmodule::ContainedNonEmptyController#public_action/, @controller.logger.logged[1] + end +end + +class DefaultUrlOptionsTest < ActionController::TestCase + tests DefaultUrlOptionsController + + def setup + @request.host = 'www.example.com' + rescue_action_in_public! + end + + def test_default_url_options_are_used_if_set + ActionController::Routing::Routes.draw do |map| + map.default_url_options 'default_url_options', :controller => 'default_url_options' + map.connect ':controller/:action/:id' + end + + get :default_url_options_action # Make a dummy request so that the controller is initialized properly. + + assert_equal 'http://www.override.com/default_url_options/new?bacon=chunky', @controller.url_for(:controller => 'default_url_options') + assert_equal 'http://www.override.com/default_url_options?bacon=chunky', @controller.send(:default_url_options_url) + ensure + ActionController::Routing::Routes.load! + end +end + +class EmptyUrlOptionsTest < ActionController::TestCase + tests NonEmptyController + + def setup + @request.host = 'www.example.com' + rescue_action_in_public! + end + + def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set + get :public_action + assert_equal "http://www.example.com/non_empty/public_action", @controller.url_for + end +end + +class EnsureNamedRoutesWorksTicket22BugTest < Test::Unit::TestCase + def test_named_routes_still_work + ActionController::Routing::Routes.draw do |map| + map.resources :things + end + EmptyController.send :include, ActionController::UrlWriter + + assert_equal '/things', EmptyController.new.send(:things_path) + ensure + ActionController::Routing::Routes.load! + end +end -- cgit v1.2.3