From 41cb23f22fe8b076328b62d8f8e2d74fccb73bda Mon Sep 17 00:00:00 2001 From: Artur Beljajev Date: Sat, 9 Jun 2018 05:04:21 +0300 Subject: [PATCH] Backport Rails 5 API controller --- lib/action_controller/api.rb | 149 ++++++++++++++++++ lib/action_controller/api/api_rendering.rb | 16 ++ .../metal/basic_implicit_render.rb | 13 ++ lib/rails5_api_controller_backport.rb | 3 + 4 files changed, 181 insertions(+) create mode 100644 lib/action_controller/api.rb create mode 100644 lib/action_controller/api/api_rendering.rb create mode 100644 lib/action_controller/metal/basic_implicit_render.rb create mode 100644 lib/rails5_api_controller_backport.rb diff --git a/lib/action_controller/api.rb b/lib/action_controller/api.rb new file mode 100644 index 000000000..5a9fd4512 --- /dev/null +++ b/lib/action_controller/api.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +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 functionalities that a complete + # \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, + # 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. + # + # Normally, +ApplicationController+ is the only controller that 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_to in + # all actions, otherwise it will return 204 No Content. + # + # 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_to method in your controllers in the same way as in + # 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 method. 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 in your entire + # application: + # + # class ApplicationController < ActionController::API + # include ActionController::MimeResponds + # end + # + # class PostsController < ApplicationController + # def index + # posts = Post.all + # + # respond_to do |format| + # format.json { render json: posts } + # format.xml { render xml: posts } + # end + # end + # end + # + # Make sure to check the modules included in ActionController::Base + # if you want to use any other functionality that is not provided + # by ActionController::API out of the box. + class API < Metal + abstract! + + # Shortcut helper that returns all the ActionController::API modules except + # the ones passed as arguments: + # + # class MyAPIBaseController < ActionController::Metal + # ActionController::API.without_modules(:ForceSSL, :UrlFor).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, + BasicImplicitRender, + StrongParameters, + + ForceSSL, + DataStreaming, + + # Before callbacks should also be executed as early 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, + + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper + ] + + MODULES.each do |mod| + include mod + end + + ActiveSupport.run_load_hooks(:action_controller_api, self) + ActiveSupport.run_load_hooks(:action_controller, self) + end +end diff --git a/lib/action_controller/api/api_rendering.rb b/lib/action_controller/api/api_rendering.rb new file mode 100644 index 000000000..52e9f60fc --- /dev/null +++ b/lib/action_controller/api/api_rendering.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module ActionController + module ApiRendering + extend ActiveSupport::Concern + + included do + include Rendering + end + + def render_to_body(options = {}) + _process_options(options) + super + end + end +end \ No newline at end of file diff --git a/lib/action_controller/metal/basic_implicit_render.rb b/lib/action_controller/metal/basic_implicit_render.rb new file mode 100644 index 000000000..9030ea585 --- /dev/null +++ b/lib/action_controller/metal/basic_implicit_render.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module ActionController + module BasicImplicitRender # :nodoc: + def send_action(method, *args) + super.tap { default_render unless performed? } + end + + def default_render(*args) + head :no_content + end + end +end \ No newline at end of file diff --git a/lib/rails5_api_controller_backport.rb b/lib/rails5_api_controller_backport.rb new file mode 100644 index 000000000..252332488 --- /dev/null +++ b/lib/rails5_api_controller_backport.rb @@ -0,0 +1,3 @@ +require_relative 'action_controller/metal/basic_implicit_render' +require_relative 'action_controller/api/api_rendering' +require_relative 'action_controller/api' \ No newline at end of file