Skip to content

Commit

Permalink
Add ActionController::HTTP
Browse files Browse the repository at this point in the history
More info http://edgeguides.rubyonrails.org/api_app.html

[Carlos Antonio da Silva & Santiago Pastorino]
  • Loading branch information
spastorino committed Mar 14, 2012
1 parent cc1c4ac commit 4c16791
Show file tree
Hide file tree
Showing 17 changed files with 383 additions and 9 deletions.
1 change: 1 addition & 0 deletions actionpack/lib/action_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module ActionController

autoload :Base
autoload :Caching
autoload :HTTP
autoload :Metal
autoload :Middleware

Expand Down
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ module ActionController
class Base < Metal
abstract!

# Shortcut helper that returns all the ActionController modules except the ones passed in the argument:
# Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument:
#
# class MetalController
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
Expand Down
134 changes: 134 additions & 0 deletions actionpack/lib/action_controller/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
require "action_controller/log_subscriber"

module ActionController
# HTTP Controller is a lightweight version of <tt>ActionController::Base</tt>,
# created for applications that don't require all functionality that a complete
# \Rails controller provides, allowing you to create faster controllers. The
# main scenario where HTTP Controllers could be used is API only applications.
#
# An HTTP Controller is different from a normal controller in the sense that
# by default it doesn't include a number of features that are usually required
# by browser access only: layouts and templates rendering, cookies, sessions,
# flash, assets, and so on. This makes the entire controller stack thinner and
# faster, suitable for API applications. It doesn't mean you won't have such
# features if you need them: they're all available for you to include in
# your application, they're just not part of the default HTTP Controller stack.
#
# By default, only the ApplicationController in a \Rails application inherits
# from <tt>ActionController::HTTP</tt>. All other controllers in turn inherit
# from ApplicationController.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
# @posts = Post.all
# render json: @posts
# end
# end
#
# Request, response and parameters objects all work the exact same way as
# <tt>ActionController::Base</tt>.
#
# == Renders
#
# The default HTTP Controller stack includes all renderers, which means you
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
# in mind that templates are not going to be rendered, so you need to ensure
# your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
# all actions.
#
# def show
# @post = Post.find(params[:id])
# render json: @post
# end
#
# == Redirects
#
# Redirects are used to move from one action to another. You can use the
# <tt>redirect</tt> method in your controllers in the same way as
# <tt>ActionController::Base</tt>. For example:
#
# def create
# redirect_to root_url and return if not_authorized?
# # do stuff here
# end
#
# == Adding new behavior
#
# In some scenarios you may want to add back some functionality provided by
# <tt>ActionController::Base</tt> that is not present by default in
# <tt>ActionController::HTTP</tt>, for instance <tt>MimeResponds</tt>. This
# module gives you the <tt>respond_to</tt> and <tt>respond_with</tt> methods.
# Adding it is quite simple, you just need to include the module in a specific
# controller or in <tt>ApplicationController</tt> in case you want it
# available to your entire app:
#
# class ApplicationController < ActionController::HTTP
# include ActionController::MimeResponds
# end
#
# class PostsController < ApplicationController
# respond_to :json, :xml
#
# def index
# @posts = Post.all
# respond_with @posts
# end
# end
#
# Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
# available modules if you want to include any other functionality that is
# not provided by <tt>ActionController::HTTP</tt> out of the box.
class HTTP < Metal
abstract!

# Shortcut helper that returns all the ActionController::HTTP modules except the ones passed in the argument:
#
# class MetalController
# ActionController::HTTP.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# end
# end
#
# This gives better control over what you want to exclude and makes it easier
# to create a bare controller class, instead of listing the modules required manually.
def self.without_modules(*modules)
modules = modules.map do |m|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
end

MODULES - modules
end

MODULES = [
HideActions,
UrlFor,
Redirecting,
Rendering,
Renderers::All,
ConditionalGet,
RackDelegation,

ForceSSL,
DataStreaming,

# Before callbacks should also be executed the earliest as possible, so
# also include them at the bottom.
AbstractController::Callbacks,

# Append rescue at the bottom to wrap as much as possible.
Rescue,

# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
Instrumentation
]

MODULES.each do |mod|
include mod
end

ActiveSupport.run_load_hooks(:action_controller, self)
end
end
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/metal/force_ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def force_ssl(options = {})
redirect_options = {:protocol => 'https://', :status => :moved_permanently}
redirect_options.merge!(:host => host) if host
redirect_options.merge!(:params => request.query_parameters)
flash.keep
flash.keep if respond_to?(:flash)
redirect_to redirect_options
end
end
Expand Down
13 changes: 10 additions & 3 deletions actionpack/lib/action_controller/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Railtie < Rails::Railtie #:nodoc:
end

initializer "action_controller.initialize_framework_caches" do
ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache }
ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache if respond_to?(:cache_store) }
end

