mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Working toward getting a basic AbstractController framework
This commit is contained in:
parent
a2637e9f1f
commit
f55514125c
9 changed files with 269 additions and 272 deletions
|
@ -1,8 +1,10 @@
|
|||
module AbstractController
|
||||
autoload :Base, "action_controller/abstract/base"
|
||||
autoload :Callbacks, "action_controller/abstract/callbacks"
|
||||
autoload :Helpers, "action_controller/abstract/helpers"
|
||||
autoload :Layouts, "action_controller/abstract/layouts"
|
||||
autoload :Logger, "action_controller/abstract/logger"
|
||||
autoload :Renderer, "action_controller/abstract/renderer"
|
||||
autoload :Base, "action_controller/abstract/base"
|
||||
autoload :Callbacks, "action_controller/abstract/callbacks"
|
||||
autoload :Helpers, "action_controller/abstract/helpers"
|
||||
autoload :Layouts, "action_controller/abstract/layouts"
|
||||
autoload :Logger, "action_controller/abstract/logger"
|
||||
autoload :Renderer, "action_controller/abstract/renderer"
|
||||
# === Exceptions
|
||||
autoload :ActionNotFound, "action_controller/abstract/exceptions"
|
||||
end
|
|
@ -17,14 +17,24 @@ module AbstractController
|
|||
end
|
||||
|
||||
def process(action_name)
|
||||
unless respond_to_action?(action_name)
|
||||
raise ActionNotFound, "The action '#{action_name}' could not be found"
|
||||
end
|
||||
|
||||
@_action_name = action_name
|
||||
process_action
|
||||
self.response_obj[:body] = self.response_body
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_action
|
||||
send(action_name)
|
||||
respond_to?(action_name) ? send(action_name) : send(:action_missing, action_name)
|
||||
end
|
||||
|
||||
def respond_to_action?(action_name)
|
||||
respond_to?(action_name) || respond_to?(:action_missing, true)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
3
actionpack/lib/action_controller/abstract/exceptions.rb
Normal file
3
actionpack/lib/action_controller/abstract/exceptions.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
module AbstractController
|
||||
class ActionNotFound < StandardError ; end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
module ActionController
|
||||
autoload :AbstractBase, "action_controller/new_base/base"
|
||||
autoload :HideActions, "action_controller/new_base/hide_actions"
|
||||
autoload :Renderer, "action_controller/new_base/renderer"
|
||||
autoload :UrlFor, "action_controller/new_base/url_for"
|
||||
end
|
|
@ -1,26 +1,61 @@
|
|||
module ActionController
|
||||
class AbstractBase < AbstractController::Base
|
||||
|
||||
# :api: public
|
||||
attr_internal :request, :response, :params
|
||||
|
||||
# :api: public
|
||||
def self.controller_name
|
||||
@controller_name ||= controller_path.split("/").last
|
||||
end
|
||||
|
||||
# :api: public
|
||||
def controller_name() self.class.controller_name end
|
||||
|
||||
|
||||
# :api: public
|
||||
def self.controller_path
|
||||
@controller_path ||= self.name.sub(/Controller$/, '').underscore
|
||||
end
|
||||
|
||||
# :api: public
|
||||
def controller_path() self.class.controller_path end
|
||||
|
||||
# :api: private
|
||||
def self.action_methods
|
||||
@action_names ||= Set.new(self.public_instance_methods - self::CORE_METHODS)
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def self.action_names() action_methods end
|
||||
|
||||
# :api: private
|
||||
def action_methods() self.class.action_names end
|
||||
|
||||
# :api: private
|
||||
def action_names() action_methods end
|
||||
|
||||
# :api: plugin
|
||||
def self.call(env)
|
||||
controller = new
|
||||
controller.call(env).to_rack
|
||||
end
|
||||
|
||||
# :api: plugin
|
||||
def response_body=(body)
|
||||
@_response["Content-Length"] = body.length
|
||||
@_response.body = body
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def call(env)
|
||||
@_request = ActionDispatch::Request.new(env)
|
||||
@_response = ActionDispatch::Response.new
|
||||
process(@_request.parameters[:action])
|
||||
end
|
||||
|
||||
# :api: private
|
||||
def to_rack
|
||||
response.to_a
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,6 +10,12 @@ module ActionController
|
|||
|
||||
def action_methods() self.class.action_names end
|
||||
def action_names() action_methods end
|
||||
|
||||
private
|
||||
|
||||
def respond_to_action?(action_name)
|
||||
!hidden_actions.include?(action_name) && (super || respond_to?(:method_missing))
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def hide_action(*args)
|
||||
|
|
11
actionpack/lib/action_controller/new_base/renderer.rb
Normal file
11
actionpack/lib/action_controller/new_base/renderer.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module ActionController
|
||||
module Renderer
|
||||
|
||||
def render(options)
|
||||
if text = options[:text]
|
||||
self.response_body = text
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -3,6 +3,10 @@ require File.join(File.expand_path(File.dirname(__FILE__)), "test_helper")
|
|||
module AbstractController
|
||||
module Testing
|
||||
|
||||
# Test basic dispatching.
|
||||
# ====
|
||||
# * Call process
|
||||
# * Test that the response_body is set correctly
|
||||
class SimpleController < AbstractController::Base
|
||||
end
|
||||
|
||||
|
@ -20,6 +24,8 @@ module AbstractController
|
|||
end
|
||||
end
|
||||
|
||||
# Test Render mixin
|
||||
# ====
|
||||
class RenderingController < AbstractController::Base
|
||||
include Renderer
|
||||
|
||||
|
@ -58,6 +64,9 @@ module AbstractController
|
|||
end
|
||||
end
|
||||
|
||||
# Test rendering with prefixes
|
||||
# ====
|
||||
# * self._prefix is used when defined
|
||||
class PrefixedViews < RenderingController
|
||||
private
|
||||
def self.prefix
|
||||
|
@ -92,6 +101,9 @@ module AbstractController
|
|||
end
|
||||
end
|
||||
|
||||
# Test rendering with layouts
|
||||
# ====
|
||||
# self._layout is used when defined
|
||||
class WithLayouts < PrefixedViews
|
||||
include Layouts
|
||||
|
||||
|
@ -136,5 +148,63 @@ module AbstractController
|
|||
end
|
||||
end
|
||||
|
||||
# respond_to_action?(action_name)
|
||||
# ====
|
||||
# * A method can be used as an action only if this method
|
||||
# returns true when passed the method name as an argument
|
||||
# * Defaults to true in AbstractController
|
||||
class DefaultRespondToActionController < AbstractController::Base
|
||||
def index() self.response_body = "success" end
|
||||
end
|
||||
|
||||
class ActionMissingRespondToActionController < AbstractController::Base
|
||||
# No actions
|
||||
private
|
||||
def action_missing(action_name)
|
||||
self.response_body = "success"
|
||||
end
|
||||
end
|
||||
|
||||
class RespondToActionController < AbstractController::Base;
|
||||
def index() self.response_body = "success" end
|
||||
|
||||
def fail() self.response_body = "fail" end
|
||||
|
||||
private
|
||||
|
||||
def respond_to_action?(action_name)
|
||||
action_name != :fail
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class TestRespondToAction < ActiveSupport::TestCase
|
||||
|
||||
def assert_dispatch(klass, body = "success", action = :index)
|
||||
response = klass.process(action).response_obj[:body]
|
||||
assert_equal body, response
|
||||
end
|
||||
|
||||
test "an arbitrary method is available as an action by default" do
|
||||
assert_dispatch DefaultRespondToActionController, "success", :index
|
||||
end
|
||||
|
||||
test "raises ActionNotFound when method does not exist and action_missing is not defined" do
|
||||
assert_raise(ActionNotFound) { DefaultRespondToActionController.process(:fail) }
|
||||
end
|
||||
|
||||
test "dispatches to action_missing when method does not exist and action_missing is defined" do
|
||||
assert_dispatch ActionMissingRespondToActionController, "success", :ohai
|
||||
end
|
||||
|
||||
test "a method is available as an action if respond_to_action? returns true" do
|
||||
assert_dispatch RespondToActionController, "success", :index
|
||||
end
|
||||
|
||||
test "raises ActionNotFound if method is defined but respond_to_action? returns false" do
|
||||
assert_raise(ActionNotFound) { RespondToActionController.process(:fail) }
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -23,38 +23,6 @@ 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
|
||||
|
@ -64,66 +32,134 @@ module ActionController
|
|||
|
||||
include ActionController::HideActions
|
||||
include ActionController::UrlFor
|
||||
include ActionController::Renderer
|
||||
|
||||
CORE_METHODS = self.public_instance_methods
|
||||
end
|
||||
end
|
||||
|
||||
# Provide some controller to run the tests on.
|
||||
# Temporary base class
|
||||
class Rack::TestCase < ActiveSupport::TestCase
|
||||
|
||||
include Rack::Test::Methods
|
||||
|
||||
setup do
|
||||
ActionController::Base.session_options[:key] = "abc"
|
||||
ActionController::Base.session_options[:secret] = ("*" * 30)
|
||||
ActionController::Routing.use_controllers! %w(happy_path/simple_dispatch)
|
||||
end
|
||||
|
||||
def self.get(url)
|
||||
setup do |test|
|
||||
test.get url
|
||||
end
|
||||
end
|
||||
|
||||
def app
|
||||
@app ||= ActionController::Dispatcher.new
|
||||
end
|
||||
|
||||
def assert_body(body)
|
||||
assert_equal [body], last_response.body
|
||||
end
|
||||
|
||||
def assert_status(code)
|
||||
assert_equal code, last_response.status
|
||||
end
|
||||
|
||||
def assert_content_type(type)
|
||||
assert_equal type, last_response.headers["Content-Type"]
|
||||
end
|
||||
|
||||
def assert_header(name, value)
|
||||
assert_equal value, last_response.headers[name]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
# Tests the controller dispatching happy path
|
||||
module HappyPath
|
||||
class SimpleDispatchController < ActionController::Base2
|
||||
def index
|
||||
render :text => "success"
|
||||
end
|
||||
|
||||
def modify_response_body
|
||||
self.response_body = "success"
|
||||
end
|
||||
|
||||
def modify_response_body_twice
|
||||
ret = (self.response_body = "success")
|
||||
self.response_body = "#{ret}!"
|
||||
end
|
||||
|
||||
def modify_response_headers
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
class SimpleRouteCase < Rack::TestCase
|
||||
setup do
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect ':controller/:action'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TestSimpleDispatch < SimpleRouteCase
|
||||
|
||||
get "/happy_path/simple_dispatch/index"
|
||||
|
||||
test "sets the body" do
|
||||
assert_body "success"
|
||||
end
|
||||
|
||||
test "sets the status code" do
|
||||
assert_status 200
|
||||
end
|
||||
|
||||
test "sets the content type" do
|
||||
assert_content_type Mime::HTML
|
||||
end
|
||||
|
||||
test "sets the content length" do
|
||||
assert_header "Content-Length", 7
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# :api: plugin
|
||||
class TestDirectResponseMod < SimpleRouteCase
|
||||
get "/happy_path/simple_dispatch/modify_response_body"
|
||||
|
||||
test "sets the body" do
|
||||
assert_body "success"
|
||||
end
|
||||
|
||||
test "setting the body manually sets the content length" do
|
||||
assert_header "Content-Length", 7
|
||||
end
|
||||
end
|
||||
|
||||
# :api: plugin
|
||||
class TestDirectResponseModTwice < SimpleRouteCase
|
||||
get "/happy_path/simple_dispatch/modify_response_body_twice"
|
||||
|
||||
test "self.response_body= returns the body being set" do
|
||||
assert_body "success!"
|
||||
end
|
||||
|
||||
test "updating the response body updates the content length" do
|
||||
assert_header "Content-Length", 8
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class EmptyController < ActionController::Base2 ; end
|
||||
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
|
||||
class ContainedEmptyController < ActionController::Base2 ; end
|
||||
end
|
||||
|
||||
class ControllerClassTests < Test::Unit::TestCase
|
||||
|
@ -137,181 +173,4 @@ class ControllerClassTests < Test::Unit::TestCase
|
|||
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
|
||||
end
|
Loading…
Reference in a new issue