Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
032778e
Add ActionController API functionality
spastorino Apr 15, 2015
2d86b6d
Move Model test class inside RenderersApiController namespace
spastorino Apr 15, 2015
135c059
Add config.api_only option to application and remove appropriate midd…
spastorino Apr 16, 2015
212a099
Add AC::API + its middleware stack integration test
spastorino Apr 16, 2015
3adb5ea
Add ApiPublicException middleware
spastorino Apr 16, 2015
2a9cf48
rails new --api generates an api app skeleton
spastorino Apr 16, 2015
c09a401
Generate appropriate initializers for an api app
spastorino Apr 17, 2015
e8915d0
api option implies skipping javascript & sprockets
spastorino Apr 17, 2015
1718683
Do not generate app/assets directory for api apps
spastorino Apr 17, 2015
9b66071
Do not generate app/helpers directory for api apps
spastorino Apr 17, 2015
3847e48
Do not generate app/views directory for api apps
spastorino Apr 17, 2015
7b47e42
Do not generate test/helpers directory for api apps
spastorino Apr 17, 2015
101df20
Add api_only option to Generators
spastorino Apr 17, 2015
c190352
Hide assets, helper, css and js namespaces for api only apps
spastorino Apr 17, 2015
e8100fc
Api apps scaffold generates routes without new and edit actions
spastorino Apr 18, 2015
20939b3
config.generators.api_only = true set rails api option on generators
spastorino Apr 18, 2015
d4fe23c
API apps scaffold generator generates an apropriate controller
spastorino Apr 19, 2015
451b1e3
Add api scaffold test for route, controller and its tests
spastorino Apr 20, 2015
6d2b405
Api apps scaffold does not generate views
spastorino Apr 20, 2015
e5b6188
Api apps scaffold does not generate helpers
spastorino Apr 20, 2015
94fdba9
Api apps scaffold does not generate assets
spastorino Apr 20, 2015
decc4e8
Do not generate lib/assets directory for api apps
spastorino Apr 20, 2015
98a9936
config.api_only = true implies config.generators.api_only = true
spastorino Apr 20, 2015
e9e94c1
Add config.api_only = true to config/application.rb when using rails …
spastorino Apr 20, 2015
7d17269
Add test to show api only apps allow overriding generator options
spastorino Apr 20, 2015
3d37300
Disable jbuilder for Rails API apps, meanwhile it doesn't play nicely
spastorino Apr 21, 2015
4c1b437
Use nex hash syntax on tests
spastorino Apr 21, 2015
4204778
Adhere to Rails convention for private indentation
spastorino Apr 21, 2015
38818c9
Remove api_rendering is not needed
spastorino Apr 21, 2015
e7b89f1
Remove Unneeded ApiPublicExceptions middleware, PublicExceptions alre…
spastorino Apr 21, 2015
b643d7a
Refactor internal? to query internal_controller? and internal_asset? …
spastorino Apr 22, 2015
440b334
Use new hash syntax
spastorino Apr 22, 2015
099055d
Remove extra whitespaces
spastorino Apr 22, 2015
674dab3
Routes resources avoid :new and :edit endpoints if api_only is enabled
jmbejar May 4, 2015
e380887
Exclude cache_digests:dependencies rake task in api app
jmbejar May 5, 2015
fd25085
Remove Compatibility module since we don't remember why it was added …
jmbejar May 5, 2015
fa11f10
Api only apps should include tmp and vendor folders
jmbejar May 5, 2015
2487bfb
Do not say that Api Controllers are faster than regular ones in docs
jmbejar May 5, 2015
08cfe34
Rename test methods in api conditional get controller tests
jmbejar May 5, 2015
11c71b2
Revert changes related with api apps in RouteWrapper
jmbejar May 5, 2015
7db63f3
Fix MimeResponds example in AC::API documentation
spastorino May 13, 2015
b6c270f
Remove unneeded option from ResourceRouteGenerator
spastorino May 13, 2015
846f352
Fix class_option description for api generators
spastorino May 13, 2015
dc4c68a
Fix scaffold generator test for resource routes
spastorino May 14, 2015
511c33a
Add AMS 0.10.0.rc1 by default for api apps
spastorino May 14, 2015
03b576e
http only => API only
spastorino May 14, 2015
37507d3
Document Generators.api_only! method
spastorino May 14, 2015
b7494b6
Add API only apps guide
spastorino May 14, 2015
f3df216
Add CHANGELOG entries for API apps functionality
spastorino May 14, 2015
564d029
Change guide heading from - to =
spastorino May 15, 2015
80702b7
It's rails new my_api --api
spastorino May 15, 2015
72d0784
Make Rails API apps return the full resource on update
spastorino May 15, 2015
ebcc15c
Add rake-cors gem (commented) in Gemfile for rails api apps
jmbejar May 28, 2015
a2c9a73
Include ParamsWrapper in AC::API
jmbejar Jun 2, 2015
cf9f2f3
Enable wrap_parameter by default in rails api applications
jmbejar Jun 2, 2015
afc78e7
Checking if controller responds to wrap_parameter is not longer required
jmbejar Jun 2, 2015
8d3e6e5
Add test coverage for implicit render in empty actions
jmbejar Jun 2, 2015
6c16577
Return 204 if render is not called in API controllers
jmbejar Jun 2, 2015
1fd42f3
Mention that doing nothing in Rails API controllers returns 204
spastorino Jun 5, 2015
51d5d62
head :no_content is implicitly called
spastorino Jun 8, 2015
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions actionpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
3 changes: 2 additions & 1 deletion actionpack/lib/action_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
module ActionController
extend ActiveSupport::Autoload