initializer "action_controller.assets_config", :group => :all do |app|
Expand All @@ -37,8 +37,15 @@ class Railtie < Rails::Railtie #:nodoc:
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
extend ::ActionController::Railties::Paths.with(app)
options.each { |k,v| send("#{k}=", v) }
extend ::ActionController::Railties::Paths.with(app) if respond_to?(:helpers_path)
options.each do |k,v|
k = "#{k}="
if respond_to?(k)
send(k, v)
elsif !Base.respond_to?(k)
raise "Invalid option key: #{k}"
end
end
end
end

Expand Down
1 change: 0 additions & 1 deletion actionpack/lib/action_controller/railties/paths.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ def self.with(app)
else
paths = app.helpers_paths
end

klass.helpers_path = paths

if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers
Expand Down
9 changes: 7 additions & 2 deletions actionpack/lib/action_dispatch/http/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ class Response
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
attr_accessor :charset, :content_type
attr_accessor :charset
attr_reader :content_type

CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze

cattr_accessor(:default_charset) { "utf-8" }

include Rack::Response::Helpers
Expand All @@ -83,6 +84,10 @@ def status=(status)
@status = Rack::Utils.status_code(status)
end

def content_type=(content_type)
@content_type = content_type.to_s
end

# The response code of the request
def response_code
@status
Expand Down
4 changes: 4 additions & 0 deletions actionpack/test/abstract_unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ def self.test_routes(&block)
end
end

class HTTP
include SharedTestRoutes.url_helpers
end

class TestCase
include ActionDispatch::TestProcess

Expand Down
19 changes: 19 additions & 0 deletions actionpack/test/controller/http/action_methods_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'abstract_unit'

class ActionMethodsHTTPController < ActionController::HTTP
def one; end
def two; end
hide_action :two
end

class ActionMethodsHTTPTest < ActiveSupport::TestCase
def setup
@controller = ActionMethodsHTTPController.new
end

def test_action_methods
assert_equal Set.new(%w(one)),
@controller.class.action_methods,
"#{@controller.controller_path} should not be empty!"
end
end
55 changes: 55 additions & 0 deletions actionpack/test/controller/http/conditional_get_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
require 'abstract_unit'

class ConditionalGetHTTPController < ActionController::HTTP
before_filter :handle_last_modified_and_etags, :only => :two

def one
if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123])
render :text => "Hi!"
end
end

def two
render :text => "Hi!"
end

private

def handle_last_modified_and_etags
fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
end
end

class ConditionalGetHTTPTest < ActionController::TestCase
tests ConditionalGetHTTPController

def setup
@last_modified = Time.now.utc.beginning_of_day.httpdate
end

def test_request_with_bang_gets_last_modified
get :two
assert_equal @last_modified, @response.headers['Last-Modified']
assert_response :success
end

def test_request_with_bang_obeys_last_modified
@request.if_modified_since = @last_modified
get :two
assert_response :not_modified
end

def test_last_modified_works_with_less_than_too
@request.if_modified_since = 5.years.ago.httpdate
get :two
assert_response :success
end

def test_request_not_modified
@request.if_modified_since = @last_modified
get :one
assert_equal 304, @response.status.to_i
assert_blank @response.body
assert_equal @last_modified, @response.headers['Last-Modified']
end
end
27 changes: 27 additions & 0 deletions actionpack/test/controller/http/data_streaming_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require 'abstract_unit'

module TestHTTPFileUtils
def file_name() File.basename(__FILE__) end
def file_path() File.expand_path(__FILE__) end
def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end
end

class DataStreamingHTTPController < ActionController::HTTP
include TestHTTPFileUtils

def one; end
def two
send_data(file_data, {})
end
end

class DataStreamingHTTPTest < ActionController::TestCase
include TestHTTPFileUtils
tests DataStreamingHTTPController

def test_data
response = process('two')
assert_kind_of String, response.body
assert_equal file_data, response.body
end
end
20 changes: 20 additions & 0 deletions actionpack/test/controller/http/force_ssl_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require 'abstract_unit'

class ForceSSLHTTPController < ActionController::HTTP
force_ssl

def one; end
def two
head :ok
end
end

class ForceSSLHTTPTest < ActionController::TestCase
tests ForceSSLHTTPController

def test_banana_redirects_to_https
get :two
assert_response 301
assert_equal "https://test.host/force_sslhttp/two", redirect_to_url
end
end
19 changes: 19 additions & 0 deletions actionpack/test/controller/http/redirect_to_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'abstract_unit'

class RedirectToHTTPController < ActionController::HTTP
def one
redirect_to :action => "two"
end

def two; end
end

class RedirectToHTTPTest < ActionController::TestCase
tests RedirectToHTTPController

def test_redirect_to
get :one
assert_response :redirect
assert_equal "http://test.host/redirect_to_http/two", redirect_to_url
end
end
Loading

0 comments on commit 4c16791

Please sign in to comment.