From 032778eefb4439a72c2933ea0bd4a7a0ef776234 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 15 Apr 2015 15:30:27 -0300 Subject: Add ActionController API functionality --- actionpack/lib/action_controller.rb | 5 + actionpack/lib/action_controller/api.rb | 157 +++++++++++++++++++++ .../lib/action_controller/api/api_rendering.rb | 14 ++ actionpack/test/abstract_unit.rb | 4 + .../test/controller/api/conditional_get_test.rb | 57 ++++++++ .../test/controller/api/data_streaming_test.rb | 26 ++++ actionpack/test/controller/api/force_ssl_test.rb | 20 +++ actionpack/test/controller/api/redirect_to_test.rb | 19 +++ actionpack/test/controller/api/renderers_test.rb | 38 +++++ actionpack/test/controller/api/url_for_test.rb | 20 +++ 10 files changed, 360 insertions(+) create mode 100644 actionpack/lib/action_controller/api.rb create mode 100644 actionpack/lib/action_controller/api/api_rendering.rb create mode 100644 actionpack/test/controller/api/conditional_get_test.rb create mode 100644 actionpack/test/controller/api/data_streaming_test.rb create mode 100644 actionpack/test/controller/api/force_ssl_test.rb create mode 100644 actionpack/test/controller/api/redirect_to_test.rb create mode 100644 actionpack/test/controller/api/renderers_test.rb create mode 100644 actionpack/test/controller/api/url_for_test.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index a1893ce920..952a035a16 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -7,6 +7,7 @@ require 'action_controller/metal/strong_parameters' module ActionController extend ActiveSupport::Autoload + autoload :API autoload :Base autoload :Caching autoload :Metal @@ -41,6 +42,10 @@ module ActionController autoload :UrlFor end + autoload_under "api" do + autoload :ApiRendering + end + autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb new file mode 100644 index 0000000000..f9cbbf227f --- /dev/null +++ b/actionpack/lib/action_controller/api.rb @@ -0,0 +1,157 @@ +require 'action_view' +require 'action_controller' +require 'action_controller/log_subscriber' + +module ActionController + # API Controller is a lightweight version of ActionController::Base, + # created for applications that don't require all functionality that a complete + # \Rails controller provides, allowing you to create faster controllers for + # example for API only applications. + # + # An API 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 API Controller stack. + # + # By default, only the ApplicationController in a \Rails application inherits + # from ActionController::API. 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 + # ActionController::Base. + # + # == Renders + # + # The default API Controller stack includes all renderers, which means you + # can use render :json 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 render or redirect 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 + # redirect method in your controllers in the same way as + # ActionController::Base. 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 + # ActionController::Base that is not present by default in + # ActionController::API, for instance MimeResponds. This + # module gives you the respond_to and respond_with methods. + # Adding it is quite simple, you just need to include the module in a specific + # controller or in ApplicationController in case you want it + # available to your entire app: + # + # class ApplicationController < ActionController::API + # 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 ActionController::Base + # available modules if you want to include any other functionality that is + # not provided by ActionController::API out of the box. + class API < Metal + abstract! + + module Compatibility + def cache_store; end + def cache_store=(*); end + def assets_dir=(*); end + def javascripts_dir=(*); end + def stylesheets_dir=(*); end + def page_cache_directory=(*); end + def asset_path=(*); end + def asset_host=(*); end + def relative_url_root=(*); end + def perform_caching=(*); end + def helpers_path=(*); end + def allow_forgery_protection=(*); end + def helper_method(*); end + def helper(*); end + end + + extend Compatibility + + # Shortcut helper that returns all the ActionController::API modules except the ones passed in the argument: + # + # class MetalController + # ActionController::API.without_modules(:Redirecting, :DataStreaming).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier + # to create an api 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 = [ + AbstractController::Rendering, + + UrlFor, + Redirecting, + ApiRendering, + Renderers::All, + ConditionalGet, + RackDelegation, + StrongParameters, + + 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 diff --git a/actionpack/lib/action_controller/api/api_rendering.rb b/actionpack/lib/action_controller/api/api_rendering.rb new file mode 100644 index 0000000000..3a08d28c39 --- /dev/null +++ b/actionpack/lib/action_controller/api/api_rendering.rb @@ -0,0 +1,14 @@ +module ActionController + module ApiRendering + extend ActiveSupport::Concern + + included do + include Rendering + end + + def render_to_body(options = {}) + _process_options(options) + super + end + end +end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 1690bdd542..cc610b6d75 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -232,6 +232,10 @@ class Rack::TestCase < ActionDispatch::IntegrationTest end module ActionController + class API + extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes) + end + class Base # This stub emulates the Railtie including the URL helpers from a Rails application extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes) diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb new file mode 100644 index 0000000000..e729ea9d33 --- /dev/null +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -0,0 +1,57 @@ +require 'abstract_unit' +require 'active_support/core_ext/integer/time' +require 'active_support/core_ext/numeric/time' + +class ConditionalGetApiController < ActionController::API + before_action :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 ConditionalGetApiTest < ActionController::TestCase + tests ConditionalGetApiController + + 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 @response.body.blank? + assert_equal @last_modified, @response.headers['Last-Modified'] + end +end diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb new file mode 100644 index 0000000000..0e7d97d1f4 --- /dev/null +++ b/actionpack/test/controller/api/data_streaming_test.rb @@ -0,0 +1,26 @@ +require 'abstract_unit' + +module TestApiFileUtils + def file_path() File.expand_path(__FILE__) end + def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end +end + +class DataStreamingApiController < ActionController::API + include TestApiFileUtils + + def one; end + def two + send_data(file_data, {}) + end +end + +class DataStreamingApiTest < ActionController::TestCase + include TestApiFileUtils + tests DataStreamingApiController + + def test_data + response = process('two') + assert_kind_of String, response.body + assert_equal file_data, response.body + end +end diff --git a/actionpack/test/controller/api/force_ssl_test.rb b/actionpack/test/controller/api/force_ssl_test.rb new file mode 100644 index 0000000000..8578340d82 --- /dev/null +++ b/actionpack/test/controller/api/force_ssl_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' + +class ForceSSLApiController < ActionController::API + force_ssl + + def one; end + def two + head :ok + end +end + +class ForceSSLApiTest < ActionController::TestCase + tests ForceSSLApiController + + def test_redirects_to_https + get :two + assert_response 301 + assert_equal "https://test.host/force_ssl_api/two", redirect_to_url + end +end diff --git a/actionpack/test/controller/api/redirect_to_test.rb b/actionpack/test/controller/api/redirect_to_test.rb new file mode 100644 index 0000000000..67832d7dba --- /dev/null +++ b/actionpack/test/controller/api/redirect_to_test.rb @@ -0,0 +1,19 @@ +require 'abstract_unit' + +class RedirectToApiController < ActionController::API + def one + redirect_to :action => "two" + end + + def two; end +end + +class RedirectToApiTest < ActionController::TestCase + tests RedirectToApiController + + def test_redirect_to + get :one + assert_response :redirect + assert_equal "http://test.host/redirect_to_api/two", redirect_to_url + end +end diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb new file mode 100644 index 0000000000..26f53499fd --- /dev/null +++ b/actionpack/test/controller/api/renderers_test.rb @@ -0,0 +1,38 @@ +require 'abstract_unit' +require 'active_support/core_ext/hash/conversions' + +class Model + def to_json(options = {}) + { :a => 'b' }.to_json(options) + end + + def to_xml(options = {}) + { :a => 'b' }.to_xml(options) + end +end + +class RenderersApiController < ActionController::API + def one + render :json => Model.new + end + + def two + render :xml => Model.new + end +end + +class RenderersApiTest < ActionController::TestCase + tests RenderersApiController + + 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 diff --git a/actionpack/test/controller/api/url_for_test.rb b/actionpack/test/controller/api/url_for_test.rb new file mode 100644 index 0000000000..0d8691a091 --- /dev/null +++ b/actionpack/test/controller/api/url_for_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' + +class UrlForApiController < ActionController::API + def one; end + def two; end +end + +class UrlForApiTest < ActionController::TestCase + tests UrlForApiController + + def setup + super + @request.host = 'www.example.com' + end + + def test_url_for + get :one + assert_equal "http://www.example.com/url_for_api/one", @controller.url_for + end +end -- cgit v1.2.3 From 2d86b6d9aec6c46fb6c85a4c504cae48d77c2564 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 15 Apr 2015 15:50:19 -0300 Subject: Move Model test class inside RenderersApiController namespace --- actionpack/test/controller/api/renderers_test.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb index 26f53499fd..d99eb0cf13 100644 --- a/actionpack/test/controller/api/renderers_test.rb +++ b/actionpack/test/controller/api/renderers_test.rb @@ -1,17 +1,17 @@ require 'abstract_unit' require 'active_support/core_ext/hash/conversions' -class Model - def to_json(options = {}) - { :a => 'b' }.to_json(options) - end +class RenderersApiController < ActionController::API + class Model + def to_json(options = {}) + { :a => 'b' }.to_json(options) + end - def to_xml(options = {}) - { :a => 'b' }.to_xml(options) + def to_xml(options = {}) + { :a => 'b' }.to_xml(options) + end end -end -class RenderersApiController < ActionController::API def one render :json => Model.new end -- cgit v1.2.3 From 135c059d6f0664991f158d358a4312e550ead7a6 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 16 Apr 2015 14:55:32 -0300 Subject: Add config.api_only option to application and remove appropriate middleware when true --- railties/lib/rails/application/configuration.rb | 3 ++- .../rails/application/default_middleware_stack.rb | 6 ++--- railties/test/application/middleware_test.rb | 27 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 78a47fcda9..6ffbb1b204 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -13,7 +13,7 @@ module Rails :railties_order, :relative_url_root, :secret_key_base, :secret_token, :serve_static_files, :ssl_options, :static_cache_control, :static_index, :session_options, :time_zone, :reload_classes_only_on_change, - :beginning_of_week, :filter_redirect, :x + :beginning_of_week, :filter_redirect, :api_only, :x attr_writer :log_level attr_reader :encoding @@ -49,6 +49,7 @@ module Rails @eager_load = nil @secret_token = nil @secret_key_base = nil + @api_only = false @x = Custom.new end diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 909ed5cc35..6f9ccec137 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -28,7 +28,7 @@ module Rails middleware.use ::Rack::Lock unless allow_concurrency? middleware.use ::Rack::Runtime - middleware.use ::Rack::MethodOverride + middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId # Must come after Rack::MethodOverride to properly log overridden methods @@ -42,9 +42,9 @@ module Rails end middleware.use ::ActionDispatch::Callbacks - middleware.use ::ActionDispatch::Cookies + middleware.use ::ActionDispatch::Cookies unless config.api_only - if config.session_store + if !config.api_only && config.session_store if config.force_ssl && !config.session_options.key?(:secure) config.session_options[:secure] = true end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 04bd19784a..ce92ebbf66 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -50,6 +50,33 @@ module ApplicationTests ], middleware end + test "api middleware stack" do + add_to_config "config.api_only = true" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "Rack::Lock", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "ActionDispatch::RequestId", + "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods + "ActionDispatch::ShowExceptions", + "ActionDispatch::DebugExceptions", + "ActionDispatch::RemoteIp", + "ActionDispatch::Reloader", + "ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement", + "ActiveRecord::QueryCache", + "ActionDispatch::ParamsParser", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + test "Rack::Cache is not included by default" do boot! -- cgit v1.2.3 From 212a099ab022fe617d94f195423432dad5cdb21a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 16 Apr 2015 15:18:40 -0300 Subject: Add AC::API + its middleware stack integration test --- .../application/initializers/frameworks_test.rb | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 97b51911d9..709286d728 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -129,6 +129,35 @@ module ApplicationTests assert_equal "false", last_response.body end + test "action_controller api executes using all the middleware stack" do + add_to_config "config.api_only = true" + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::API + 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 + Rails.application.routes.draw do + get "/: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" -- cgit v1.2.3 From 3adb5eac3b6d81a0943bebd8dffa25a3b63681eb Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 16 Apr 2015 15:56:07 -0300 Subject: Add ApiPublicException middleware --- actionpack/lib/action_dispatch.rb | 1 + .../middleware/api_public_exceptions.rb | 47 ++++++++++++++++++++++ actionpack/test/dispatch/show_exceptions_test.rb | 41 ++++++++++++++++++- 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index dcd3ee0644..74ddf2954a 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -53,6 +53,7 @@ module ActionDispatch autoload :ExceptionWrapper autoload :Flash autoload :ParamsParser + autoload :ApiPublicExceptions autoload :PublicExceptions autoload :Reloader autoload :RemoteIp diff --git a/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb new file mode 100644 index 0000000000..126842672d --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb @@ -0,0 +1,47 @@ +module ActionDispatch + class ApiPublicExceptions + attr_accessor :public_path + + def initialize(public_path) + @public_path = public_path + end + + def call(env) + exception = env["action_dispatch.exception"] + status = env["PATH_INFO"][1..-1] + request = ActionDispatch::Request.new(env) + content_type = request.formats.first + body = { :status => status, :error => exception.message } + + render(status, content_type, body) + end + + private + + def render(status, content_type, body) + format = content_type && "to_#{content_type.to_sym}" + if format && body.respond_to?(format) + render_format(status, content_type, body.public_send(format)) + else + render_html(status) + end + end + + def render_format(status, content_type, body) + [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", + 'Content-Length' => body.bytesize.to_s}, [body]] + end + + def render_html(status) + found = false + path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale + path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path)) + + if found || File.exist?(path) + render_format(status, 'text/html', File.read(path)) + else + [404, { "X-Cascade" => "pass" }, []] + end + end + end +end diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 72eaa916bc..c70bc5f95e 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -11,7 +11,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest when "/bad_params" raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new) when "/method_not_allowed" - raise ActionController::MethodNotAllowed + raise ActionController::MethodNotAllowed, 'PUT' when "/unknown_http_method" raise ActionController::UnknownHttpMethod when "/not_found_original_exception" @@ -22,7 +22,8 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end end - ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) + ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) + ProductionApiApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::ApiPublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) test "skip exceptions app if not showing exceptions" do @app = ProductionApp @@ -55,6 +56,42 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest assert_equal "", body end + test "rescue api apps with json response" do + @app = ProductionApiApp + + get "/", headers: { 'HTTP_ACCEPT' => 'application/json', 'action_dispatch.show_exceptions' => true } + assert_response 500 + assert_equal({ :status => '500', :error => 'puke!' }.to_json, body) + + get "/method_not_allowed", headers: { 'HTTP_ACCEPT' => 'application/json', 'action_dispatch.show_exceptions' => true } + assert_response 405 + assert_equal({ :status => '405', :error => 'Only PUT requests are allowed.' }.to_json, body) + + get "/unknown_http_method", headers: { 'HTTP_ACCEPT' => 'application/json', 'action_dispatch.show_exceptions' => true } + assert_response 405 + assert_equal({ :status => '405', :error => 'ActionController::UnknownHttpMethod' }.to_json, body) + end + + test "rescue api apps unknown content-type requests with html response" do + @app = ProductionApiApp + + get "/", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } + assert_response 500 + assert_equal "500 error fixture\n", body + + get "/bad_params", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } + assert_response 400 + assert_equal "400 error fixture\n", body + + get "/not_found", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } + assert_response 404 + assert_equal "404 error fixture\n", body + + get "/unknown_http_method", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } + assert_response 405 + assert_equal("", body) + end + test "localize rescue error page" do old_locale, I18n.locale = I18n.locale, :da -- cgit v1.2.3 From 2a9cf48a615d39b932f0323d158e935a0300f7c4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 16 Apr 2015 18:28:40 -0300 Subject: rails new --api generates an api app skeleton --- railties/lib/rails/generators.rb | 1 + railties/lib/rails/generators/app_base.rb | 4 +- .../rails/generators/rails/app/app_generator.rb | 7 +- .../app/controllers/application_controller.rb.tt | 4 +- railties/test/generators/api_app_generator_test.rb | 76 ++++++++++++++++++++++ 5 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 railties/test/generators/api_app_generator_test.rb diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 79088bbe3c..96742dd2e6 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -45,6 +45,7 @@ module Rails DEFAULT_OPTIONS = { rails: { + api: false, assets: true, force_plural: false, helper: true, diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c02b39d203..b85bf874a6 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -252,7 +252,7 @@ module Rails end def assets_gemfile_entry - return [] if options[:skip_sprockets] + return [] if options[:skip_sprockets] || options[:api] gems = [] gems << GemfileEntry.version('sass-rails', '~> 5.0', @@ -280,7 +280,7 @@ module Rails end def javascript_gemfile_entry - if options[:skip_javascript] + if options[:skip_javascript] || options[:api] [] else gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry] diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 152c26860e..6197b85bb6 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -175,6 +175,9 @@ module Rails class_option :version, type: :boolean, aliases: "-v", group: :rails, desc: "Show Rails version number and quit" + class_option :api, type: :boolean, + desc: "Preconfigure smaller stack for API only apps" + def initialize(*args) super @@ -245,11 +248,11 @@ module Rails end def create_tmp_files - build(:tmp) + build(:tmp) unless options[:api] end def create_vendor_files - build(:vendor) + build(:vendor) unless options[:api] end def delete_js_folder_skipping_javascript diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt index d83690e1b9..f726fd6305 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt @@ -1,5 +1,7 @@ -class ApplicationController < ActionController::Base +class ApplicationController < ActionController::<%= options[:api] ? "API" : "Base" %> +<%- unless options[:api] -%> # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception +<%- end -%> end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb new file mode 100644 index 0000000000..d7121da45f --- /dev/null +++ b/railties/test/generators/api_app_generator_test.rb @@ -0,0 +1,76 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/app/app_generator' + +class ApiAppGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests Rails::Generators::AppGenerator + + arguments [destination_root, '--api'] + + def setup + Rails.application = TestApp::Application + super + + Kernel::silence_warnings do + Thor::Base.shell.send(:attr_accessor, :always_force) + @shell = Thor::Base.shell.new + @shell.send(:always_force=, true) + end + end + + def teardown + super + Rails.application = TestApp::Application.instance + end + + def test_skeleton_is_created + run_generator + + default_files.each { |path| assert_file path } + skipped_files.each { |path| assert_no_file path } + end + + def test_api_modified_files + run_generator + + assert_file "Gemfile" do |content| + assert_no_match(/gem 'coffee-rails'/, content) + assert_no_match(/gem 'jquery-rails'/, content) + assert_no_match(/gem 'sass-rails'/, content) + end + assert_file "app/controllers/application_controller.rb", /ActionController::API/ + end + + private + + def default_files + files = %W( + .gitignore + Gemfile + Rakefile + config.ru + app/controllers + app/mailers + app/models + config/environments + config/initializers + config/locales + db + lib + lib/tasks + lib/assets + log + test/fixtures + test/controllers + test/integration + test/models + ) + files.concat %w(bin/bundle bin/rails bin/rake) + files + end + + def skipped_files + %w(vendor/assets + tmp/cache/assets) + end +end -- cgit v1.2.3 From c09a401660058606109291d1f1763e8cd13bdb4b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 11:51:15 -0300 Subject: Generate appropriate initializers for an api app --- railties/lib/rails/generators/rails/app/app_generator.rb | 11 +++++++++++ .../app/templates/config/initializers/wrap_parameters.rb.tt | 2 ++ railties/test/generators/api_app_generator_test.rb | 9 ++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 6197b85bb6..d05fb55212 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -188,6 +188,10 @@ module Rails if !options[:skip_active_record] && !DATABASES.include?(options[:database]) raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." end + + # Force sprockets to be skipped when generating http only app. + # Can't modify options hash as it's frozen by default. + self.options = options.merge(skip_sprockets: true).freeze if options[:api] end public_task :set_default_accessors! @@ -273,6 +277,13 @@ module Rails end end + def delete_non_api_initializers_if_api_option + if options[:api] + remove_file 'config/initializers/session_store.rb' + remove_file 'config/initializers/cookies_serializer.rb' + end + end + def finish_template build(:leftovers) end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 94f612c3dd..0fc1514684 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -1,5 +1,6 @@ # Be sure to restart your server when you modify this file. +<%- unless options[:api] -%> # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. @@ -7,6 +8,7 @@ ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end +<%- end -%> <%- unless options.skip_active_record? -%> # To enable root element in JSON for ActiveRecord objects. diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index d7121da45f..3c0c8054a4 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -38,6 +38,10 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_no_match(/gem 'jquery-rails'/, content) assert_no_match(/gem 'sass-rails'/, content) end + + assert_file "config/initializers/wrap_parameters.rb" do |content| + assert_no_match(/wrap_parameters/, content) + end assert_file "app/controllers/application_controller.rb", /ActionController::API/ end @@ -70,7 +74,10 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase end def skipped_files - %w(vendor/assets + %w(config/initializers/assets.rb + config/initializers/cookies_serializer.rb + config/initializers/session_store.rb + vendor/assets tmp/cache/assets) end end -- cgit v1.2.3 From e8915d098c7ed1d652b0abb0fa646770627cd60f Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 11:59:14 -0300 Subject: api option implies skipping javascript & sprockets --- railties/lib/rails/generators/app_base.rb | 4 ++-- railties/lib/rails/generators/rails/app/app_generator.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index b85bf874a6..c02b39d203 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -252,7 +252,7 @@ module Rails end def assets_gemfile_entry - return [] if options[:skip_sprockets] || options[:api] + return [] if options[:skip_sprockets] gems = [] gems << GemfileEntry.version('sass-rails', '~> 5.0', @@ -280,7 +280,7 @@ module Rails end def javascript_gemfile_entry - if options[:skip_javascript] || options[:api] + if options[:skip_javascript] [] else gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry] diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index d05fb55212..d0e957037a 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -191,7 +191,7 @@ module Rails # Force sprockets to be skipped when generating http only app. # Can't modify options hash as it's frozen by default. - self.options = options.merge(skip_sprockets: true).freeze if options[:api] + self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze if options[:api] end public_task :set_default_accessors! -- cgit v1.2.3 From 1718683016c3d5487ed9ff5eca1377c295d638ed Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 15:27:04 -0300 Subject: Do not generate app/assets directory for api apps --- railties/lib/rails/generators/rails/app/app_generator.rb | 6 ++++++ railties/test/generators/api_app_generator_test.rb | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index d0e957037a..0c2d333b3f 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -259,6 +259,12 @@ module Rails build(:vendor) unless options[:api] end + def delete_app_assets_if_api_option + if options[:api] + remove_dir 'app/assets' + end + end + def delete_js_folder_skipping_javascript if options[:skip_javascript] remove_dir 'app/assets/javascripts' diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 3c0c8054a4..1f4d2c41b0 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -74,7 +74,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase end def skipped_files - %w(config/initializers/assets.rb + %w(app/assets + config/initializers/assets.rb config/initializers/cookies_serializer.rb config/initializers/session_store.rb vendor/assets -- cgit v1.2.3 From 9b66071a3e81b1db10b969010db96f6d5434472b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 15:28:52 -0300 Subject: Do not generate app/helpers directory for api apps --- railties/lib/rails/generators/rails/app/app_generator.rb | 6 ++++++ railties/test/generators/api_app_generator_test.rb | 1 + 2 files changed, 7 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 0c2d333b3f..f6ac0107e1 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -265,6 +265,12 @@ module Rails end end + def delete_app_helpers_if_api_option + if options[:api] + remove_dir 'app/helpers' + end + end + def delete_js_folder_skipping_javascript if options[:skip_javascript] remove_dir 'app/assets/javascripts' diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 1f4d2c41b0..70b8bde3d5 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -75,6 +75,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase def skipped_files %w(app/assets + app/helpers config/initializers/assets.rb config/initializers/cookies_serializer.rb config/initializers/session_store.rb -- cgit v1.2.3 From 3847e485b6feddf1bcfdb856238f0bd096001193 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 15:31:46 -0300 Subject: Do not generate app/views directory for api apps --- railties/lib/rails/generators/rails/app/app_generator.rb | 6 ++++++ railties/test/generators/api_app_generator_test.rb | 1 + 2 files changed, 7 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index f6ac0107e1..e7543d7bbf 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -271,6 +271,12 @@ module Rails end end + def delete_app_views_if_api_option + if options[:api] + remove_dir 'app/views' + end + end + def delete_js_folder_skipping_javascript if options[:skip_javascript] remove_dir 'app/assets/javascripts' diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 70b8bde3d5..d19034cf4a 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -76,6 +76,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase def skipped_files %w(app/assets app/helpers + app/views config/initializers/assets.rb config/initializers/cookies_serializer.rb config/initializers/session_store.rb -- cgit v1.2.3 From 7b47e4250df86f6ecf509b2ae1a151762676c4ed Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 15:33:15 -0300 Subject: Do not generate test/helpers directory for api apps --- railties/lib/rails/generators/rails/app/app_generator.rb | 1 + railties/test/generators/api_app_generator_test.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index e7543d7bbf..bd096d3c16 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -268,6 +268,7 @@ module Rails def delete_app_helpers_if_api_option if options[:api] remove_dir 'app/helpers' + remove_dir 'test/helpers' end end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index d19034cf4a..3eb9815b75 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -81,6 +81,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase config/initializers/cookies_serializer.rb config/initializers/session_store.rb vendor/assets + test/helpers tmp/cache/assets) end end -- cgit v1.2.3 From 101df203eb8e96a398792a3e3308e93c2fd96a47 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 20:33:43 -0300 Subject: Add api_only option to Generators --- railties/lib/rails/configuration.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 76364cea8f..d99d27a756 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -74,7 +74,7 @@ module Rails end class Generators #:nodoc: - attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging + attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging, :api_only attr_reader :hidden_namespaces def initialize @@ -83,6 +83,7 @@ module Rails @fallbacks = {} @templates = [] @colorize_logging = true + @api_only = false @hidden_namespaces = [] end -- cgit v1.2.3 From c19035299a314380285addf6f269591dc648d5cf Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 17 Apr 2015 20:34:21 -0300 Subject: Hide assets, helper, css and js namespaces for api only apps --- railties/lib/rails/generators.rb | 5 +++++ railties/test/application/generators_test.rb | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 96742dd2e6..b2f227595d 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -65,6 +65,7 @@ module Rails } def self.configure!(config) #:nodoc: + api_only! if config.api_only no_color! unless config.colorize_logging aliases.deep_merge! config.aliases options.deep_merge! config.options @@ -102,6 +103,10 @@ module Rails @fallbacks ||= {} end + def self.api_only! + hide_namespaces "assets", "helper", "css", "js" + end + # Remove the color from output. def self.no_color! Thor::Base.shell = Thor::Shell::Basic diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 78ada58ec8..5db4638685 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -125,5 +125,20 @@ module ApplicationTests assert_equal expected, c.generators.options end end + + test "api only generators hide assets, helper, js and css namespaces" do + add_to_config <<-RUBY + config.generators.api_only = true + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert Rails::Generators.hidden_namespaces.include?("assets") + assert Rails::Generators.hidden_namespaces.include?("helper") + assert Rails::Generators.hidden_namespaces.include?("js") + assert Rails::Generators.hidden_namespaces.include?("css") + end end end -- cgit v1.2.3 From e8100fc4e306818d5b9a5d2bfd75dcf3f44553b4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 18 Apr 2015 04:52:13 -0300 Subject: Api apps scaffold generates routes without new and edit actions --- .../generators/rails/resource_route/resource_route_generator.rb | 6 +++++- railties/test/generators/resource_generator_test.rb | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index c986f95e67..17d22768ed 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,6 +1,8 @@ module Rails module Generators class ResourceRouteGenerator < NamedBase # :nodoc: + class_option :api, type: :boolean, + desc: "Preconfigure smaller stack for API only apps" # Properly nests namespaces passed into a generator # @@ -22,7 +24,9 @@ module Rails end # inserts the primary resource - write("resources :#{file_name.pluralize}", route_length + 1) + resources = "resources :#{file_name.pluralize}" + resources << ", except: [:new, :edit]" if options.api? + write(resources, route_length + 1) # ends blocks regular_class_path.each_index do |index| diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index 581d80d60e..f73169c6fb 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -85,4 +85,10 @@ class ResourceGeneratorTest < Rails::Generators::TestCase assert_no_match(/resources :accounts$/, route) end end + + def test_api_only_does_not_generate_edit_route + run_generator ["Account", "--api"] + + assert_file "config/routes.rb", /resources :accounts, except: \[:new, :edit\]$/ + end end -- cgit v1.2.3 From 20939b3fcc3e34850e3c4b47327eff7ccaaa8eba Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 18 Apr 2015 19:53:40 -0400 Subject: config.generators.api_only = true set rails api option on generators --- railties/lib/rails/generators.rb | 4 ++++ railties/test/application/generators_test.rb | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index b2f227595d..8239c9bd9e 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -105,6 +105,10 @@ module Rails def self.api_only! hide_namespaces "assets", "helper", "css", "js" + + options[:rails].merge!( + api: true + ) end # Remove the color from output. diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 5db4638685..def57f360b 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -126,7 +126,7 @@ module ApplicationTests end end - test "api only generators hide assets, helper, js and css namespaces" do + test "api only generators hide assets, helper, js and css namespaces and set api option" do add_to_config <<-RUBY config.generators.api_only = true RUBY @@ -139,6 +139,7 @@ module ApplicationTests assert Rails::Generators.hidden_namespaces.include?("helper") assert Rails::Generators.hidden_namespaces.include?("js") assert Rails::Generators.hidden_namespaces.include?("css") + assert Rails::Generators.options[:rails][:api] end end end -- cgit v1.2.3 From d4fe23c76b3a3307830a19be28aea177e4b96987 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 19 Apr 2015 18:22:14 -0400 Subject: API apps scaffold generator generates an apropriate controller --- .../scaffold_controller_generator.rb | 5 +- .../templates/api_controller.rb | 63 ++++++++++++++++++++++ .../test_unit/scaffold/scaffold_generator.rb | 6 ++- .../scaffold/templates/api_functional_test.rb | 41 ++++++++++++++ railties/test/application/rake_test.rb | 20 +++++++ .../scaffold_controller_generator_test.rb | 47 ++++++++++++++++ 6 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb create mode 100644 railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index c01b82884d..cd0f9c7eef 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -10,11 +10,14 @@ module Rails class_option :helper, type: :boolean class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" + class_option :api, type: :boolean, + desc: "Preconfigure smaller stack for API only apps" argument :attributes, type: :array, default: [], banner: "field:type field:type" def create_controller_files - template "controller.rb", File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") + template_file = options.api? ? "api_controller.rb" : "controller.rb" + template template_file, File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") end hook_for :template_engine, :test_framework, as: :scaffold diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb new file mode 100644 index 0000000000..ca76da6530 --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb @@ -0,0 +1,63 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_file_path %>/application_controller" + +<% end -%> +<% module_namespacing do -%> +class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: [:show, :update, :destroy] + + # GET <%= route_url %> + def index + @<%= plural_table_name %> = <%= orm_class.all(class_name) %> + + render json: <%= "@#{plural_table_name}" %> + end + + # GET <%= route_url %>/1 + def show + render json: <%= "@#{singular_table_name}" %> + end + + # POST <%= route_url %> + def create + @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> + + if @<%= orm_instance.save %> + render json: <%= "@#{singular_table_name}" %>, status: :created, location: <%= "@#{singular_table_name}" %> + else + render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity + end + end + + # PATCH/PUT <%= route_url %>/1 + def update + if @<%= orm_instance.update("#{singular_table_name}_params") %> + head :no_content + else + render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity + end + end + + # DELETE <%= route_url %>/1 + def destroy + @<%= orm_instance.destroy %> + + head :no_content + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_<%= singular_table_name %> + @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> + end + + # Only allow a trusted parameter "white list" through. + def <%= "#{singular_table_name}_params" %> + <%- if attributes_names.empty? -%> + params[:<%= singular_table_name %>] + <%- else -%> + params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + <%- end -%> + end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index c36a64db31..31be3180e8 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -8,10 +8,14 @@ module TestUnit # :nodoc: check_class_collision suffix: "ControllerTest" + class_option :api, type: :boolean, + desc: "Preconfigure smaller stack for API only apps" + argument :attributes, type: :array, default: [], banner: "field:type field:type" def create_test_files - template "functional_test.rb", + template_file = options.api? ? "api_functional_test.rb" : "functional_test.rb" + template template_file, File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb") end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb new file mode 100644 index 0000000000..423437bba1 --- /dev/null +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= controller_class_name %>ControllerTest < ActionController::TestCase + setup do + @<%= singular_table_name %> = <%= table_name %>(:one) + end + + test "should get index" do + get :index + assert_response :success + assert_not_nil assigns(:<%= table_name %>) + end + + test "should create <%= singular_table_name %>" do + assert_difference('<%= class_name %>.count') do + post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + end + + assert_response 201 + end + + test "should show <%= singular_table_name %>" do + get :show, params: { id: <%= "@#{singular_table_name}" %> } + assert_response :success + end + + test "should update <%= singular_table_name %>" do + patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + assert_response 204 + end + + test "should destroy <%= singular_table_name %>" do + assert_difference('<%= class_name %>.count', -1) do + delete :destroy, params: { id: <%= "@#{singular_table_name}" %> } + end + + assert_response 204 + end +end +<% end -%> diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index dd26ec867d..3682c6c9d5 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -194,6 +194,26 @@ module ApplicationTests assert_no_match(/Errors running/, output) end + def test_api_scaffold_tests_pass_by_default + add_to_config <<-RUBY + config.api_only = true + config.generators.api_only = true + RUBY + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::API + end + RUBY + + output = Dir.chdir(app_path) do + `rails generate scaffold user username:string password:string; + bundle exec rake db:migrate test` + end + + assert_match(/5 runs, 8 assertions, 0 failures, 0 errors/, output) + assert_no_match(/Errors running/, output) + end + def test_scaffold_with_references_columns_tests_pass_when_belongs_to_is_optional app_file "config/initializers/active_record_belongs_to_required_by_default.rb", "Rails.application.config.active_record.belongs_to_required_by_default = false" diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 7c282377d7..5dae36b65e 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -185,4 +185,51 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`) end end + + def test_api_only_generates_a_proper_api_controller + run_generator ["User", "--api"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/class UsersController < ApplicationController/, content) + assert_no_match(/respond_to/, content) + + assert_match(/before_action :set_user, only: \[:show, :update, :destroy\]/, content) + + assert_instance_method :index, content do |m| + assert_match(/@users = User\.all/, m) + assert_match(/render json: @users/, m) + end + + assert_instance_method :show, content do |m| + assert_match(/render json: @user/, m) + end + + assert_instance_method :create, content do |m| + assert_match(/@user = User\.new\(user_params\)/, m) + assert_match(/@user\.save/, m) + assert_match(/@user\.errors/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@user\.update\(user_params\)/, m) + assert_match(/@user\.errors/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@user\.destroy/, m) + end + end + end + + def test_api_controller_tests + run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}", "--api"] + + assert_file "test/controllers/users_controller_test.rb" do |content| + assert_match(/class UsersControllerTest < ActionController::TestCase/, content) + assert_match(/test "should get index"/, content) + assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) + assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) + assert_no_match(/assert_redirected_to/, content) + end + end end -- cgit v1.2.3 From 451b1e35746fc1f7089200ac20811080089ef192 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 19 Apr 2015 20:16:57 -0400 Subject: Add api scaffold test for route, controller and its tests --- .../test/generators/scaffold_generator_test.rb | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 8d7fea8741..bb08b92126 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -87,6 +87,61 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/stylesheets/product_lines.css" end + def test_api_scaffold_on_invoke + run_generator %w(product_line title:string product:belongs_to user:references --api) + + # Model + assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ + assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/product_lines.yml" + assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/ + assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/ + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/resources :product_lines, except: \[:new, :edit\]$/, route) + end + + # Controller + assert_file "app/controllers/product_lines_controller.rb" do |content| + assert_match(/class ProductLinesController < ApplicationController/, content) + assert_no_match(/respond_to/, content) + + assert_match(/before_action :set_product_line, only: \[:show, :update, :destroy\]/, content) + + assert_instance_method :index, content do |m| + assert_match(/@product_lines = ProductLine\.all/, m) + assert_match(/render json: @product_lines/, m) + end + + assert_instance_method :show, content do |m| + assert_match(/render json: @product_line/, m) + end + + assert_instance_method :create, content do |m| + assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m) + assert_match(/@product_line\.save/, m) + assert_match(/@product_line\.errors/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@product_line\.update\(product_line_params\)/, m) + assert_match(/@product_line\.errors/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@product_line\.destroy/, m) + end + end + + assert_file "test/controllers/product_lines_controller_test.rb" do |test| + assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) + assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_no_match(/assert_redirected_to/, test) + end + end + def test_functional_tests_without_attributes run_generator ["product_line"] -- cgit v1.2.3 From 6d2b405a4e4f6e076dfda11438efc5d21b1ff2a2 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 19 Apr 2015 20:23:19 -0400 Subject: Api apps scaffold does not generate views --- railties/lib/rails/generators.rb | 3 ++- railties/test/application/generators_test.rb | 1 + railties/test/generators/scaffold_generator_test.rb | 9 ++++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 8239c9bd9e..0f99df4f57 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -107,7 +107,8 @@ module Rails hide_namespaces "assets", "helper", "css", "js" options[:rails].merge!( - api: true + api: true, + template_engine: nil ) end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index def57f360b..0cca7a50b1 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -140,6 +140,7 @@ module ApplicationTests assert Rails::Generators.hidden_namespaces.include?("js") assert Rails::Generators.hidden_namespaces.include?("css") assert Rails::Generators.options[:rails][:api] + assert_nil Rails::Generators.options[:rails][:template_engine] end end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index bb08b92126..fed2982abd 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -88,7 +88,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end def test_api_scaffold_on_invoke - run_generator %w(product_line title:string product:belongs_to user:references --api) + run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine) # Model assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ @@ -140,6 +140,13 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) assert_no_match(/assert_redirected_to/, test) end + + # Views + assert_no_file "app/views/layouts/product_lines.html.erb" + + %w(index show new edit _form).each do |view| + assert_no_file "app/views/product_lines/#{view}.html.erb" + end end def test_functional_tests_without_attributes -- cgit v1.2.3 From e5b6188b47c811c94b7626a8ec42d946367b3c03 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 19 Apr 2015 21:17:24 -0400 Subject: Api apps scaffold does not generate helpers --- railties/lib/rails/generators.rb | 1 + railties/test/application/generators_test.rb | 1 + railties/test/generators/scaffold_generator_test.rb | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 0f99df4f57..9ff0e47aea 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -108,6 +108,7 @@ module Rails options[:rails].merge!( api: true, + helper: false, template_engine: nil ) end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 0cca7a50b1..6cb2bfd77b 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -140,6 +140,7 @@ module ApplicationTests assert Rails::Generators.hidden_namespaces.include?("js") assert Rails::Generators.hidden_namespaces.include?("css") assert Rails::Generators.options[:rails][:api] + assert_equal false, Rails::Generators.options[:rails][:helper] assert_nil Rails::Generators.options[:rails][:template_engine] end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index fed2982abd..05a5c926eb 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -88,7 +88,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end def test_api_scaffold_on_invoke - run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine) + run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper) # Model assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ @@ -147,6 +147,9 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase %w(index show new edit _form).each do |view| assert_no_file "app/views/product_lines/#{view}.html.erb" end + + # Helpers + assert_no_file "app/helpers/product_lines_helper.rb" end def test_functional_tests_without_attributes -- cgit v1.2.3 From 94fdba9c92b233d3f5e9314266aabe51b51bb3e4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 19 Apr 2015 21:15:39 -0400 Subject: Api apps scaffold does not generate assets --- railties/lib/rails/generators.rb | 1 + railties/test/application/generators_test.rb | 1 + railties/test/generators/scaffold_generator_test.rb | 7 ++++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 9ff0e47aea..68d2a121fd 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -108,6 +108,7 @@ module Rails options[:rails].merge!( api: true, + assets: false, helper: false, template_engine: nil ) diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 6cb2bfd77b..36298f2385 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -140,6 +140,7 @@ module ApplicationTests assert Rails::Generators.hidden_namespaces.include?("js") assert Rails::Generators.hidden_namespaces.include?("css") assert Rails::Generators.options[:rails][:api] + assert_equal false, Rails::Generators.options[:rails][:assets] assert_equal false, Rails::Generators.options[:rails][:helper] assert_nil Rails::Generators.options[:rails][:template_engine] end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 05a5c926eb..775f4b4ff5 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -88,7 +88,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end def test_api_scaffold_on_invoke - run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper) + run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper --no-assets) # Model assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ @@ -150,6 +150,11 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Helpers assert_no_file "app/helpers/product_lines_helper.rb" + + # Assets + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/product_lines.js" + assert_no_file "app/assets/stylesheets/product_lines.css" end def test_functional_tests_without_attributes -- cgit v1.2.3 From decc4e8f82c11c71068aa513db7f17268650f7c3 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 19 Apr 2015 21:29:58 -0400 Subject: Do not generate lib/assets directory for api apps --- railties/lib/rails/generators/rails/app/app_generator.rb | 1 + railties/test/generators/api_app_generator_test.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index bd096d3c16..4ad78137f0 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -262,6 +262,7 @@ module Rails def delete_app_assets_if_api_option if options[:api] remove_dir 'app/assets' + remove_dir 'lib/assets' end end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 3eb9815b75..1aa3884466 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -62,7 +62,6 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase db lib lib/tasks - lib/assets log test/fixtures test/controllers @@ -80,6 +79,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase config/initializers/assets.rb config/initializers/cookies_serializer.rb config/initializers/session_store.rb + lib/assets vendor/assets test/helpers tmp/cache/assets) -- cgit v1.2.3 From 98a9936228f8a9485131b58a728aaae5ed2dde42 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 20 Apr 2015 12:28:23 -0400 Subject: config.api_only = true implies config.generators.api_only = true --- railties/lib/rails/application/configuration.rb | 9 +++++++-- railties/test/application/generators_test.rb | 2 +- railties/test/application/rake_test.rb | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 6ffbb1b204..4fc7a1db62 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -13,10 +13,10 @@ module Rails :railties_order, :relative_url_root, :secret_key_base, :secret_token, :serve_static_files, :ssl_options, :static_cache_control, :static_index, :session_options, :time_zone, :reload_classes_only_on_change, - :beginning_of_week, :filter_redirect, :api_only, :x + :beginning_of_week, :filter_redirect, :x attr_writer :log_level - attr_reader :encoding + attr_reader :encoding, :api_only def initialize(*) super @@ -61,6 +61,11 @@ module Rails end end + def api_only=(value) + @api_only = value + generators.api_only = value + end + def paths @paths ||= begin paths = super diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 36298f2385..ed8daa52fc 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -128,7 +128,7 @@ module ApplicationTests test "api only generators hide assets, helper, js and css namespaces and set api option" do add_to_config <<-RUBY - config.generators.api_only = true + config.api_only = true RUBY # Initialize the application diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 3682c6c9d5..a839a0b9b6 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -197,7 +197,6 @@ module ApplicationTests def test_api_scaffold_tests_pass_by_default add_to_config <<-RUBY config.api_only = true - config.generators.api_only = true RUBY app_file "app/controllers/application_controller.rb", <<-RUBY -- cgit v1.2.3 From e9e94c1d1540ebca77125f0f6beb6fdec2201242 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 20 Apr 2015 12:35:08 -0400 Subject: Add config.api_only = true to config/application.rb when using rails new --api --- .../lib/rails/generators/rails/app/templates/config/application.rb | 7 +++++++ railties/test/generators/api_app_generator_test.rb | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index a2661bfb51..6b7d7abd0b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -32,5 +32,12 @@ module <%= app_const_base %> # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de +<%- if options[:api] -%> + + # Only loads a smaller set of middleware suitable for API only apps. + # Middleware like session, flash, cookies can be added back manually. + # Skip views, helpers and assets when generating a new resource. + config.api_only = true +<%- end -%> end end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 1aa3884466..ff70729110 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -39,6 +39,10 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_no_match(/gem 'sass-rails'/, content) end + assert_file "config/application.rb" do |content| + assert_match(/config.api_only = true/, content) + end + assert_file "config/initializers/wrap_parameters.rb" do |content| assert_no_match(/wrap_parameters/, content) end -- cgit v1.2.3 From 7d17269be3ff1427c63b645f50a07c012cad6e4b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 20 Apr 2015 12:45:46 -0400 Subject: Add test to show api only apps allow overriding generator options --- railties/test/application/generators_test.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index ed8daa52fc..84cc6e120b 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -144,5 +144,21 @@ module ApplicationTests assert_equal false, Rails::Generators.options[:rails][:helper] assert_nil Rails::Generators.options[:rails][:template_engine] end + + test "api only generators allow overriding generator options" do + add_to_config <<-RUBY + config.generators.helper = true + config.api_only = true + config.generators.template_engine = :my_template + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert Rails::Generators.options[:rails][:api] + assert Rails::Generators.options[:rails][:helper] + assert_equal :my_template, Rails::Generators.options[:rails][:template_engine] + end end end -- cgit v1.2.3 From 3d3730040dba728912a42cd6a571042833f93cee Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 20 Apr 2015 20:12:43 -0400 Subject: Disable jbuilder for Rails API apps, meanwhile it doesn't play nicely --- railties/lib/rails/generators/app_base.rb | 2 ++ railties/test/generators/api_app_generator_test.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c02b39d203..249fe96772 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -266,6 +266,8 @@ module Rails end def jbuilder_gemfile_entry + return [] if options[:api] + comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder' GemfileEntry.version('jbuilder', '~> 2.0', comment) end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index ff70729110..fc4c101309 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -37,6 +37,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_no_match(/gem 'coffee-rails'/, content) assert_no_match(/gem 'jquery-rails'/, content) assert_no_match(/gem 'sass-rails'/, content) + assert_no_match(/gem 'jbuilder'/, content) end assert_file "config/application.rb" do |content| -- cgit v1.2.3 From 4c1b437ed7dea3660065c03e5e056a8aac700e21 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 21 Apr 2015 16:15:45 -0400 Subject: Use nex hash syntax on tests --- actionpack/test/controller/api/conditional_get_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb index e729ea9d33..3863e2a45d 100644 --- a/actionpack/test/controller/api/conditional_get_test.rb +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -3,22 +3,22 @@ require 'active_support/core_ext/integer/time' require 'active_support/core_ext/numeric/time' class ConditionalGetApiController < ActionController::API - before_action :handle_last_modified_and_etags, :only => :two + before_action :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!" + if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123]) + render text: "Hi!" end end def two - render :text => "Hi!" + render text: "Hi!" end private def handle_last_modified_and_etags - fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) + fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ]) end end -- cgit v1.2.3 From 4204778f8d26582c77fe6c1ff41d39fc523e50c0 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 21 Apr 2015 16:16:02 -0400 Subject: Adhere to Rails convention for private indentation --- .../middleware/api_public_exceptions.rb | 41 +++++++++++----------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb index 126842672d..00c7a9622a 100644 --- a/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb @@ -17,31 +17,30 @@ module ActionDispatch end private - - def render(status, content_type, body) - format = content_type && "to_#{content_type.to_sym}" - if format && body.respond_to?(format) - render_format(status, content_type, body.public_send(format)) - else - render_html(status) + def render(status, content_type, body) + format = content_type && "to_#{content_type.to_sym}" + if format && body.respond_to?(format) + render_format(status, content_type, body.public_send(format)) + else + render_html(status) + end end - end - def render_format(status, content_type, body) - [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", - 'Content-Length' => body.bytesize.to_s}, [body]] - end + def render_format(status, content_type, body) + [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", + 'Content-Length' => body.bytesize.to_s}, [body]] + end - def render_html(status) - found = false - path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale - path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path)) + def render_html(status) + found = false + path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale + path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path)) - if found || File.exist?(path) - render_format(status, 'text/html', File.read(path)) - else - [404, { "X-Cascade" => "pass" }, []] + if found || File.exist?(path) + render_format(status, 'text/html', File.read(path)) + else + [404, { "X-Cascade" => "pass" }, []] + end end - end end end -- cgit v1.2.3 From 38818c93a9959118a87bbdeda16e0766adc67598 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 21 Apr 2015 16:35:18 -0400 Subject: Remove api_rendering is not needed --- actionpack/lib/action_controller.rb | 4 ---- actionpack/lib/action_controller/api.rb | 2 +- actionpack/lib/action_controller/api/api_rendering.rb | 14 -------------- 3 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 actionpack/lib/action_controller/api/api_rendering.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 952a035a16..f4c9cfd190 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -42,10 +42,6 @@ module ActionController autoload :UrlFor end - autoload_under "api" do - autoload :ApiRendering - end - autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index f9cbbf227f..2b31ed2cbe 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -127,7 +127,7 @@ module ActionController UrlFor, Redirecting, - ApiRendering, + Rendering, Renderers::All, ConditionalGet, RackDelegation, diff --git a/actionpack/lib/action_controller/api/api_rendering.rb b/actionpack/lib/action_controller/api/api_rendering.rb deleted file mode 100644 index 3a08d28c39..0000000000 --- a/actionpack/lib/action_controller/api/api_rendering.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActionController - module ApiRendering - extend ActiveSupport::Concern - - included do - include Rendering - end - - def render_to_body(options = {}) - _process_options(options) - super - end - end -end -- cgit v1.2.3 From e7b89f10813e16936f305b0cc5456c7e37e8ee9a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 21 Apr 2015 16:45:43 -0400 Subject: Remove Unneeded ApiPublicExceptions middleware, PublicExceptions already does the work --- actionpack/lib/action_dispatch.rb | 1 - .../middleware/api_public_exceptions.rb | 46 ---------------------- actionpack/test/dispatch/show_exceptions_test.rb | 37 ----------------- 3 files changed, 84 deletions(-) delete mode 100644 actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 74ddf2954a..dcd3ee0644 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -53,7 +53,6 @@ module ActionDispatch autoload :ExceptionWrapper autoload :Flash autoload :ParamsParser - autoload :ApiPublicExceptions autoload :PublicExceptions autoload :Reloader autoload :RemoteIp diff --git a/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb deleted file mode 100644 index 00c7a9622a..0000000000 --- a/actionpack/lib/action_dispatch/middleware/api_public_exceptions.rb +++ /dev/null @@ -1,46 +0,0 @@ -module ActionDispatch - class ApiPublicExceptions - attr_accessor :public_path - - def initialize(public_path) - @public_path = public_path - end - - def call(env) - exception = env["action_dispatch.exception"] - status = env["PATH_INFO"][1..-1] - request = ActionDispatch::Request.new(env) - content_type = request.formats.first - body = { :status => status, :error => exception.message } - - render(status, content_type, body) - end - - private - def render(status, content_type, body) - format = content_type && "to_#{content_type.to_sym}" - if format && body.respond_to?(format) - render_format(status, content_type, body.public_send(format)) - else - render_html(status) - end - end - - def render_format(status, content_type, body) - [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}", - 'Content-Length' => body.bytesize.to_s}, [body]] - end - - def render_html(status) - found = false - path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale - path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path)) - - if found || File.exist?(path) - render_format(status, 'text/html', File.read(path)) - else - [404, { "X-Cascade" => "pass" }, []] - end - end - end -end diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index c70bc5f95e..a7cd56f263 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -23,7 +23,6 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) - ProductionApiApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::ApiPublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) test "skip exceptions app if not showing exceptions" do @app = ProductionApp @@ -56,42 +55,6 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest assert_equal "", body end - test "rescue api apps with json response" do - @app = ProductionApiApp - - get "/", headers: { 'HTTP_ACCEPT' => 'application/json', 'action_dispatch.show_exceptions' => true } - assert_response 500 - assert_equal({ :status => '500', :error => 'puke!' }.to_json, body) - - get "/method_not_allowed", headers: { 'HTTP_ACCEPT' => 'application/json', 'action_dispatch.show_exceptions' => true } - assert_response 405 - assert_equal({ :status => '405', :error => 'Only PUT requests are allowed.' }.to_json, body) - - get "/unknown_http_method", headers: { 'HTTP_ACCEPT' => 'application/json', 'action_dispatch.show_exceptions' => true } - assert_response 405 - assert_equal({ :status => '405', :error => 'ActionController::UnknownHttpMethod' }.to_json, body) - end - - test "rescue api apps unknown content-type requests with html response" do - @app = ProductionApiApp - - get "/", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } - assert_response 500 - assert_equal "500 error fixture\n", body - - get "/bad_params", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } - assert_response 400 - assert_equal "400 error fixture\n", body - - get "/not_found", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } - assert_response 404 - assert_equal "404 error fixture\n", body - - get "/unknown_http_method", headers: { 'HTTP_ACCEPT' => 'application/x-custom', 'action_dispatch.show_exceptions' => true } - assert_response 405 - assert_equal("", body) - end - test "localize rescue error page" do old_locale, I18n.locale = I18n.locale, :da -- cgit v1.2.3 From b643d7a7c6584a07624d2169a6cb54b38d345958 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 22 Apr 2015 15:45:24 -0400 Subject: Refactor internal? to query internal_controller? and internal_asset? methods --- actionpack/lib/action_dispatch/routing/inspector.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 48c10a7d4c..aa507f59c7 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -45,12 +45,22 @@ module ActionDispatch end def internal? - controller.to_s =~ %r{\Arails/(info|mailers|welcome)} + internal_controller? || internal_asset? end def engine? rack_app.respond_to?(:routes) end + + private + def internal_controller? + controller.to_s =~ %r{\arails/(info|mailers|welcome)} + end + + def internal_asset? + Rails.application.config.respond_to?(:assets) && + path =~ %r{\a#{Rails.application.config.assets.prefix}\z} + end end ## -- cgit v1.2.3 From 440b334cbb0b803994967972547239d73dba1fce Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 22 Apr 2015 15:57:35 -0400 Subject: Use new hash syntax --- actionpack/test/controller/api/redirect_to_test.rb | 2 +- actionpack/test/controller/api/renderers_test.rb | 12 ++++++------ railties/test/application/initializers/frameworks_test.rb | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/actionpack/test/controller/api/redirect_to_test.rb b/actionpack/test/controller/api/redirect_to_test.rb index 67832d7dba..18877c4b3a 100644 --- a/actionpack/test/controller/api/redirect_to_test.rb +++ b/actionpack/test/controller/api/redirect_to_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' class RedirectToApiController < ActionController::API def one - redirect_to :action => "two" + redirect_to action: "two" end def two; end diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb index d99eb0cf13..9405538833 100644 --- a/actionpack/test/controller/api/renderers_test.rb +++ b/actionpack/test/controller/api/renderers_test.rb @@ -4,20 +4,20 @@ require 'active_support/core_ext/hash/conversions' class RenderersApiController < ActionController::API class Model def to_json(options = {}) - { :a => 'b' }.to_json(options) + { a: 'b' }.to_json(options) end def to_xml(options = {}) - { :a => 'b' }.to_xml(options) + { a: 'b' }.to_xml(options) end end def one - render :json => Model.new + render json: Model.new end def two - render :xml => Model.new + render xml: Model.new end end @@ -27,12 +27,12 @@ class RenderersApiTest < ActionController::TestCase def test_render_json get :one assert_response :success - assert_equal({ :a => 'b' }.to_json, @response.body) + 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) + assert_equal({ a: 'b' }.to_xml, @response.body) end end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 709286d728..af98e08d0e 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -140,7 +140,7 @@ module ApplicationTests app_file "app/controllers/omg_controller.rb", <<-RUBY class OmgController < ApplicationController def show - render :json => { :omg => 'omg' } + render json: { omg: 'omg' } end end RUBY -- cgit v1.2.3 From 099055de66ba400750d4cda7049c047aeb179ab7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 22 Apr 2015 16:00:38 -0400 Subject: Remove extra whitespaces --- actionpack/test/dispatch/show_exceptions_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index a7cd56f263..cbb12a2209 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -22,7 +22,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest end end - ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) + ProductionApp = ActionDispatch::ShowExceptions.new(Boomer.new, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public")) test "skip exceptions app if not showing exceptions" do @app = ProductionApp -- cgit v1.2.3 From 674dab30bc46d63875c533f4cd4daa2b62ad67c5 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Mon, 4 May 2015 17:04:15 -0300 Subject: Routes resources avoid :new and :edit endpoints if api_only is enabled --- actionpack/lib/action_dispatch/routing/mapper.rb | 25 ++++++-- .../lib/action_dispatch/routing/route_set.rb | 22 +++++-- actionpack/test/dispatch/routing_test.rb | 75 ++++++++++++++++++++++ .../resource_route/resource_route_generator.rb | 4 +- .../test/generators/resource_generator_test.rb | 6 -- 5 files changed, 111 insertions(+), 21 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 15980db39b..7cfe4693c1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1042,7 +1042,7 @@ module ActionDispatch class Resource #:nodoc: attr_reader :controller, :path, :options, :param - def initialize(entities, options = {}) + def initialize(entities, api_only = false, options = {}) @name = entities.to_s @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @@ -1050,10 +1050,15 @@ module ActionDispatch @param = (options[:param] || :id).to_sym @options = options @shallow = false + @api_only = api_only end def default_actions - [:index, :create, :new, :show, :update, :destroy, :edit] + if @api_only + [:index, :create, :show, :update, :destroy] + else + [:index, :create, :new, :show, :update, :destroy, :edit] + end end def actions @@ -1120,7 +1125,7 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - def initialize(entities, options) + def initialize(entities, api_only, options) super @as = nil @controller = (options[:controller] || plural).to_s @@ -1128,7 +1133,11 @@ module ActionDispatch end def default_actions - [:show, :create, :update, :destroy, :new, :edit] + if @api_only + [:show, :create, :update, :destroy] + else + [:show, :create, :update, :destroy, :new, :edit] + end end def plural @@ -1178,7 +1187,7 @@ module ActionDispatch return self end - resource_scope(:resource, SingletonResource.new(resources.pop, options)) do + resource_scope(:resource, SingletonResource.new(resources.pop, api_only?, options)) do yield if block_given? concerns(options[:concerns]) if options[:concerns] @@ -1336,7 +1345,7 @@ module ActionDispatch return self end - resource_scope(:resources, Resource.new(resources.pop, options)) do + resource_scope(:resources, Resource.new(resources.pop, api_only?, options)) do yield if block_given? concerns(options[:concerns]) if options[:concerns] @@ -1768,6 +1777,10 @@ module ActionDispatch delete :destroy if parent_resource.actions.include?(:destroy) end end + + def api_only? + @set.api_only? + end end # Routing Concerns allow you to declare common routes that can be reused diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index cf35e85b40..454593b59f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -319,17 +319,23 @@ module ActionDispatch end def self.new_with_config(config) + route_set_config = DEFAULT_CONFIG + + # engines apparently don't have this set if config.respond_to? :relative_url_root - new Config.new config.relative_url_root - else - # engines apparently don't have this set - new + route_set_config.relative_url_root = config.relative_url_root + end + + if config.respond_to? :api_only + route_set_config.api_only = config.api_only end + + new route_set_config end - Config = Struct.new :relative_url_root + Config = Struct.new :relative_url_root, :api_only - DEFAULT_CONFIG = Config.new(nil) + DEFAULT_CONFIG = Config.new(nil, false) def initialize(config = DEFAULT_CONFIG) self.named_routes = NamedRouteCollection.new @@ -352,6 +358,10 @@ module ActionDispatch @config.relative_url_root end + def api_only? + @config.api_only + end + def request_class ActionDispatch::Request end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 62c99a2edc..77f86b7a62 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -167,6 +167,46 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/session/reset', reset_session_path end + def test_session_singleton_resource_for_api_app + self.class.stub_controllers do |_| + config = ActionDispatch::Routing::RouteSet::Config.new + config.api_only = true + + routes = ActionDispatch::Routing::RouteSet.new(config) + + routes.draw do + resource :session do + get :create + post :reset + end + end + @app = RoutedRackApp.new routes + end + + get '/session' + assert_equal 'sessions#create', @response.body + assert_equal '/session', session_path + + post '/session' + assert_equal 'sessions#create', @response.body + + put '/session' + assert_equal 'sessions#update', @response.body + + delete '/session' + assert_equal 'sessions#destroy', @response.body + + post '/session/reset' + assert_equal 'sessions#reset', @response.body + assert_equal '/session/reset', reset_session_path + + get '/session/new' + assert_equal 'Not Found', @response.body + + get '/session/edit' + assert_equal 'Not Found', @response.body + end + def test_session_info_nested_singleton_resource draw do resource :session do @@ -509,6 +549,41 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/projects/1/edit', edit_project_path(:id => '1') end + def test_projects_for_api_app + self.class.stub_controllers do |_| + config = ActionDispatch::Routing::RouteSet::Config.new + config.api_only = true + + routes = ActionDispatch::Routing::RouteSet.new(config) + routes.draw do + resources :projects, controller: :project + end + @app = RoutedRackApp.new routes + end + + get '/projects' + assert_equal 'project#index', @response.body + assert_equal '/projects', projects_path + + post '/projects' + assert_equal 'project#create', @response.body + + get '/projects.xml' + assert_equal 'project#index', @response.body + assert_equal '/projects.xml', projects_path(format: 'xml') + + get '/projects/1' + assert_equal 'project#show', @response.body + assert_equal '/projects/1', project_path(id: '1') + + get '/projects/1.xml' + assert_equal 'project#show', @response.body + assert_equal '/projects/1.xml', project_path(id: '1', format: 'xml') + + get '/projects/1/edit' + assert_equal 'Not Found', @response.body + end + def test_projects_with_post_action_and_new_path_on_collection draw do resources :projects, :controller => :project do diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index 17d22768ed..7472e5f071 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -24,9 +24,7 @@ module Rails end # inserts the primary resource - resources = "resources :#{file_name.pluralize}" - resources << ", except: [:new, :edit]" if options.api? - write(resources, route_length + 1) + write("resources :#{file_name.pluralize}", route_length + 1) # ends blocks regular_class_path.each_index do |index| diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index f73169c6fb..581d80d60e 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -85,10 +85,4 @@ class ResourceGeneratorTest < Rails::Generators::TestCase assert_no_match(/resources :accounts$/, route) end end - - def test_api_only_does_not_generate_edit_route - run_generator ["Account", "--api"] - - assert_file "config/routes.rb", /resources :accounts, except: \[:new, :edit\]$/ - end end -- cgit v1.2.3 From e3808878c6dcea42a8bf050f28943a5dfffea4e7 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 5 May 2015 14:42:39 -0300 Subject: Exclude cache_digests:dependencies rake task in api app --- actionview/lib/action_view/railtie.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index 9a26cba574..5dc7950d6b 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -48,8 +48,10 @@ module ActionView end end - rake_tasks do - load "action_view/tasks/dependencies.rake" + rake_tasks do |app| + unless app.config.api_only + load "action_view/tasks/dependencies.rake" + end end end end -- cgit v1.2.3 From fd2508522c341c3f708219b5fc1834f24caf04e3 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 5 May 2015 14:50:41 -0300 Subject: Remove Compatibility module since we don't remember why it was added :smile: --- actionpack/lib/action_controller.rb | 1 - actionpack/lib/action_controller/api.rb | 19 ------------------- 2 files changed, 20 deletions(-) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index f4c9cfd190..f6bc5b951c 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -16,7 +16,6 @@ module ActionController autoload :FormBuilder autoload_under "metal" do - autoload :Compatibility autoload :ConditionalGet autoload :Cookies autoload :DataStreaming diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 2b31ed2cbe..ff199567b9 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -85,25 +85,6 @@ module ActionController class API < Metal abstract! - module Compatibility - def cache_store; end - def cache_store=(*); end - def assets_dir=(*); end - def javascripts_dir=(*); end - def stylesheets_dir=(*); end - def page_cache_directory=(*); end - def asset_path=(*); end - def asset_host=(*); end - def relative_url_root=(*); end - def perform_caching=(*); end - def helpers_path=(*); end - def allow_forgery_protection=(*); end - def helper_method(*); end - def helper(*); end - end - - extend Compatibility - # Shortcut helper that returns all the ActionController::API modules except the ones passed in the argument: # # class MetalController -- cgit v1.2.3 From fa11f10c948fde0a580acf4668849664c8ae95c6 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 5 May 2015 15:01:47 -0300 Subject: Api only apps should include tmp and vendor folders --- railties/lib/rails/generators/rails/app/app_generator.rb | 6 ++++-- railties/test/generators/api_app_generator_test.rb | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 4ad78137f0..6d35fc61f0 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -252,17 +252,19 @@ module Rails end def create_tmp_files - build(:tmp) unless options[:api] + build(:tmp) end def create_vendor_files - build(:vendor) unless options[:api] + build(:vendor) end def delete_app_assets_if_api_option if options[:api] remove_dir 'app/assets' remove_dir 'lib/assets' + remove_dir 'tmp/cache/assets' + remove_dir 'vendor/assets' end end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index fc4c101309..3d80af9323 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -72,6 +72,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase test/controllers test/integration test/models + tmp + vendor ) files.concat %w(bin/bundle bin/rails bin/rake) files -- cgit v1.2.3 From 2487bfb39a8ef5e18fd9cc8f971395139e7c727c Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 5 May 2015 15:08:21 -0300 Subject: Do not say that Api Controllers are faster than regular ones in docs --- actionpack/lib/action_controller/api.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index ff199567b9..cb819067da 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -5,14 +5,14 @@ require 'action_controller/log_subscriber' module ActionController # API Controller is a lightweight version of ActionController::Base, # created for applications that don't require all functionality that a complete - # \Rails controller provides, allowing you to create faster controllers for - # example for API only applications. + # \Rails controller provides, allowing you to create controllers with just the + # features that you need for API only applications. # # An API 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 + # flash, assets, and so on. This makes the entire controller stack thinner, + # 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 API Controller stack. # -- cgit v1.2.3 From 08cfe34174d5cb496d4a312858ca46431463b5a8 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 5 May 2015 15:15:15 -0300 Subject: Rename test methods in api conditional get controller tests --- actionpack/test/controller/api/conditional_get_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb index 3863e2a45d..d1eb27bf24 100644 --- a/actionpack/test/controller/api/conditional_get_test.rb +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -29,13 +29,13 @@ class ConditionalGetApiTest < ActionController::TestCase @last_modified = Time.now.utc.beginning_of_day.httpdate end - def test_request_with_bang_gets_last_modified + def test_request_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 + def test_request_obeys_last_modified @request.if_modified_since = @last_modified get :two assert_response :not_modified -- cgit v1.2.3 From 11c71b207e11e6a94b65f5e480845ecc3a06de0d Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 5 May 2015 17:18:17 -0300 Subject: Revert changes related with api apps in RouteWrapper See the following commit to have context about this change: https://github.com/rails/rails/commit/757a2bc3e3e52a5d9418656928db993db42b741b --- actionpack/lib/action_dispatch/routing/inspector.rb | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index aa507f59c7..48c10a7d4c 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -45,22 +45,12 @@ module ActionDispatch end def internal? - internal_controller? || internal_asset? + controller.to_s =~ %r{\Arails/(info|mailers|welcome)} end def engine? rack_app.respond_to?(:routes) end - - private - def internal_controller? - controller.to_s =~ %r{\arails/(info|mailers|welcome)} - end - - def internal_asset? - Rails.application.config.respond_to?(:assets) && - path =~ %r{\a#{Rails.application.config.assets.prefix}\z} - end end ## -- cgit v1.2.3 From 7db63f3d35577e457250ba68d7a77651b2feb362 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 13 May 2015 14:54:12 -0300 Subject: Fix MimeResponds example in AC::API documentation --- actionpack/lib/action_controller/api.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index cb819067da..9dc96bd3e6 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -71,11 +71,13 @@ module ActionController # end # # class PostsController < ApplicationController - # respond_to :json, :xml - # # def index # @posts = Post.all - # respond_with @posts + # + # respond_to do |format| + # format.json { render json: @posts } + # format.xml { render xml: @posts } + # end # end # end # -- cgit v1.2.3 From b6c270fb62a3ee7c89200084df7666347b787717 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 13 May 2015 16:28:25 -0300 Subject: Remove unneeded option from ResourceRouteGenerator --- .../rails/generators/rails/resource_route/resource_route_generator.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index 7472e5f071..42705107ae 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,9 +1,6 @@ module Rails module Generators class ResourceRouteGenerator < NamedBase # :nodoc: - class_option :api, type: :boolean, - desc: "Preconfigure smaller stack for API only apps" - # Properly nests namespaces passed into a generator # # $ rails generate resource admin/users/products -- cgit v1.2.3 From 846f35203db958b0730018e23c30d9feaee3aa27 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 13 May 2015 16:33:33 -0300 Subject: Fix class_option description for api generators --- .../rails/scaffold_controller/scaffold_controller_generator.rb | 2 +- railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index cd0f9c7eef..d0b8cad896 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -11,7 +11,7 @@ module Rails class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" class_option :api, type: :boolean, - desc: "Preconfigure smaller stack for API only apps" + desc: "Generates API controller" argument :attributes, type: :array, default: [], banner: "field:type field:type" diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index 31be3180e8..d634584beb 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -9,7 +9,7 @@ module TestUnit # :nodoc: check_class_collision suffix: "ControllerTest" class_option :api, type: :boolean, - desc: "Preconfigure smaller stack for API only apps" + desc: "Generates API functional tests" argument :attributes, type: :array, default: [], banner: "field:type field:type" -- cgit v1.2.3 From dc4c68aaaad8a89c7717930d06c4c6340314f3a2 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 14:47:53 -0300 Subject: Fix scaffold generator test for resource routes --- railties/test/generators/scaffold_generator_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 775f4b4ff5..3401b96d7d 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -99,7 +99,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Route assert_file "config/routes.rb" do |route| - assert_match(/resources :product_lines, except: \[:new, :edit\]$/, route) + assert_match(/resources :product_lines$/, route) end # Controller -- cgit v1.2.3 From 511c33a157464aa2b6476f26f66df6b7a1a4003e Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 17:17:41 -0300 Subject: Add AMS 0.10.0.rc1 by default for api apps --- railties/lib/rails/generators/rails/app/templates/Gemfile | 5 +++++ railties/test/generators/api_app_generator_test.rb | 1 + 2 files changed, 6 insertions(+) diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 29203b9c37..cf811503be 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -21,6 +21,11 @@ source 'https://rubygems.org' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +<%- if options.api? -%> +# Use ActiveModelSerializers to serialize JSON responses +gem 'active_model_serializers', '~> 0.10.0.rc1' + +<%- end -%> <% if RUBY_ENGINE == 'ruby' -%> group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 3d80af9323..16c0f1ae82 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -38,6 +38,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_no_match(/gem 'jquery-rails'/, content) assert_no_match(/gem 'sass-rails'/, content) assert_no_match(/gem 'jbuilder'/, content) + assert_match(/gem 'active_model_serializers'/, content) end assert_file "config/application.rb" do |content| -- cgit v1.2.3 From 03b576ee06ec5fe9f980dfa07c16cdbbadd7d6e2 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 18:16:08 -0300 Subject: http only => API only --- railties/lib/rails/generators/rails/app/app_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 6d35fc61f0..4b73313388 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -189,7 +189,7 @@ module Rails raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." end - # Force sprockets to be skipped when generating http only app. + # Force sprockets to be skipped when generating API only apps. # Can't modify options hash as it's frozen by default. self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze if options[:api] end -- cgit v1.2.3 From 37507d3b837308915748c05cca8b8d41c0b469f9 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 18:17:20 -0300 Subject: Document Generators.api_only! method --- railties/lib/rails/generators.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 68d2a121fd..b430cf1909 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -103,6 +103,10 @@ module Rails @fallbacks ||= {} end + # Configure generators for API only applications. It basically hides + # everything that is usually browser related, such as assets and session + # migration generators, and completely disable views, helpers and assets + # so generators such as scaffold won't create them. def self.api_only! hide_namespaces "assets", "helper", "css", "js" -- cgit v1.2.3 From b7494b66bc56dfd1a86ed8c7de0e7692a7db2831 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 18:30:14 -0300 Subject: Add API only apps guide --- guides/source/api_app.md | 435 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 guides/source/api_app.md diff --git a/guides/source/api_app.md b/guides/source/api_app.md new file mode 100644 index 0000000000..40aefbc0fe --- /dev/null +++ b/guides/source/api_app.md @@ -0,0 +1,435 @@ +Using Rails for API-only Apps +----------------------------- + +In this guide you will learn: + +- What Rails provides for API-only applications +- How to configure Rails to start without any browser features +- How to decide which middlewares you will want to include +- How to decide which modules to use in your controller + +endprologue. + +### What is an API app? + +Traditionally, when people said that they used Rails as an “API”, they +meant providing a programmatically accessible API alongside their web +application.\ +For example, GitHub provides [an API](http://developer.github.com) that +you can use from your own custom clients. + +With the advent of client-side frameworks, more developers are using +Rails to build a backend that is shared between their web application +and other native applications. + +For example, Twitter uses its [public API](https://dev.twitter.com) in +its web application, which is built as a static site that consumes JSON +resources. + +Instead of using Rails to generate dynamic HTML that will communicate +with the server through forms and links, many developers are treating +their web application as just another client, delivered as static HTML, +CSS and JavaScript, and consuming a simple JSON API + +This guide covers building a Rails application that serves JSON +resources to an API client **or** client-side framework. + +### Why use Rails for JSON APIs? + +The first question a lot of people have when thinking about building a +JSON API using Rails is: “isn’t using Rails to spit out some JSON +overkill? Shouldn’t I just use something like Sinatra?” + +For very simple APIs, this may be true. However, even in very HTML-heavy +applications, most of an application’s logic is actually outside of the +view layer. + +The reason most people use Rails is that it provides a set of defaults +that allows us to get up and running quickly without having to make a +lot of trivial decisions. + +Let’s take a look at some of the things that Rails provides out of the +box that are still applicable to API applications. + +Handled at the middleware layer: + +- Reloading: Rails applications support transparent reloading. This + works even if your application gets big and restarting the server + for every request becomes non-viable. +- Development Mode: Rails application come with smart defaults for + development, making development pleasant without compromising + production-time performance. +- Test Mode: Ditto test mode. +- Logging: Rails applications log every request, with a level of + verbosity appropriate for the current mode. Rails logs in + development include information about the request environment, + database queries, and basic performance information. +- Security: Rails detects and thwarts [IP spoofing + attacks](http://en.wikipedia.org/wiki/IP_address_spoofing) and + handles cryptographic signatures in a [timing + attack](http://en.wikipedia.org/wiki/Timing_attack) aware way. Don’t + know what an IP spoofing attack or a timing attack is? Exactly. +- Parameter Parsing: Want to specify your parameters as JSON instead + of as a URL-encoded String? No problem. Rails will decode the JSON + for you and make it available in *params*. Want to use nested + URL-encoded params? That works too. +- Conditional GETs: Rails handles conditional *GET*, (*ETag* and + *Last-Modified*), processing request headers and returning the + correct response headers and status code. All you need to do is use + the + [stale?](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) + check in your controller, and Rails will handle all of the HTTP + details for you. +- Caching: If you use *dirty?* with public cache control, Rails will + automatically cache your responses. You can easily configure the + cache store. +- HEAD requests: Rails will transparently convert *HEAD* requests into + *GET* requests, and return just the headers on the way out. This + makes *HEAD* work reliably in all Rails APIs. + +While you could obviously build these up in terms of existing Rack +middlewares, I think this list demonstrates that the default Rails +middleware stack provides a lot of value, even if you’re “just +generating JSON”. + +Handled at the ActionPack layer: + +- Resourceful Routing: If you’re building a RESTful JSON API, you want + to be using the Rails router. Clean and conventional mapping from + HTTP to controllers means not having to spend time thinking about + how to model your API in terms of HTTP. +- URL Generation: The flip side of routing is URL generation. A good + API based on HTTP includes URLs (see [the GitHub gist + API](http://developer.github.com/v3/gists/) for an example). +- Header and Redirection Responses: *head :no\_content* and + *redirect\_to user\_url(current\_user)* come in handy. Sure, you + could manually add the response headers, but why? +- Caching: Rails provides page, action and fragment caching. Fragment + caching is especially helpful when building up a nested JSON object. +- Basic, Digest and Token Authentication: Rails comes with + out-of-the-box support for three kinds of HTTP authentication. +- Instrumentation: Rails 3.0 added an instrumentation API that will + trigger registered handlers for a variety of events, such as action + processing, sending a file or data, redirection, and database + queries. The payload of each event comes with relevant information + (for the action processing event, the payload includes the + controller, action, params, request format, request method and the + request’s full path). +- Generators: This may be passé for advanced Rails users, but it can + be nice to generate a resource and get your model, controller, test + stubs, and routes created for you in a single command. +- Plugins: Many third-party libraries come with support for Rails that + reduces or eliminates the cost of setting up and gluing together the + library and the web framework. This includes things like overriding + default generators, adding rake tasks, and honoring Rails choices + (like the logger and cache backend). + +Of course, the Rails boot process also glues together all registered +components. For example, the Rails boot process is what uses your +*config/database.yml* file when configuring ActiveRecord. + +**The short version is**: you may not have thought about which parts of +Rails are still applicable even if you remove the view layer, but the +answer turns out to be “most of it”. + +### The Basic Configuration + +If you’re building a Rails application that will be an API server first +and foremost, you can start with a more limited subset of Rails and add +in features as needed. + +You can generate a new api Rails app: + +\ +\$ rails new my\_api —api\ + + +This will do three main things for you: + +- Configure your application to start with a more limited set of + middleware than normal. Specifically, it will not include any + middleware primarily useful for browser applications (like cookie + support) by default. +- Make *ApplicationController* inherit from *ActionController::API* + instead of *ActionController::Base*. As with middleware, this will + leave out any *ActionController* modules that provide functionality + primarily used by browser applications. +- Configure the generators to skip generating views, helpers and + assets when you generate a new resource. + +If you want to take an existing app and make it an API app, follow the +following steps. + +In *config/application.rb* add the following line at the top of the +*Application* class: + +\ +config.api\_only!\ + + +Change *app/controllers/application\_controller.rb*: + + + +1. instead of\ + class ApplicationController \< ActionController::Base\ + end + + + +1. do\ + class ApplicationController \< ActionController::API\ + end\ + + +### Choosing Middlewares + +An API application comes with the following middlewares by default. + +- *Rack::Cache*: Caches responses with public *Cache-Control* headers + using HTTP caching semantics. See below for more information. +- *Rack::Sendfile*: Uses a front-end server’s file serving support + from your Rails application. +- *Rack::Lock*: If your application is not marked as threadsafe + (*config.threadsafe!*), this middleware will add a mutex around your + requests. +- *ActionDispatch::RequestId*: +- *Rails::Rack::Logger*: +- *Rack::Runtime*: Adds a header to the response listing the total + runtime of the request. +- *ActionDispatch::ShowExceptions*: Rescue exceptions and re-dispatch + them to an exception handling application +- *ActionDispatch::DebugExceptions*: Log exceptions +- *ActionDispatch::RemoteIp*: Protect against IP spoofing attacks +- *ActionDispatch::Reloader*: In development mode, support code + reloading. +- *ActionDispatch::ParamsParser*: Parse XML, YAML and JSON parameters + when the request’s *Content-Type* is one of those. +- *ActionDispatch::Head*: Dispatch *HEAD* requests as *GET* requests, + and return only the status code and headers. +- *Rack::ConditionalGet*: Supports the *stale?* feature in Rails + controllers. +- *Rack::ETag*: Automatically set an *ETag* on all string responses. + This means that if the same response is returned from a controller + for the same URL, the server will return a *304 Not Modified*, even + if no additional caching steps are taken. This is primarily a + client-side optimization; it reduces bandwidth costs but not server + processing time. + +Other plugins, including *ActiveRecord*, may add additional middlewares. +In general, these middlewares are agnostic to the type of app you are +building, and make sense in an API-only Rails application. + +You can get a list of all middlewares in your application via: + +\ +\$ rake middleware\ + + +#### Using Rack::Cache + +When used with Rails, *Rack::Cache* uses the Rails cache store for its +entity and meta stores. This means that if you use memcache, for your +Rails app, for instance, the built-in HTTP cache will use memcache. + +To make use of *Rack::Cache*, you will want to use *stale?* in your +controller. Here’s an example of *stale?* in use. + +\ +def show\ + @post = Post.find(params[:id]) + +if stale?(:last\_modified =\> `post.updated_at) + render json: `post\ + end\ +end\ + + +The call to *stale?* will compare the *If-Modified-Since* header in the +request with *@post.updated\_at*. If the header is newer than the last +modified, this action will return a *304 Not Modified* response. +Otherwise, it will render the response and include a *Last-Modified* +header with the response. + +Normally, this mechanism is used on a per-client basis. *Rack::Cache* +allows us to share this caching mechanism across clients. We can enable +cross-client caching in the call to *stale?* + +\ +def show\ + @post = Post.find(params[:id]) + +if stale?(:last\_modified =\> `post.updated_at, :public => true) + render json: `post\ + end\ +end\ + + +This means that *Rack::Cache* will store off *Last-Modified* value for a +URL in the Rails cache, and add an *If-Modified-Since* header to any +subsequent inbound requests for the same URL. + +Think of it as page caching using HTTP semantics. + +NOTE: The *Rack::Cache* middleware is always outside of the *Rack::Lock* +mutex, even in single-threaded apps. + +#### Using Rack::Sendfile + +When you use the *send\_file* method in a Rails controller, it sets the +*X-Sendfile* header. *Rack::Sendfile* is responsible for actually +sending the file. + +If your front-end server supports accelerated file sending, +*Rack::Sendfile* will offload the actual file sending work to the +front-end server. + +You can configure the name of the header that your front-end server uses +for this purposes using *config.action\_dispatch.x\_sendfile\_header* in +the appropriate environment config file. + +You can learn more about how to use *Rack::Sendfile* with popular +front-ends in [the Rack::Sendfile +documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile) + +The values for popular servers once they are configured to support +accelerated file sending: + + + +1. Apache and lighttpd\ + config.action\_dispatch.x\_sendfile\_header = “X-Sendfile” + + + +1. nginx\ + config.action\_dispatch.x\_sendfile\_header = “X-Accel-Redirect”\ + + +Make sure to configure your server to support these options following +the instructions in the *Rack::Sendfile* documentation. + +NOTE: The *Rack::Sendfile* middleware is always outside of the +*Rack::Lock* mutex, even in single-threaded apps. + +#### Using ActionDispatch::ParamsParser + +*ActionDispatch::ParamsParser* will take parameters from the client in +JSON and make them available in your controller as *params*. + +To use this, your client will need to make a request with JSON-encoded +parameters and specify the *Content-Type* as *application/json*. + +Here’s an example in jQuery: + +\ +jQuery.ajax({\ + type: ‘POST’,\ + url: ‘/people’\ + dataType: ‘json’,\ + contentType: ‘application/json’,\ + data: JSON.stringify({ person: { firstName: “Yehuda”, lastName: “Katz” +} }), + +success: function(json) { }\ +});\ + + +*ActionDispatch::ParamsParser* will see the *Content-Type* and your +params will be *{ :person =\> { :firstName =\> “Yehuda”, :lastName =\> +“Katz” } }*. + +#### Other Middlewares + +Rails ships with a number of other middlewares that you might want to +use in an API app, especially if one of your API clients is the browser: + +- *Rack::MethodOverride*: Allows the use of the *\_method* hack to + route POST requests to other verbs. +- *ActionDispatch::Cookies*: Supports the *cookie* method in + *ActionController*, including support for signed and encrypted + cookies. +- *ActionDispatch::Flash*: Supports the *flash* mechanism in + *ActionController*. +- *ActionDispatch::BestStandards*: Tells Internet Explorer to use the + most standards-compliant available renderer. In production mode, if + ChromeFrame is available, use ChromeFrame. +- Session Management: If a *config.session\_store* is supplied, this + middleware makes the session available as the *session* method in + *ActionController*. + +Any of these middlewares can be adding via: + +\ +config.middleware.use Rack::MethodOverride\ + + +#### Removing Middlewares + +If you don’t want to use a middleware that is included by default in the +API-only middleware set, you can remove it using +*config.middleware.delete*: + +\ +config.middleware.delete ::Rack::Sendfile\ + + +Keep in mind that removing these features may remove support for certain +features in *ActionController*. + +### Choosing Controller Modules + +An API application (using *ActionController::API*) comes with the +following controller modules by default: + +- *ActionController::UrlFor*: Makes *url\_for* and friends available +- *ActionController::Redirecting*: Support for *redirect\_to* +- *ActionController::Rendering*: Basic support for rendering +- *ActionController::Renderers::All*: Support for *render :json* and + friends +- *ActionController::ConditionalGet*: Support for *stale?* +- *ActionController::ForceSSL*: Support for *force\_ssl* +- *ActionController::RackDelegation*: Support for the *request* and + *response* methods returning *ActionDispatch::Request* and + *ActionDispatch::Response* objects. +- *ActionController::DataStreaming*: Support for *send\_file* and + *send\_data* +- *AbstractController::Callbacks*: Support for *before\_filter* and + friends +- *ActionController::Instrumentation*: Support for the instrumentation + hooks defined by *ActionController* (see [the + source](https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/instrumentation.rb) + for more). +- *ActionController::Rescue*: Support for *rescue\_from*. + +Other plugins may add additional modules. You can get a list of all +modules included into *ActionController::API* in the rails console: + +\ +\$ irb\ +\>\> ActionController::API.ancestors - +ActionController::Metal.ancestors\ + + +#### Adding Other Modules + +All ActionController modules know about their dependent modules, so you +can feel free to include any modules into your controllers, and all +dependencies will be included and set up as well. + +Some common modules you might want to add: + +- *AbstractController::Translation*: Support for the *l* and *t* + localization and translation methods. These delegate to + *I18n.translate* and *I18n.localize*. +- *ActionController::HTTPAuthentication::Basic* (or *Digest* + or +Token): Support for basic, digest or token HTTP authentication. +- *AbstractController::Layouts*: Support for layouts when rendering. +- *ActionController::MimeResponds*: Support for content negotiation + (*respond\_to*, *respond\_with*). +- *ActionController::Cookies*: Support for *cookies*, which includes + support for signed and encrypted cookies. This requires the cookie + middleware. + +The best place to add a module is in your *ApplicationController*. You +can also add modules to individual controllers. -- cgit v1.2.3 From f3df21649a76ccce92be967a070c4f8fd7f370e9 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 18:46:29 -0300 Subject: Add CHANGELOG entries for API apps functionality --- actionpack/CHANGELOG.md | 6 ++++++ railties/CHANGELOG.md | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 66f1d8c6a6..7b2f150c67 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,9 @@ +* Add support for API only apps. + ActionController::API is added as a replacement of + ActionController::Base for this kind of applications. + + *Santiago Pastorino & Jorge Bejar* + * Remove `assigns` and `assert_template`. Both methods have been extracted into a gem at https://github.com/rails/rails-controller-testing. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a0fb612cba..3536929d00 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,10 @@ +* Add support for API only apps. + Middleware stack was slimmed down and it has only the needed + middleware for API apps & generators generates the right files, + folders and configurations. + + *Santiago Pastorino & Jorge Bejar* + * Make generated scaffold functional tests work inside engines. *Yuji Yaginuma* -- cgit v1.2.3 From 564d0299c0087bc234c3bad6141df02d1ce3d46b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 14 May 2015 22:41:08 -0300 Subject: Change guide heading from - to = --- guides/source/api_app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 40aefbc0fe..57846ae4e5 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -1,5 +1,5 @@ Using Rails for API-only Apps ------------------------------ +============================= In this guide you will learn: -- cgit v1.2.3 From 80702b78e1686e5569d8e0b86f2500a2df25c9cf Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 15 May 2015 11:23:47 -0300 Subject: It's rails new my_api --api --- guides/source/api_app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 57846ae4e5..0a6335ed88 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -141,7 +141,7 @@ in features as needed. You can generate a new api Rails app: \ -\$ rails new my\_api —api\ +\$ rails new my\_api --api\ This will do three main things for you: -- cgit v1.2.3 From 72d0784611af76dd0bb3154a662c6ec14a022f4c Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 15 May 2015 11:28:52 -0300 Subject: Make Rails API apps return the full resource on update --- .../generators/rails/scaffold_controller/templates/api_controller.rb | 2 +- .../generators/test_unit/scaffold/templates/api_functional_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb index ca76da6530..695b7cc90b 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb @@ -32,7 +32,7 @@ class <%= controller_class_name %>Controller < ApplicationController # PATCH/PUT <%= route_url %>/1 def update if @<%= orm_instance.update("#{singular_table_name}_params") %> - head :no_content + render json: <%= "@#{singular_table_name}" %> else render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb index 423437bba1..7e41162d47 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb @@ -27,7 +27,7 @@ class <%= controller_class_name %>ControllerTest < ActionController::TestCase test "should update <%= singular_table_name %>" do patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> } - assert_response 204 + assert_response 200 end test "should destroy <%= singular_table_name %>" do -- cgit v1.2.3 From ebcc15ca4ea22f8ace57a5251ceb8de4b917cd90 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Wed, 27 May 2015 21:10:29 -0300 Subject: Add rake-cors gem (commented) in Gemfile for rails api apps --- railties/lib/rails/generators/rails/app/templates/Gemfile | 3 +++ .../rails/app/templates/config/initializers/cors.rb | 14 ++++++++++++++ railties/test/generators/api_app_generator_test.rb | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index cf811503be..606f1d4f96 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -25,6 +25,9 @@ source 'https://rubygems.org' # Use ActiveModelSerializers to serialize JSON responses gem 'active_model_serializers', '~> 0.10.0.rc1' +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +# gem 'rack-cors' + <%- end -%> <% if RUBY_ENGINE == 'ruby' -%> group :development, :test do diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb new file mode 100644 index 0000000000..45c44d24f8 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb @@ -0,0 +1,14 @@ +# Avoid CORS issues when API is called from the frontend app +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests + +# Read more: https://github.com/cyu/rack-cors + +# Rails.application.config.middleware.insert_before 0, "Rack::Cors" do +# allow do +# origins 'example.com' +# +# resource '*', +# headers: :any, +# methods: [:get, :post, :put, :patch, :delete, :options, :head] +# end +# end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 16c0f1ae82..94d1008aae 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -45,9 +45,12 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_match(/config.api_only = true/, content) end + assert_file "config/initializers/cors.rb" + assert_file "config/initializers/wrap_parameters.rb" do |content| assert_no_match(/wrap_parameters/, content) end + assert_file "app/controllers/application_controller.rb", /ActionController::API/ end -- cgit v1.2.3 From a2c9a7308474bc1d7fc35df9560c46125d94f5a4 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 2 Jun 2015 15:48:20 -0300 Subject: Include ParamsWrapper in AC::API ParamsWrapper was initially removed from API controllers according to the following discusision: https://github.com/rails-api/rails-api/issues/33 However, we're including it again so Rails API devs can decide whether to enable or disable it. --- actionpack/lib/action_controller/api.rb | 6 ++++- .../test/controller/api/params_wrapper_test.rb | 26 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 actionpack/test/controller/api/params_wrapper_test.rb diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 9dc96bd3e6..b4583a073b 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -128,7 +128,11 @@ module ActionController # Add instrumentations hooks at the bottom, to ensure they instrument # all the methods properly. - Instrumentation + Instrumentation, + + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper ] MODULES.each do |mod| diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb new file mode 100644 index 0000000000..e40a39d829 --- /dev/null +++ b/actionpack/test/controller/api/params_wrapper_test.rb @@ -0,0 +1,26 @@ +require 'abstract_unit' + +class ParamsWrapperForApiTest < ActionController::TestCase + class UsersController < ActionController::API + attr_accessor :last_parameters + + wrap_parameters :person, format: [:json] + + def test + self.last_parameters = params.except(:controller, :action) + head :ok + end + end + + class Person; end + + tests UsersController + + def test_specify_wrapper_name + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, params: { 'username' => 'sikachu' } + + expected = { 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }} + assert_equal expected, @controller.last_parameters + end +end -- cgit v1.2.3 From cf9f2f329a589a7867b395fbc0274a90a40c9b6f Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 2 Jun 2015 15:54:07 -0300 Subject: Enable wrap_parameter by default in rails api applications --- .../rails/app/templates/config/initializers/wrap_parameters.rb.tt | 2 -- railties/test/generators/api_app_generator_test.rb | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 0fc1514684..94f612c3dd 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -1,6 +1,5 @@ # Be sure to restart your server when you modify this file. -<%- unless options[:api] -%> # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. @@ -8,7 +7,6 @@ ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end -<%- end -%> <%- unless options.skip_active_record? -%> # To enable root element in JSON for ActiveRecord objects. diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 94d1008aae..9978ad0da1 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -47,9 +47,7 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_file "config/initializers/cors.rb" - assert_file "config/initializers/wrap_parameters.rb" do |content| - assert_no_match(/wrap_parameters/, content) - end + assert_file "config/initializers/wrap_parameters.rb" assert_file "app/controllers/application_controller.rb", /ActionController::API/ end -- cgit v1.2.3 From afc78e72b4fdf940bdd747c7f8461d562ab6bb73 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 2 Jun 2015 15:55:26 -0300 Subject: Checking if controller responds to wrap_parameter is not longer required --- .../rails/app/templates/config/initializers/wrap_parameters.rb.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 94f612c3dd..cadc85cfac 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -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] if respond_to?(:wrap_parameters) + wrap_parameters format: [:json] end <%- unless options.skip_active_record? -%> -- cgit v1.2.3 From 8d3e6e5f4d97aa8f2b8b8dde949a1b79b50276d1 Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 2 Jun 2015 17:08:53 -0300 Subject: Add test coverage for implicit render in empty actions --- actionpack/test/controller/render_test.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index cabacad940..c9c43de37d 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -24,6 +24,11 @@ class TestControllerWithExtraEtags < ActionController::Base end end +class ImplicitRenderTestController < ActionController::Base + def empty_action + end +end + class TestController < ActionController::Base protect_from_forgery @@ -463,6 +468,15 @@ class MetalRenderTest < ActionController::TestCase end end +class ImplicitRenderTest < ActionController::TestCase + tests ImplicitRenderTestController + + def test_implicit_no_content_response + get :empty_action + assert_response :no_content + end +end + class HeadRenderTest < ActionController::TestCase tests TestController -- cgit v1.2.3 From 6c165773117dc7e60f5bb4762e58c7c522d69fcc Mon Sep 17 00:00:00 2001 From: Jorge Bejar Date: Tue, 2 Jun 2015 17:12:50 -0300 Subject: Return 204 if render is not called in API controllers --- actionpack/lib/action_controller.rb | 1 + actionpack/lib/action_controller/api.rb | 1 + .../lib/action_controller/metal/basic_implicit_render.rb | 11 +++++++++++ actionpack/lib/action_controller/metal/implicit_render.rb | 9 +++------ actionpack/test/controller/api/implicit_render_test.rb | 15 +++++++++++++++ 5 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 actionpack/lib/action_controller/metal/basic_implicit_render.rb create mode 100644 actionpack/test/controller/api/implicit_render_test.rb diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index f6bc5b951c..89fc4520d3 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -25,6 +25,7 @@ module ActionController autoload :Head autoload :Helpers autoload :HttpAuthentication + autoload :BasicImplicitRender autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index b4583a073b..6fab19296d 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -114,6 +114,7 @@ module ActionController Renderers::All, ConditionalGet, RackDelegation, + BasicImplicitRender, StrongParameters, ForceSSL, diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb new file mode 100644 index 0000000000..6c6f8381ff --- /dev/null +++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb @@ -0,0 +1,11 @@ +module ActionController + module BasicImplicitRender + def send_action(method, *args) + super.tap { default_render unless performed? } + end + + def default_render(*args) + head :no_content + end + end +end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 1573ea7099..d66b2214ce 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,17 +1,14 @@ module ActionController module ImplicitRender - def send_action(method, *args) - ret = super - default_render unless performed? - ret - end + + include BasicImplicitRender def default_render(*args) if template_exists?(action_name.to_s, _prefixes, variants: request.variant) render(*args) else logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger - head :no_content + super end end diff --git a/actionpack/test/controller/api/implicit_render_test.rb b/actionpack/test/controller/api/implicit_render_test.rb new file mode 100644 index 0000000000..26f9cd8f78 --- /dev/null +++ b/actionpack/test/controller/api/implicit_render_test.rb @@ -0,0 +1,15 @@ +require 'abstract_unit' + +class ImplicitRenderAPITestController < ActionController::API + def empty_action + end +end + +class ImplicitRenderAPITest < ActionController::TestCase + tests ImplicitRenderAPITestController + + def test_implicit_no_content_response + get :empty_action + assert_response :no_content + end +end -- cgit v1.2.3 From 1fd42f33385fb2b6647d2d43faec0399e2e3118c Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 5 Jun 2015 16:04:07 -0300 Subject: Mention that doing nothing in Rails API controllers returns 204 --- actionpack/lib/action_controller/api.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index 6fab19296d..d8149e0232 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -38,7 +38,7 @@ module ActionController # can use render :json 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 render or redirect in - # all actions. + # all actions, otherwise it will return 204 No Content response. # # def show # @post = Post.find(params[:id]) -- cgit v1.2.3 From 51d5d6252ee093d3fa004abec79319ae8b4c42c4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 8 Jun 2015 14:07:53 -0300 Subject: head :no_content is implicitly called --- .../generators/rails/scaffold_controller/templates/api_controller.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb index 695b7cc90b..bc3c9b3f6b 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb @@ -41,8 +41,6 @@ class <%= controller_class_name %>Controller < ApplicationController # DELETE <%= route_url %>/1 def destroy @<%= orm_instance.destroy %> - - head :no_content end private -- cgit v1.2.3