autoload :API
autoload :Base
autoload :Caching
autoload :Metal
Expand All @@ -15,7 +16,6 @@ module ActionController
autoload :FormBuilder

autoload_under "metal" do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove Compatability?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removal of that line is unrelated, but anyway it's a module that does not exist anymore.

autoload :Compatibility
autoload :ConditionalGet
autoload :Cookies
autoload :DataStreaming
Expand All @@ -25,6 +25,7 @@ module ActionController
autoload :Head
autoload :Helpers
autoload :HttpAuthentication
autoload :BasicImplicitRender
autoload :ImplicitRender
autoload :Instrumentation
autoload :MimeResponds
Expand Down
145 changes: 145 additions & 0 deletions actionpack/lib/action_controller/api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
require 'action_view'
require 'action_controller'
require 'action_controller/log_subscriber'

module ActionController
# API Controller is a lightweight version of <tt>ActionController::Base</tt>,
# created for applications that don't require all functionality that a complete
# \Rails controller provides, allowing you to create 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,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still do allow template rendering for jbuilder I suppose?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this module is added back from jbuilder itself rails/jbuilder@29c0014#diff-524d52316875e7ab48f1778a8ae620cfR14

# 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.
#
# By default, only the ApplicationController in a \Rails application inherits
# from <tt>ActionController::API</tt>. All other controllers in turn inherit
# from ApplicationController.
#
# A sample controller could look like this:
#
# class PostsController < ApplicationController
# def index
# @posts = Post.all
# render json: @posts
# end
# end
#
# Request, response and parameters objects all work the exact same way as
# <tt>ActionController::Base</tt>.
#
# == Renders
#
# The default API Controller stack includes all renderers, which means you
# can use <tt>render :json</tt> and brothers freely in your controllers. Keep
# in mind that templates are not going to be rendered, so you need to ensure
# your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
# all actions, otherwise it will return 204 No Content response.
#
# def show
# @post = Post.find(params[:id])
# render json: @post
# end
#
# == Redirects
#
# Redirects are used to move from one action to another. You can use the
# <tt>redirect</tt> method in your controllers in the same way as
# <tt>ActionController::Base</tt>. For example:
#
# def create
# redirect_to root_url and return if not_authorized?
# # do stuff here
# end
#
# == Adding new behavior
#
# In some scenarios you may want to add back some functionality provided by
# <tt>ActionController::Base</tt> that is not present by default in
# <tt>ActionController::API</tt>, for instance <tt>MimeResponds</tt>. This
# module gives you the <tt>respond_to</tt> and <tt>respond_with</tt> methods.
# Adding it is quite simple, you just need to include the module in a specific
# controller or in <tt>ApplicationController</tt> in case you want it
# available to your entire app:
#
# class ApplicationController < ActionController::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
#
# Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
# available modules if you want to include any other functionality that is
# not provided by <tt>ActionController::API</tt> out of the box.
class API < Metal
abstract!

# 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why leaving the include for the users since it is only what they can do? I think we should implement it for ourself and just require users to do:

ActionController::API.without_modules(:Redirecting, :DataStreaming)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature already exists in Base so I wouldn't change the current API

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ‘

# 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,
Rendering,
Renderers::All,
ConditionalGet,
RackDelegation,
BasicImplicitRender,
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,

# 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, self)
end
end
11 changes: 11 additions & 0 deletions actionpack/lib/action_controller/metal/basic_implicit_render.rb
Original file line number Diff line number Diff line change
@@ -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
9 changes: 3 additions & 6 deletions actionpack/lib/action_controller/metal/implicit_render.rb
Original file line number Diff line number Diff line change
@@ -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

Expand Down
25 changes: 19 additions & 6 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1042,18 +1042,23 @@ module Resources
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
@as = options[:as]
@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
Expand Down Expand Up @@ -1120,15 +1125,19 @@ def shallow?
end

class SingletonResource < Resource #:nodoc:
def initialize(entities, options)
def initialize(entities, api_only, options)
super
@as = nil
@controller = (options[:controller] || plural).to_s
@as = options[:as]
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
Expand Down Expand Up @@ -1178,7 +1187,7 @@ def resource(*resources, &block)
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]
Expand Down Expand Up @@ -1336,7 +1345,7 @@ def resources(*resources, &block)
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]
Expand Down Expand Up @@ -1768,6 +1777,10 @@ def set_member_mappings_for_resource
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
Expand Down
22 changes: 16 additions & 6 deletions actionpack/lib/action_dispatch/routing/route_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -319,17 +319,23 @@ def self.default_resources_path_names
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
Expand All @@ -352,6 +358,10 @@ def relative_url_root
@config.relative_url_root
end

def api_only?
@config.api_only
end

def request_class
ActionDispatch::Request
end
Expand Down
4 changes: 4 additions & 0 deletions actionpack/test/abstract_unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ def assert_header(name, value)
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)
Expand Down
57 changes: 57 additions & 0 deletions actionpack/test/controller/api/conditional_get_test.rb
Original file line number Diff line number Diff line change
@@ -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_gets_last_modified
get :two
assert_equal @last_modified, @response.headers['Last-Modified']
assert_response :success
end

def test_request_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
Loading