Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add ActionController::HTTP

More info http://edgeguides.rubyonrails.org/api_app.html

[Carlos Antonio da Silva & Santiago Pastorino]
  • Loading branch information...
commit 4c16791f355c74f8e6ad916e67fd4ae81efbf708 1 parent cc1c4ac
Santiago Pastorino spastorino authored
1  actionpack/lib/action_controller.rb
View
@@ -6,6 +6,7 @@ module ActionController
autoload :Base
autoload :Caching
+ autoload :HTTP
autoload :Metal
autoload :Middleware
2  actionpack/lib/action_controller/base.rb
View
@@ -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|
134 actionpack/lib/action_controller/http.rb
View
@@ -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  actionpack/lib/action_controller/metal/force_ssl.rb
View
@@ -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
13 actionpack/lib/action_controller/railtie.rb
View
@@ -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|
@@ -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
1  actionpack/lib/action_controller/railties/paths.rb
View
@@ -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
9 actionpack/lib/action_dispatch/http/response.rb
View
@@ -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
@@ -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
4 actionpack/test/abstract_unit.rb
View
@@ -293,6 +293,10 @@ def self.test_routes(&block)
end
end
+ class HTTP
+ include SharedTestRoutes.url_helpers
+ end
+
class TestCase
include ActionDispatch::TestProcess
19 actionpack/test/controller/http/action_methods_test.rb
View
@@ -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 actionpack/test/controller/http/conditional_get_test.rb
View
@@ -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 actionpack/test/controller/http/data_streaming_test.rb
View
@@ -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 actionpack/test/controller/http/force_ssl_test.rb
View
@@ -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 actionpack/test/controller/http/redirect_to_test.rb
View
@@ -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
37 actionpack/test/controller/http/renderers_test.rb
View
@@ -0,0 +1,37 @@
+require 'abstract_unit'
+
+class Model
+ def to_json(options = {})
+ { :a => 'b' }.to_json(options)
+ end
+
+ def to_xml(options = {})
+ { :a => 'b' }.to_xml(options)
+ end
+end
+
+class RenderersHTTPController < ActionController::HTTP
+ def one
+ render :json => Model.new
+ end
+
+ def two
+ render :xml => Model.new
+ end
+end
+
+class RenderersHTTPTest < ActionController::TestCase
+ tests RenderersHTTPController
+
+ def test_render_json
+ get :one
+ assert_response :success
+ assert_equal({ :a => 'b' }.to_json, @response.body)
+ end
+
+ def test_render_xml
+ get :two
+ assert_response :success
+ assert_equal({ :a => 'b' }.to_xml, @response.body)
+ end
+end
20 actionpack/test/controller/http/url_for_test.rb
View
@@ -0,0 +1,20 @@
+require 'abstract_unit'
+
+class UrlForHTTPController < ActionController::HTTP
+ def one; end
+ def two; end
+end
+
+class UrlForHTTPTest < ActionController::TestCase
+ tests UrlForHTTPController
+
+ def setup
+ super
+ @request.host = 'www.example.com'
+ end
+
+ def test_url_for
+ get :one
+ assert_equal "http://www.example.com/url_for_http/one", @controller.url_for
+ end
+end
2  railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt
View
@@ -5,7 +5,7 @@
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
ActiveSupport.on_load(:action_controller) do
- wrap_parameters format: [:json]
+ wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end
<%- unless options.skip_active_record? -%>
27 railties/test/application/initializers/frameworks_test.rb
View
@@ -130,6 +130,33 @@ def from_bar_helper
assert_equal "false", last_response.body
end
+ test "action_controller http initializes successfully" do
+ app_file "app/controllers/application_controller.rb", <<-RUBY
+ class ApplicationController < ActionController::HTTP
+ end
+ RUBY
+
+ app_file "app/controllers/omg_controller.rb", <<-RUBY
+ class OmgController < ApplicationController
+ def show
+ render :json => { :omg => 'omg' }
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match "/:controller(/:action)"
+ end
+ RUBY
+
+ require 'rack/test'
+ extend Rack::Test::Methods
+
+ get '/omg/show'
+ assert_equal '{"omg":"omg"}', last_response.body
+ end
+
# AD
test "action_dispatch extensions are applied to ActionDispatch" do
add_to_config "config.action_dispatch.tld_length = 2"
Please sign in to comment.
Something went wrong with that request. Please try again.