Rails api #19832

Merged
merged 60 commits into from Jun 11, 2015

Projects

None yet
@dhh
Member
dhh commented Apr 20, 2015

[Description extracted from http://wyeworks.com/blog/2015/4/20/rails-api-is-going-to-be-included-in-rails-5/]

A decision was made to incorporate Rails API into Rails core 🎉 🎉 🎉.

What Is Rails API?

The original idea behind Rails API was to serve as a starting point for a version of Rails better suited for JS-heavy apps. As of today, Rails API provides: trimmed down controllers and middleware stack together with a matching set of generators, all specifically tailored for API type applications.

Next steps

We still need to discuss the “Rails way” for API applications, how API apps should be built and, what features we’d like included from our original list of ideas. In particular:

  • Do we want to avoid asset generation in Rails by having a back-end and a front-end apps?
  • Do we prefer to have a single application and keep asset generation in Rails instead?
  • Do we like Active Model Serializers better than Jbuilder?
  • If not, can we make Rails API play nicely with Jbuilder?
@arthurnn arthurnn and 2 others commented on an outdated diff Apr 20, 2015
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!"
@arthurnn
arthurnn Apr 20, 2015 Member

should we use new hash syntax on code we are adding?

@dhh
dhh Apr 20, 2015 Member

Yes please.

On Mon, Apr 20, 2015 at 10:58 PM, Arthur Nogueira Neves <
notifications@github.com> wrote:

In actionpack/test/controller/api/conditional_get_test.rb
#19832 (comment):

@@ -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!"
    

should we use new hash syntax on code we are adding?


Reply to this email directly or view it on GitHub
https://github.com/rails/rails/pull/19832/files#r28728425.

@dhh dhh and 2 others commented on an outdated diff Apr 20, 2015
...b/action_dispatch/middleware/api_public_exceptions.rb
+ 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
+
@dhh
dhh Apr 20, 2015 Member

✂️ newline and intent below private.

@arthurnn
arthurnn Apr 20, 2015 Member

also, I think this needs to follow the private indentation. (sorry, dhh said that already)

@ZhangHanDong

Good job, although I have been using Rails Metal for my Api.

@spastorino
Member

Hey @dhh, thanks for opening the PR.
We need to discuss a lot of stuff about this, I've written a short blog post that also has links for a previous article I've written in the past http://wyeworks.com/blog/2015/4/20/rails-api-is-going-to-be-included-in-rails-5/

Some of the things that needs discussions are ...

  • Would we like to have a backend app and a frontend app and then avoid assets generation in Rails?
  • Would we prefer to have just one app and keep assets generation in Rails?
  • Would we like to include Active Model Serializers by default or jbuilder?
  • How this will play nicely when used with jbuilder?
@guilleiguaran guilleiguaran commented on the diff Apr 21, 2015
...emplates/app/controllers/application_controller.rb.tt
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
+<%- end -%>
@guilleiguaran
guilleiguaran Apr 21, 2015 Member

What about that advice:

# For APIs, you may want to use :null_session instead.

Don't we want to do that for APIs?

@hammerdr
hammerdr Apr 21, 2015 Contributor

null_session should be an explicit choice, I think. If you have a API endpoint that doesn't have any parameters to validate, the null_session will still execute the endpoint. Exception is safer.

@georgeclaghorn
georgeclaghorn Apr 21, 2015 Contributor

Doesn't seem to make sense for a controller in an API-only app to have cross-site request forgery protection. Also, from the docs, API controllers do not have session handling:

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.

@hammerdr
hammerdr Apr 21, 2015 Contributor

Many APIs would want CSRF protection. This includes single page javascript applications.

null_session isn't the same thing as a browser session, but is instead more like an 'empty' request. It's more like passing a NullObject to a controller instead of raising an exception. The benefit of doing so is that you have more explicit control over the actions regarding that request on a per action basis. With exceptions, you're handling the exception in a more generic location (controller level instead of action level).

Not all of this is 100% verified, just off of my memory. Apologies if I got something wrong.

@georgeclaghorn
georgeclaghorn Apr 21, 2015 Contributor

Sorry, I misunderstood! Was thinking in the context of a public API. Thanks for correcting me. 😄

@jsanders
jsanders Apr 21, 2015

I'm not sure if there's a consensus on this, or what that consensus is if it exists, but I think it still makes sense to use a cookie for authentication when serving an API to a client using AJAX through a browser, because you can use an HttpOnly cookie, which javascript can't access, rather than local storage, which it can. If you're doing that, then CSRF protection is still important.

Having said that, it doesn't look (based on your quote) like API controllers will be using cookie-based sessions by default, so maybe they don't need CSRF protection by default either.

Edit: Wrote this before seeing @hammerdr's comment – I'll leave it here anyway, but it might be irrelevant.

@spastorino
spastorino Apr 21, 2015 Member

It doesn't need CSRF protection, we are not adding session stuff on this configuration.
The comment is still valid for regular Rails applications where you can build APIs, for those APIs maybe null_session is a good idea. For Rails API's apis you don't need this kind of stuff.

@rafaelfranca rafaelfranca added this to the 5.0.0 milestone Apr 21, 2015
@rafaelfranca rafaelfranca and 1 other commented on an outdated diff Apr 21, 2015
actionpack/lib/action_controller/api.rb
+ # == 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
+ # respond_to :json, :xml
@rafaelfranca
rafaelfranca Apr 21, 2015 Member

This feature was removed from core.

@spastorino
spastorino Apr 21, 2015 Member

Good catch, I maybe I should just remove all that.

@rafaelfranca rafaelfranca and 1 other commented on an outdated diff Apr 21, 2015
actionpack/lib/action_controller/api.rb
+ # class PostsController < ApplicationController
+ # respond_to :json, :xml
+ #
+ # def index
+ # @posts = Post.all
+ # respond_with @posts
+ # end
+ # end
+ #
+ # Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
+ # available modules if you want to include any other functionality that is
+ # not provided by <tt>ActionController::API</tt> out of the box.
+ class API < Metal
+ abstract!
+
+ module Compatibility
@rafaelfranca
rafaelfranca Apr 21, 2015 Member

Do you remember why we need this module?

@spastorino
spastorino Apr 21, 2015 Member

To be honest I don't remember very well and was in my todo list to review it. What tries to do if I'm not wrong is to avoid crashing when plugins, etc assumes that your controller is a Base controller which won't be true. But maybe we should try to remove and see what happens without this.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 21, 2015
actionpack/lib/action_controller/api/api_rendering.rb
@@ -0,0 +1,14 @@
+module ActionController
+ module ApiRendering
@rafaelfranca
rafaelfranca Apr 21, 2015 Member

What is the difference between ApiRendering and the one included inside ActionController::Base?

@jmbejar
jmbejar Apr 21, 2015 Contributor

It seems this module was added to avoid the inclusion of ActionView::Rendering (this is currently being included on rails-api).

However, I tested this PR without this module and it seems to work properly.

I think this still works because the render method options are processed in the Renderers module here (and api controllers still use this module).

In brief, we should be fine just including Rendering, no need to have a specific ApiRendering.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 21, 2015
...b/action_dispatch/middleware/api_public_exceptions.rb
@@ -0,0 +1,47 @@
+module ActionDispatch
+ class ApiPublicExceptions
@rafaelfranca
rafaelfranca Apr 21, 2015 Member

The same question that ApiRendering about this one.

@jmbejar
jmbejar Apr 21, 2015 Contributor

In fact, ApiPublicExceptions is never used apart from tests (it's not included in the default middleware like rails-api gem does here).

After testing for a while, I'm pretty convinced that exceptions are rendered in the json response fine without the need of this module. The existent PublicException is doing exactly the same, I think.

@spastorino I think we can just remove this file and related tests, but maybe I'm missing something?

@spastorino
spastorino Apr 21, 2015 Member

@rafaelfranca great catches bro. Yeah this was needed some time ago but it is not anymore.
Removed spastorino@c23d78e

@chancancode
Member

@rwz you might have opinions on some of the open questions?

@dhh
Member
dhh commented Apr 21, 2015

Agree on keeping assets out of the app. Need to take another (long over
due) look at AMS. But regardless of whether we go with AMS or jbuilder as
default, both should be totally equal and trivial to swap between.

On Tue, Apr 21, 2015 at 5:08 PM, Santiago Pastorino <
notifications@github.com> wrote:

BTW, because I left some open questions without answers, my take on those
is ...

Would we like to have a backend app and a frontend app and then avoid
assets generation in Rails?
Yes. So Rails should only generate the API and completely skip assets
generation.

Would we prefer to have just one app and keep assets generation in
Rails?
No.

Would we like to include Active Model Serializers by default or
jbuilder?
AMS.

How this will play nicely when used with jbuilder?
It should work.

But given that the questions could be a bit controversial I left it there
to be able to discuss.


Reply to this email directly or view it on GitHub
#19832 (comment).

@omegahm omegahm and 1 other commented on an outdated diff Apr 21, 2015
actionpack/lib/action_dispatch/routing/inspector.rb
@@ -45,7 +45,11 @@ def action
end
def internal?
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
+ internal_controller = controller.to_s =~ %r{\arails/(info|mailers|welcome)}
+ internal_asset = Rails.application.config.respond_to?(:assets) &&
+ path =~ %r{\a#{Rails.application.config.assets.prefix}\z}
+
+ internal_controller || internal_asset
@omegahm
omegahm Apr 21, 2015

The original is using the || short-circuit in a much nicer way then here.
You do all the work for internal_asset even though you don't need it.

A nicer solution might be to split it into two private methods and call them with the same short-circuiting logic:

def internal?
  internal_controller? || internal_asset?
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
@spastorino
spastorino Apr 22, 2015 Member

Agreed and fixed.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 22, 2015
actionpack/lib/action_dispatch/routing/inspector.rb
@@ -45,7 +45,11 @@ def action
end
def internal?
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
+ internal_controller = controller.to_s =~ %r{\arails/(info|mailers|welcome)}
+ internal_asset = Rails.application.config.respond_to?(:assets) &&
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

Why config is not responding to assets anymore? I could not find where we remove it.

@lucasmazza
lucasmazza Apr 22, 2015 Member

it's defined by sprockets-rails now, since 5172d93

@spastorino
spastorino Apr 22, 2015 Member

Thing is sprockets is not included when using Rails API. Anyway maybe we can fix this in sprockets railtie but unsure what's better given the current way that sprockets is hooking into AV.

@rafaelfranca
rafaelfranca Apr 22, 2015 Member

Got it. I think it seems good this way

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 22, 2015
railties/lib/rails/generators/rails/app/app_generator.rb
@@ -184,6 +187,10 @@ def initialize(*args)
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, skip_javascript: true).freeze if options[:api]
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

can't we just override/define skip_sprockets and skip_javascript methods and check if options[:api] is present inside both?

@jmbejar
jmbejar May 5, 2015 Contributor

This is implemented in this way taking advantage of existent conditional logic in templates.

Examples: https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt#L29
https://github.com/rails/rails/blob/master/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt#L21

I think it's better to have this done here, so we can avoid having more conditionals in the templates.

Thoughts?

@rafaelfranca
rafaelfranca May 11, 2015 Member

I'm suggesting to compose the conditionals in new methods. This way you would have to change the templates, but we don't need to change an implementation detail.

@spastorino
spastorino May 13, 2015 Member

@rafaelfranca to do what you're mentioning we would need to change things like ...

  1. https://github.com/rails/rails/blob/master/railties/lib/rails/generators/app_base.rb#L174
  2. https://github.com/rails/rails/blob/master/railties/lib/rails/generators/app_base.rb#L170

The most trivial change ends being like https://gist.github.com/spastorino/7989a586151e8da13351, not just adding one method.

I don't think the diff is much better than what we already have but we should reconsider following a better approach in the future. Something around the lines of having an options class instead of a hash and defining methods in it for each possibility.

@rafaelfranca rafaelfranca and 3 others commented on an outdated diff Apr 22, 2015
railties/lib/rails/generators/rails/app/app_generator.rb
@@ -244,11 +251,31 @@ def create_test_files
end
def create_tmp_files
- build(:tmp)
+ build(:tmp) unless options[:api]
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

why we would remove tmp in an API application? The application still need pids and temporary stuff

@ekampp
ekampp Apr 24, 2015

Tmp might also be useful for temporary file processing, if you're not using a file service.

@jmbejar
jmbejar May 5, 2015 Contributor

Just had a discussion with @spastorino. We agree that there is no reason to remove tmp or vendor folder.

We will revert this change.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 22, 2015
railties/lib/rails/generators/rails/app/app_generator.rb
end
def create_vendor_files
- build(:vendor)
+ build(:vendor) unless options[:api]
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

I think vendor is also needed in an API application. For example, I usually store internal gems engines inside vendor/gems to keep them in the same project.

@ekampp
ekampp Apr 24, 2015

Also, if you have a very closed-off system, using the bundle package --all or bundle --path vendor/bundle both uses the vendor dir.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 22, 2015
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 <tt>ActionController::Base</tt>,
+ # created for applications that don't require all functionality that a complete
+ # \Rails controller provides, allowing you to create faster controllers for
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

"faster controllers" I don't believe it is true and even it is we know that it would matter in real world. I'd change it to allowing you to create controllers with just the features that you need for API only applications.

@ekampp
ekampp Apr 24, 2015

Loading less is inherently faster than loading more. The statement is correct. Weather or not it's relevant to the real world is debatable.

@rafaelfranca rafaelfranca and 2 others commented on an outdated diff Apr 22, 2015
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 <tt>ActionController::Base</tt>,
+ # created for applications that don't require all functionality that a complete
+ # \Rails controller provides, allowing you to create faster controllers 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
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

same here about the "faster". I'd just omit it

@rafaelfranca rafaelfranca commented on the diff Apr 22, 2015
actionpack/lib/action_controller/api.rb
+ 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
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

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)
@spastorino
spastorino Apr 22, 2015 Member

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

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
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"
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

New hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/controller/api/renderers_test.rb
@@ -0,0 +1,38 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/conversions'
+
+class RenderersApiController < ActionController::API
+ class Model
+ def to_json(options = {})
+ { :a => 'b' }.to_json(options)
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/controller/api/renderers_test.rb
@@ -0,0 +1,38 @@
+require 'abstract_unit'
+require 'active_support/core_ext/hash/conversions'
+
+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)
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/controller/api/renderers_test.rb
+require 'abstract_unit'
+require 'active_support/core_ext/hash/conversions'
+
+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)
+ end
+ end
+
+ def one
+ render :json => Model.new
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/controller/api/renderers_test.rb
+ class Model
+ def to_json(options = {})
+ { :a => 'b' }.to_json(options)
+ end
+
+ def to_xml(options = {})
+ { :a => 'b' }.to_xml(options)
+ end
+ end
+
+ def one
+ render :json => Model.new
+ end
+
+ def two
+ render :xml => Model.new
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/controller/api/renderers_test.rb
+ 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)
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/controller/api/renderers_test.rb
+ 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)
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
actionpack/test/dispatch/show_exceptions_test.rb
@@ -22,7 +22,7 @@ def call(env)
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"))
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

✂️ these extra whitespaces added. We don't need to align this one.

@rafaelfranca rafaelfranca commented on an outdated diff Apr 22, 2015
...ties/test/application/initializers/frameworks_test.rb
@@ -129,6 +129,35 @@ def from_bar_helper
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' }
@rafaelfranca
rafaelfranca Apr 22, 2015 Member

new hash syntax

@rafaelfranca
Member

I remember that in our original patch we had a really great guide about this feature and I think it would be really great if we could get it back.

About the questions agree with @dhh.

@rafaelfranca
Member

@spastorino and, great implementation bro.

@Davidslv Davidslv commented on the diff Apr 22, 2015
railties/test/generators/api_app_generator_test.rb
+ 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)
@Davidslv
Davidslv Apr 22, 2015

Maybe just use the name of the gems instead, if someone uses double quotes it will not match

@guilleiguaran
guilleiguaran Apr 22, 2015 Member

This is testing here just against the default generated Gemfile that use single quote as convention, so this is fine.

@guilleiguaran
Member

@dhh Let me know if you have any concerns or recommendations about AMS API, right now that we're open for discussion and planning for the new version!!!

@jipiboily
Contributor

FWIW, here are my 2 cents.

I personally would by far prefer ActiveModel Serializers, but as long as we can swap them, I would be fine.

I think, that I would keep the assets generation out of API apps by default, but make it possible. I think most people using Rails as an API are more likely to have a separate setup for their front end, like using Gulp & Grunt or any other cool JS stuff there is.

Whatever the decisions are, I am very happy to see Rails API in Rails 5! Thanks for all the hard work on this (and everything else).

@ralph
ralph commented Apr 22, 2015

I'd argument for keeping asset compilation/concatenation/etc. out of the API app.

If you are writing a JS app, you'd rather use a JS toolchain to prepare the assets. Also, many JS frameworks come with their own build tools, like EmberJS and Angular 2.0. Furthermore, developers working on the JS app exclusively don't have to install a Ruby AND a JS env, if you provide them an API server for development.

@spastorino
Member

@rafaelfranca putting the guide back is in my todo list :)

@yuki24
Contributor
yuki24 commented Apr 24, 2015

I've used both ActiveModel Serializer and jbuilder pretty heavily and have never been a happy user of AMS. much prefer jbuilder.

@digitalysin

make sure to add built in authentication functionality since how api server handles authentication is little bit different.

@ekampp ekampp commented on an outdated diff Apr 24, 2015
actionpack/lib/action_controller/api.rb
+ 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
@ekampp
ekampp Apr 24, 2015

Compatibility appears here again. See above, we might not need this.

@ekampp ekampp and 2 others commented on an outdated diff Apr 24, 2015
actionpack/lib/action_dispatch/routing/inspector.rb
end
def engine?
rack_app.respond_to?(:routes)
end
+
+ private
+ def internal_controller?
+ controller.to_s =~ %r{\arails/(info|mailers|welcome)}
@ekampp
ekampp Apr 24, 2015

Shouldn't this be a escaped, capital A, like this: \A?

Also, why use the %r{xyz} syntax, as opposed to /xyz/?

@bsodmike
bsodmike Apr 24, 2015 Contributor

hey @ekampp, how are you keeping mate? Been a while.

Well, %r{...} lets you specify '/' without having to escape 'em, alternatively %r{\arails/(info|mailers|welcome)} => /\arails\/(info|mailers|welcome)/

However, you are correct re 'A' above needing to be \A. It needs to be replaced with %r{\Arails/(info|mailers|welcome)}.

@ekampp
ekampp Apr 24, 2015

I, personally prefer the double-slash syntax. I find it easier human readable, but I suppose that's a matter of internal rails style guide.

And I have been keeping good, @bsodmike! I'm keeping an eye out for the new rails release. One of the more promising in a long time. Haven't been working much directly with rails for a while.

@spastorino
spastorino May 2, 2015 Member

This is being moved out the PR. Changed related to this method were due to issues with sprockets-rails and they were fixed there. Will push some changes reverting this.

@ekampp ekampp and 1 other commented on an outdated diff Apr 24, 2015
actionpack/lib/action_dispatch/routing/inspector.rb
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}
@ekampp
ekampp Apr 24, 2015

Same question as above

@spastorino
spastorino May 2, 2015 Member

Same here, this is not going to be included.

@ekampp ekampp and 1 other commented on an outdated diff Apr 24, 2015
actionpack/test/controller/api/conditional_get_test.rb
+
+ 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
@ekampp
ekampp Apr 24, 2015

"...with_bang..."? What "bang" are we talking about here?

@spastorino
spastorino May 2, 2015 Member

Seems like the test name is outdated now.

@sandstrom

We're using Rails 4 as an API (for an SPA), so we very happy to see more support for the api use-case 😄

  • Regarding asset generation, we've chosen to keep it outside Rails (but also know of companies doing it the other way, using the pipeline to build the SPA).
  • Would prefer Active Model Serializer as the default over JBuilder (tried both and AMS was a better fit). No strong preference here though, as long as both are supported.

Again, happy to see this change!

@uberllama
Contributor

Moving API-only configuration into Rails 5 is an excellent decision, but it certainly shouldn't bias toward any given JSON serializer. Some people like to serialize their JSON in classes, some like composable views. I fall into the latter camp and its been great. I love Jbuilder.

Regarding the question of asset compilation, its an issue I'm dealing with right now and a tricky one. We have a Rails API serving a large React SPA and are currently near EOL using AP. We'll be moving to Webpack. That being said, we haven't decided whether or not to fully extract the front end from the repo and have separate builds or to keep the front end assets within the Rails app, and have web pack build a compiled JS file that AP then uglifies and hashes. Its certainly an interesting issue.

@uberllama
Contributor

Another useful component would be a single line config to transform incoming camel case params (JSON standard) into the expected snake case. You can do this with Jbuilder on output currently with Jbuilder.key_format camelize: :lower. I'm currently monkey patching ActionController.process_action to do it.

@jhoffner

For what it's worth, I was not a big fan of AMS either, it was too restrictive for my needs. I ended up building a simple Serializer base class which wrapped JBuilder so that I could still use partials but without using templates, which greatly increased performance. Using the same base class I also created a custom JSON API adapter. It's the best of both AMS and JBuilder.

My code has a bunch of bells and whistles that @dhh probably wouldn't want in a core library but I can make a repo/gist if anyone is interested.

@ekampp
ekampp commented Apr 27, 2015

I'm not sure that the "I like AMS" vs. "i like JBuilder" discussion will get us very far. Based on the number of people implementing AMS, it's quite clear that there's a number of people who like it, as with jBuilder also. So I think looking to implement an agnostic solution will suit the most people?

@dhh
Member
dhh commented Apr 28, 2015

I think it's trivial to make --api work with both approaches, and so we
should. The only open question is what the default should be. I'm partial
to jbuilder, but open to a discussion on the matter, and want to dig into
AMS further.

On Mon, Apr 27, 2015 at 6:48 PM, Emil Kampp notifications@github.com
wrote:

I'm not sure that the "I like AMS" vs. "i like JBuilder" discussion will
get us very far. Based on the number of people implementing AMS, it's quite
clear that there's a number of people who like it, as with jBuilder also.
So I think looking to implement an agnostic solution will suit the most
people?


Reply to this email directly or view it on GitHub
#19832 (comment).

@ekampp
ekampp commented Apr 28, 2015

I don't see that it does much difference, which one is the default, if they are easy to exchange. But I think we should keep in mind what people would use the --api option. Personally I don't think it will be first-time users of the Rails project that will use the --api option, but rather those who has some experience.

@dhh
Member
dhh commented Apr 28, 2015

I think --api should work just as well for the newbie
ember/angular/backbone/whatever developer who wants to make their front end
app work as it should for someone who has all the experience in the world.

Anyway, we can make progress on all the other points in the meantime.

On Tue, Apr 28, 2015 at 4:18 PM, Emil Kampp notifications@github.com
wrote:

I don't see that it does much difference, which one is the default, if
they are easy to exchange. But I think we should keep in mind what people
would use the --api option. Personally I don't think it will be first-time
users of the Rails project that will use the --api option, but rather those
who has some experience.


Reply to this email directly or view it on GitHub
#19832 (comment).

@ekampp
ekampp commented Apr 28, 2015

Right, didn't imply that it shouldn't be easy, just that the selection for the default JSON formatter should reflect the most common user type.

@simi
Contributor
simi commented Apr 28, 2015

I think we can use two bins. rails and rails-api.
Dne 28.4.2015 16:28 "David Heinemeier Hansson" notifications@github.com
napsal(a):

I think --api should work just as well for the newbie
ember/angular/backbone/whatever developer who wants to make their front end
app work as it should for someone who has all the experience in the world.

Anyway, we can make progress on all the other points in the meantime.

On Tue, Apr 28, 2015 at 4:18 PM, Emil Kampp notifications@github.com
wrote:

I don't see that it does much difference, which one is the default, if
they are easy to exchange. But I think we should keep in mind what people
would use the --api option. Personally I don't think it will be
first-time
users of the Rails project that will use the --api option, but rather
those
who has some experience.

Reply to this email directly or view it on GitHub
#19832 (comment).

Reply to this email directly or view it on GitHub
#19832 (comment).

@obsidienne

If you provide an --api switch on creation, how can I use it to add an api to an existing rails "legacy" app ?

Like somebody satisfied with rails sending html pages (no single page application) and needing a JSON api for iPhone/Android/WP natif app ?

@dhh
Member
dhh commented Apr 30, 2015

No change is needed for that. Rails out of the box already provides everything you need for an API. This is only for people who only want an API and nothing else.

On Apr 29, 2015, at 18:31, Claudio Bernardes notifications@github.com wrote:

If you provide an --api switch on creation, how can I use it to add an api to an existing rails "legacy" app ?

Like somebody satisfied with rails sending html pages (no single page application) and needing a JSON api for iPhone/Android/WP natif app ?


Reply to this email directly or view it on GitHub.

@ekampp
ekampp commented Apr 30, 2015

@simi what would two bins give us?

@simi
Contributor
simi commented Apr 30, 2015

If I understand this well, this is going to replace https://github.com/rails-api/rails-api. It will be nice to keep rails-api command also.

As @dhh mentioned:

I think --api should work just as well for the newbie.

I think it will be friendly for newbies to be able to use just rails-api new project for API projects.

@dhh
Member
dhh commented Apr 30, 2015

Doesn't make sense to me with an extra command now that we are making it a native feature. "rails new --api myapp" is good.

On Apr 30, 2015, at 15:11, Josef Šimánek notifications@github.com wrote:

If I understand this well, this is going to replace https://github.com/rails-api/rails-api. It will be nice to keep rails-api command also.

As @dhh mentioned:

I think --api should work just as well for the newbie.

I think it will be friendly for newbies to be able to use just rails-api new project for API projects.


Reply to this email directly or view it on GitHub.

@simi
Contributor
simi commented Apr 30, 2015

rails-api command was mean't to be just an rails alias with default --api parameter.

@dhh
Member
dhh commented Apr 30, 2015

What else was it meant to do?

On Apr 30, 2015, at 15:25, Josef Šimánek notifications@github.com wrote:

rails-api command was mean't to be just an rails alias with default --api parameter.


Reply to this email directly or view it on GitHub.

@bsodmike
Contributor
bsodmike commented May 1, 2015

@simi Seems like with the --api flag added, https://github.com/rails-api/rails-api would no longer be needed? No need for the alias "command" either.

@steveklabnik
Member

@dhh: I don't think we need both rails-api and rails --api. Just use the latter and call it a day.

@jjoos jjoos commented on an outdated diff May 2, 2015
actionpack/lib/action_dispatch/routing/inspector.rb
@@ -45,12 +45,22 @@ def action
end
def internal?
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
+ internal_controller? || internal_asset?
@jjoos
jjoos May 2, 2015

The second part of this method has been dropped in 757a2bc, probably best to just revert it to the current rails master version, since these changes don't seem related to rails-api and the erroneous \a => \A change below.

@paulwalker

Whatever the chosen serializer, it should not be so opinionated as to prevent or make extremely difficult any chosen JSON representation. As it stands, the inherit flaw with AMS is that it does not easily allow for serializing anything outside of the main "model" data. I found it rather unusable for anything any more complicated than a vanilla key/value model data representations. No matter your opinion on Hypermedia APIs, JSON schema, et all, more and more developers are tasked with things like gasp self describing APIs. If it's valid JSON, Rails should not get in your way at least. Please don't let "Rails API" turn into "Rails API (💩on application/json+*)

@rafaelfranca
Member

:shipit:

@spastorino
Member

Rails API works with ams ~> 0.10.0.rc1 and jbuilder master.
jbuilder fix rails/jbuilder@29c0014

@spastorino
Member

Will wait for more reviewers to merge.
/cc @jeremy @dhh

@rafaelfranca rafaelfranca commented on an outdated diff May 14, 2015
guides/source/api_app.md
@@ -0,0 +1,435 @@
+Using Rails for API-only Apps
+-----------------------------
@claudiob claudiob and 1 other commented on an outdated diff May 15, 2015
guides/source/api_app.md
+*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:
+
+<shell>\
+\$ rails new my\_api —api\
@claudiob
claudiob May 15, 2015 Member

Should this line say rails new my_api --api (two dashes)?

@claudiob claudiob and 1 other commented on an outdated diff May 15, 2015
...rails/scaffold_controller/templates/api_controller.rb
+
+ # 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
@claudiob
claudiob May 15, 2015 Member

How about we change this line to render json: <%= "@#{singular_table_name}" %>?

The rationale behind this is that POST or PATCH calls may modify the fields of the underlying resource that weren't part of the provided parameters (for example: created_at or updated_at timestamps), and it's convenient for the user to get the full resource back, rather than having to ask for the full resource in a second HTTP call.

I think this a better default behavior for a scaffold – a discussion was made (and merged) in jbuilder at rails/jbuilder#143 and there was a long discussion about it in Rails at #12136 which had some positive opinions by @guilleiguaran, @rafaelfranca, @carlosantoniodasilva and @chancancode – the main concern was backwards-compatibility (which shouldn't be an issue in this PR).

@claudiob
claudiob May 15, 2015 Member

To be clear, when you run rails g scaffold_controller post in a Rails 4.2 app with jbuilder in the Gemfile, this is what you get in app/controllers/posts_controller.rb:

  # PATCH/PUT /posts/1
  # PATCH/PUT /posts/1.json
  def update
    respond_to do |format|
      if @post.update(post_params)
        format.html { redirect_to @post, notice: 'Post was successfully updated.' }
        format.json { render :show, status: :ok, location: @post }
      else
        format.html { render :edit }
        format.json { render json: @post.errors, status: :unprocessable_entity }
      end
    end
  end

So in a (non-API) Rails app that uses jbuilder, a successful update results in:

render :show, status: :ok, location: @post

That's why, for coherence, I suggest something similar should be returned by a Rails-API app too.

@claudiob claudiob and 1 other commented on an outdated diff May 15, 2015
...s/test_unit/scaffold/templates/api_functional_test.rb
+ 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
@claudiob
claudiob May 15, 2015 Member

And this would say assert_response 200 (see comment above)

@spastorino
spastorino May 15, 2015 Member

Done too :), thanks for the feedback

@andrewcallahan

Every time I make a new rails API I have to add the rack-cors gem and configuration files to the app so that I can make cross origin requests from the browser. Since cross origin requests are an extremely common, if not universal, use case for APIs, I feel like it would be nice if this just came baked in so that 'it just works' when you make a new Rails API and start making calls from the browser. Thoughts?

@kenips
kenips commented May 15, 2015

Agree with @andrewcallahan on the CORS comments.

@todd
Contributor
todd commented May 15, 2015

I'm not sure I'd install rack-cors by default, but include it in the generated Gemfile as a comment, the same way bcrypt, unicorn, et al. are.

@kenips
kenips commented May 15, 2015

Yep that sounds really reasonable!

@yuki24
Contributor
yuki24 commented May 15, 2015

@andrewcallahan @kenips @todd @kenips There's already a pull request for adding CORS support to Rails: #19135

@kenips
kenips commented May 16, 2015

@yuki24 I thought #19135 is only concerning ActionDispatch::Static? That doesn't impact Controller response does it?

@kenips kenips commented on the diff May 16, 2015
guides/source/api_app.md
+
+**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:
+
+<shell>\
+\$ rails new my\_api --api\
+</shell>
@kenips
kenips May 16, 2015

A few places have formatting issues in this file not translating to md correctly.

@yuki24
Contributor
yuki24 commented May 16, 2015

@kenip ah, you are right, it's only for ActionDispatch::Static. But why would you need something else when you can add headers in the controller layer? Also, I don't think I understand CORS well, but why would you need to use CORS for APIs? CORS shouldn't be used as part of authentication.

@ekampp
ekampp commented May 16, 2015

@yuki24, CORS isn't about authentication. And you would want it for API self discovery.

And who ever does skip_authentication because they need to implement CORS shouldn't be paid for their work.

@kenips
kenips commented May 16, 2015

@yuki24 @ekampp you guys are correct, CORS shouldn't be used for authentication / authorization. The spec is designed for allowing request of resources from a different origin (domain). In this case it would matter IF your entire front-end application / SPA is hosted on a different domain, which is a configuration gaining popularity (say www.example.com for your front-end app / SPA and api.example.com for your Rails api app).

Previously we have been using the Rails assets pipeline to manage our front-end app / SPA and hence they would be under the same domain. Some front-end frameworks have even suggested to use of proxying /api to the backend to keep requests in the same domain. However, given the support of full "API only server" in here and returning assets management back to front-end frameworks, I can only see the trend of deploy front-end and back-end into separate domains rise.

@ekampp
ekampp commented May 16, 2015

I don't have an opinion on whether or not CORS should be in the Rails API. If it goes in, though, I do have an opinion about the quality.

AFAIK OPTIONS requests are made to all endpoints before they're used by the client. The current implementation for CORS doesn't play well with a complex routing map. Perhaps this could be something that comes with routing and can be overridden in controller if needed?

@yuki24 yuki24 commented on the diff May 16, 2015
...ties/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'
@yuki24
yuki24 May 16, 2015 Contributor

Any explanation on why AMS is the default?

@bf4
bf4 May 17, 2015 Contributor

It's also interesting since the test in this PR assumes no serializer, just to_json or to_xml on the model

I would rather have a first-class serializer interface in rails much like there is for renderers. Right now, that interface is a private-looking method :_render_with_renderer_json or :_render_option_json, (e.g. see ActionController:: Serialization)

to wit:

modifying a renderer is pretty simple:

# https://github.com/rails/rails/blob/4-2-stable/actionpack/lib/action_controller/metal/renderers.rb#L66-L128
# https://github.com/rails/rails/blob/4-2-stable//actionview/lib/action_view/renderer/renderer.rb#L32
 ActionController::Renderers.remove :json
  ActionController::Renderers.add :json do |json, options|
    if !json.is_a?(String)
      # changed from
      # json = json.to_json(options)
      # changed to
      json = json.as_json(options) if json.respond_to?(:as_json)
      json = JSON.pretty_generate(json, options)
    end

    if options[:callback].present?
      if content_type.nil? || content_type == Mime::JSON
        self.content_type = Mime::JS
      end

      "/**/#{options[:callback]}(#{json})"
    else
      self.content_type ||= Mime::JSON
      json
    end
  end

whereas creating a serializer is more complicated and less obvious

# When Rails renders JSON, it calls the json renderer method
# "_render_with_renderer_#{key = json}"
# https://github.com/rails/rails/blob/8c2c1bccccb3ea1f22503a184429d94193a1d9aa/actionpack/lib/action_controller/metal/renderers.rb#L44-L45
# which is defined in AMS https://github.com/rails-api/active_model_serializers/blob/1577969cb76309d1f48c68facc73e44c49489744/lib/action_controller/serialization.rb#L36-L37
    [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
      define_method renderer_method do |resource, options|
          super(adapter, options)
      end
    end

which is really hard to find in the source code... since it calls "_render_with_renderer_#{key}" where the key is json...

# https://github.com/rails/rails/blob/8c2c1bccccb3ea1f22503a184429d94193a1d9aa/actionpack/lib/action_controller/metal/renderers.rb#L44-L45
   def _render_to_body_with_renderer(options)
      _renderers.each do |name|
        if options.key?(name)
          _process_options(options)
          method_name = Renderers._render_with_renderer_method_name(name)
          return send(method_name, options.delete(name), options)
        end
      end
      nil
    end

    # A Set containing renderer names that correspond to available renderer procs.
    # Default values are <tt>:json</tt>, <tt>:js</tt>, <tt>:xml</tt>.
    RENDERERS = Set.new

    def self._render_with_renderer_method_name(key)
      "_render_with_renderer_#{key}"
    end

and AMS hooks into this by basically by taking the render json: @model which would normally hit the json renderer with @model.to_json and replace @model with something like ModelSerializer.new(@model) that gets to_json called on it....

So, maybe the Renderer could

    add :json do |json, options|
-      json = json.to_json(options) unless json.kind_of?(String)
+      json = serializer_for(json).to_json(options) unless json.kind_of?(String)

so that you'd have maybe a serializer registry like for mime-types or just a template method a controller could override

def serializer_for(model)
  ActiveModel::Serializer::Adapter.create(
    ActiveModel::Serializer.serializer_for(resource).new(resource, serializer_opts),
    adapter_opts
  ) 
end

or

 ActionController::Serializers.register :user, UserSerializer, only: [:json]
@spastorino
spastorino May 26, 2015 Member

@yuki24 it is the default for now because it is historically much more used in conjunction with Rails API. Jbuilder is mainly used when apps needs an API as a side of the server rendered app and not as an API only thing.

Rails API will work with both. Jbuilder will be just commented out.

@bf4 it's an interesting discussion but I'd said it's better to discuss after merging. Feel free to send an email to the Rails mailing list and/or provide a PR if it's easier for you.

@yuki24
yuki24 May 28, 2015 Contributor

@spastorino does popularity matter?

I think @paulwalker already explained what I wanted to say better, but let me describe what is more specific.

  • AMS has finally seen fragment cache support just 3 months ago when jbuilder has support even for russian-doll caching since 2013.
  • AMS doesn't know about view_context which jbuilder does have access to.
  • A false performance benchmark has been there on the internet, a more realistic benchmark shows that there's only about 20% difference between them. What's interesting is that the 20% comes from implicit rendering which oder is always O(1). This means in a real-world app, the difference would be tiny.
  • I really like implementing versioning with variant (take a look at versioncake as an example). WIth AMS, I can't do anything like that.
  • Hard to understand APIs. 0.8, 0.9 and 0.10 all have different interfaces and it's hard to catch up. jbuilder is much more consistent.
  • I respect the maintainers, but there are 100+ ASM issues when jbuilder only has 4.
  • The fact that jbuilder is mainly used when apps need an API as a side of the server rendered app doesn't necessarily mean you shouldn't use it for API-only apps.

I can come up with a lot of cases AMS doesn't cover, but I can't find any reasons why jbuilder shouldn't be the default. I would like to hear more about real-world examples that jbuilder doesn't work and AWS does better than jbuilder. Although, I'm not interested in hearing opinions like "jbuilder looks weird" or "AMS is more OO-ed". These are not real issues, but just opinions.

@spastorino
spastorino May 28, 2015 Member

@yuki24 the important thing to me is to be able to quickly switch into both.
The good thing AMS have is that you don't want to implement every single serialization point. You just define the serializers and relations and you don't have to build (and possibly make a mistake) on every single possible result. And even more you could switch formats just with a configuration.

@yuki24
yuki24 May 28, 2015 Contributor

you don't want to implement every single serialization point.

I don't understand this. Can you give me some examples? You can use json.render! partial: ... and I don't think you have to implement every single serialization point.

@spastorino
spastorino May 28, 2015 Member

If you have 6 models you need to implement 6 partials, right?.
In AMS you do not implement the format.

@yuki24
yuki24 May 28, 2015 Contributor

But even if you used AMS, you would still need to implement 6 serializers when you have 6 models. Am I missing something? Would you only need one serializer for 6 models?

@spastorino
spastorino May 28, 2015 Member

The implementation of each serializer is basically describing what you need to be serialized in a declarative way. You're not defining a format. You just say serialize :title, :body, with associations with :comments, :author, etc and you get a thing formatted for instance in JSON API way http://jsonapi.org/ and I can even change to another format without changing the code.

If you were doing that with a template based library you need to explicitly put all the code to generate each tag, attribute, value, etc and once you put that code you can't change the format.
And it's also error prone because you can generate an invalid format.

@yuki24
yuki24 May 29, 2015 Contributor

@spastorino ok, I kind of like the idea. Then can we also add a feature like implicit serialization? I want to say render @resource, serializer: ... without explicitly specifying anything like :json or :xml since I wouldn't be able to take advantage of being format-agnostic unless you would need to return multiple formats. Also it should be possible to change AMS adapter on the fly. I can imagine people would want to use one serializer but return JSON data (JSON and JSON_API) in a different structure, depending on the request for transition purposes (e.g. iOS apps).

I have a feeling that you are assuming that new apps would return JSON data that follows the JSON API conversions. I understand http://jsonapi.org is just an example, but it seems like the new version of AMS is deeply tied into one specification. Are there any other specs out there already? If yes, AMS should be generalized so others can easily integrate with it. Right now I only see JSON and JSON_API adapters. If there was only a limited number of choices or just one, the benefit of being format-agnostic would be quite low.

@yuki24
Contributor
yuki24 commented May 16, 2015

@kenips understood. Thanks for the clarification.

@kaspth kaspth and 2 others commented on an outdated diff May 18, 2015
...rails/scaffold_controller/templates/api_controller.rb
+ end
+
+ # PATCH/PUT <%= route_url %>/1
+ def update
+ if @<%= orm_instance.update("#{singular_table_name}_params") %>
+ render json: <%= "@#{singular_table_name}" %>
+ else
+ render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity
+ end
+ end
+
+ # DELETE <%= route_url %>/1
+ def destroy
+ @<%= orm_instance.destroy %>
+
+ head :no_content
@kaspth
kaspth May 18, 2015 Member

Rails master implicitly renders head :no_content if no template is found, so we don't need this line 😄

@spastorino
spastorino May 18, 2015 Member

@kaspth when has that changed?. Maybe I need to rebase to get that behavior?.

@rafaelfranca
rafaelfranca May 18, 2015 Member

Some months ago.

@spastorino
spastorino May 19, 2015 Member

Ahhh I wasn't getting what you meant exactly. The thing is ImplicitRender module is not included in Rails API. ImplicitRender only deals with templates and Rails API + AMS does not use templates so ImplicitRender as is today doesn't make sense for Rails API. We could adapt it to work I guess and that's part of some work that needs to be done in the future.
When we make ImplicitRender work with Rails API we should remove this line.
Thanks for reviewing guys :).

@miguelsan

This will be a huge milestone and a brilliant move for Rails!
We've been investing into a microservices architecture and this is a real MUST for the near future. We even started trying other frameworks on some parts of our setup because of the hurdles we found in Rails until now, a pity.
CORS should be in too, at least seamless compatibility with rack-cors or similar.
Also some kind of convention (as it was done with i18n, for instance) is needed on both directions of the interface. json-api-client comes to me as an example of the serialization/deserialization that should be possible out of the box.

@uberllama
Contributor

Hey guys, I've snagged this code in initializers/wrap_parameters.rb to convert incoming camelCase params into the expected snake case.

# Convert camelCase parameters to snake_case, allowing standard JSON API posts.
module ActionController
  module ParamsNormalizer
    extend ActiveSupport::Concern

    def process_action(*args)
      deep_underscore_params!(request.request_parameters)
      super
    end

    private
      def deep_underscore_params!(val)
        case val
        when Array
          val.map {|v| deep_underscore_params! v }
        when Hash
          val.keys.each do |k, v = val[k]|
            val.delete k
            val[k.underscore] = deep_underscore_params!(v)
          end
          val
        else
          val
        end
      end
  end
end

# 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)
  include ::ActionController::ParamsNormalizer
end

It makes me feel gross, like I've eaten nothing but double downs for a week straight. But it does the job in facilitating Rails as a standard JSON API. Ideally this would be a single line config, just like Jbuilder.key_format camelize: :lower.

Thoughts?

@ekampp
ekampp commented May 22, 2015

Not sure it's relevant for this discussion? Perhaps it's better suited in a separate issue?

@spastorino
Member

Yep and please feel free to add all the issues, to send emails to Rails mailing list with proposals, etc after this PR is merged.

@ekampp ekampp commented on the diff May 27, 2015
actionpack/lib/action_controller.rb
@@ -15,7 +16,6 @@ module ActionController
autoload :FormBuilder
autoload_under "metal" do
- autoload :Compatibility
@ekampp
ekampp May 27, 2015

Why remove Compatability?

@spastorino
spastorino May 27, 2015 Member

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

@ekampp ekampp and 1 other commented on an outdated diff May 27, 2015
actionpack/lib/action_controller/api.rb
+ 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
@ekampp
ekampp May 27, 2015

Is Rails standard not to have a trailing comma in a multiline list?

@dhh
dhh May 29, 2015 Member

Yes. No trailing comma.

@dhh dhh commented on the diff May 28, 2015
actionpack/lib/action_controller/api.rb
@@ -0,0 +1,140 @@
+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,
@dhh
dhh May 28, 2015 Member

We still do allow template rendering for jbuilder I suppose?

@spastorino
spastorino May 28, 2015 Member

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

@dhh dhh and 1 other commented on an outdated diff May 29, 2015
actionpack/lib/action_controller/api.rb
+ # 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.
@dhh
dhh May 29, 2015 Member

We should mention that you can also just do nothing, and then we'll return 204.

@spastorino
spastorino May 29, 2015 Member

If we do not add a render or redirect we would get a 200 OK.
That's because ImplicitRender is the module that returns 204 and it's not included in Rails API because it's main purpose is to deal with templates.

@dhh dhh commented on the diff May 29, 2015
railties/lib/rails/generators/rails/app/app_generator.rb
+ remove_dir 'vendor/assets'
+ end
+ end
+
+ def delete_app_helpers_if_api_option
+ if options[:api]
+ remove_dir 'app/helpers'
+ remove_dir 'test/helpers'
+ end
+ end
+
+ def delete_app_views_if_api_option
+ if options[:api]
+ remove_dir 'app/views'
+ end
+ end
@dhh
dhh May 29, 2015 Member

So how does all this work if you're using jbuilder?

@spastorino
spastorino May 29, 2015 Member

app/views is generated by jbuilder itself when you run the generators for the first time.

@dhh dhh and 1 other commented on an outdated diff May 29, 2015
...p/templates/config/initializers/wrap_parameters.rb.tt
@@ -7,6 +8,7 @@
ActiveSupport.on_load(:action_controller) do
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
end
+<%- end -%>
@dhh
dhh May 29, 2015 Member

Why isn't this needed for APIs?

@spastorino
spastorino Jun 2, 2015 Member

👍 I'm going to include this.

@spastorino
spastorino Jun 5, 2015 Member

This was added 👍

@spastorino
Member

@dhh everything was done, I think we are ready to merge now. There are some engines issues but could be tackled afterwards.

@dhh
Member
dhh commented Jun 7, 2015

Make it so!

On Friday, June 5, 2015, Santiago Pastorino notifications@github.com
wrote:

@dhh https://github.com/dhh everything was done, I think we are ready
to merge now. There are some engines issues but could be tackled afterwards.


Reply to this email directly or view it on GitHub
#19832 (comment).

spastorino and others added some commits Apr 15, 2015
@spastorino spastorino Add ActionController API functionality 032778e
@spastorino spastorino Move Model test class inside RenderersApiController namespace 2d86b6d
@spastorino spastorino Add config.api_only option to application and remove appropriate midd…
…leware when true
135c059
@spastorino spastorino Add AC::API + its middleware stack integration test 212a099
@spastorino spastorino Add ApiPublicException middleware 3adb5ea
@spastorino spastorino rails new --api generates an api app skeleton 2a9cf48
@spastorino spastorino Generate appropriate initializers for an api app c09a401
@spastorino spastorino api option implies skipping javascript & sprockets e8915d0
@spastorino spastorino Do not generate app/assets directory for api apps 1718683
@spastorino spastorino Do not generate app/helpers directory for api apps 9b66071
@spastorino spastorino Do not generate app/views directory for api apps 3847e48
@spastorino spastorino Do not generate test/helpers directory for api apps 7b47e42
@spastorino spastorino Add api_only option to Generators 101df20
@spastorino spastorino Hide assets, helper, css and js namespaces for api only apps c190352
@spastorino spastorino Api apps scaffold generates routes without new and edit actions e8100fc
@spastorino spastorino config.generators.api_only = true set rails api option on generators 20939b3
@spastorino spastorino API apps scaffold generator generates an apropriate controller d4fe23c
@spastorino spastorino Add api scaffold test for route, controller and its tests 451b1e3
@spastorino spastorino Api apps scaffold does not generate views 6d2b405
@spastorino spastorino Api apps scaffold does not generate assets 94fdba9
@spastorino spastorino Api apps scaffold does not generate helpers e5b6188
@spastorino spastorino Do not generate lib/assets directory for api apps decc4e8
@spastorino spastorino config.api_only = true implies config.generators.api_only = true 98a9936
@spastorino spastorino Add config.api_only = true to config/application.rb when using rails …
…new --api
e9e94c1
@spastorino spastorino Add test to show api only apps allow overriding generator options 7d17269
@spastorino spastorino Disable jbuilder for Rails API apps, meanwhile it doesn't play nicely 3d37300
@spastorino spastorino Use nex hash syntax on tests 4c1b437
@spastorino spastorino Adhere to Rails convention for private indentation 4204778
@spastorino spastorino Remove api_rendering is not needed 38818c9
@spastorino spastorino Remove Unneeded ApiPublicExceptions middleware, PublicExceptions alre…
…ady does the work
e7b89f1
@spastorino spastorino Refactor internal? to query internal_controller? and internal_asset? …
…methods
b643d7a
@spastorino spastorino Use new hash syntax 440b334
@spastorino spastorino Remove extra whitespaces 099055d
@jmbejar @spastorino jmbejar Routes resources avoid :new and :edit endpoints if api_only is enabled 674dab3
@jmbejar @spastorino jmbejar Exclude cache_digests:dependencies rake task in api app e380887
@jmbejar @spastorino jmbejar Remove Compatibility module since we don't remember why it was added 😄 fd25085
@jmbejar @spastorino jmbejar Api only apps should include tmp and vendor folders fa11f10
@jmbejar @spastorino jmbejar Do not say that Api Controllers are faster than regular ones in docs 2487bfb
@jmbejar @spastorino jmbejar Rename test methods in api conditional get controller tests 08cfe34
@jmbejar @spastorino jmbejar Revert changes related with api apps in RouteWrapper
See the following commit to have context about this change:
rails@757a2bc
11c71b2
@spastorino spastorino Fix MimeResponds example in AC::API documentation 7db63f3
@spastorino spastorino Remove unneeded option from ResourceRouteGenerator b6c270f
@spastorino spastorino Fix class_option description for api generators 846f352
@spastorino spastorino Fix scaffold generator test for resource routes dc4c68a
@spastorino spastorino Add AMS 0.10.0.rc1 by default for api apps 511c33a
@spastorino spastorino http only => API only 03b576e
@spastorino spastorino Document Generators.api_only! method 37507d3
@spastorino spastorino Add API only apps guide b7494b6
@spastorino spastorino Add CHANGELOG entries for API apps functionality f3df216
@spastorino spastorino Change guide heading from - to = 564d029
@spastorino spastorino It's rails new my_api --api 80702b7
@spastorino spastorino Make Rails API apps return the full resource on update 72d0784
@jmbejar @spastorino jmbejar Add rake-cors gem (commented) in Gemfile for rails api apps ebcc15c
@jmbejar @spastorino jmbejar Include ParamsWrapper in AC::API
ParamsWrapper was initially removed from API controllers according to
the following discusision:
rails-api/rails-api#33

However, we're including it again so Rails API devs can decide
whether to enable or disable it.
a2c9a73
@jmbejar @spastorino jmbejar Enable wrap_parameter by default in rails api applications cf9f2f3
@jmbejar @spastorino jmbejar Checking if controller responds to wrap_parameter is not longer required afc78e7
@jmbejar @spastorino jmbejar Add test coverage for implicit render in empty actions 8d3e6e5
@jmbejar @spastorino jmbejar Return 204 if render is not called in API controllers 6c16577
@spastorino spastorino Mention that doing nothing in Rails API controllers returns 204 1fd42f3
@spastorino spastorino head :no_content is implicitly called 51d5d62
@spastorino spastorino merged commit 21f7bcb into rails:master Jun 11, 2015

1 check was pending

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details
@bf4
Contributor
bf4 commented Jun 11, 2015

@spastorino Awesome! Would you be interested in me writing a PR along the lines of https://groups.google.com/d/topic/rubyonrails-core/K8t4-DZ_DkQ/discussion (as discussed above)?

@joaomdmoura

Amazing! Really happy you made it! Let's keep up the good work!

@bf4 bf4 referenced this pull request in rails-api/rails-api Jun 11, 2015
Merged

Announce merge of Rails::API into Rails #201

@claudiob
Member

👏 👏 👏 👏 👏 👏

@ajoy39
ajoy39 commented Jun 12, 2015

Great work! 


Sent from Mailbox

On Thu, Jun 11, 2015 at 4:04 PM, Claudio B. notifications@github.com
wrote:

👏 👏 👏 👏 👏 👏

Reply to this email directly or view it on GitHub:
#19832 (comment)

@dhh
Member
dhh commented Jun 12, 2015

🤘

@gfvcastro
Contributor

Excellent. 👏 🎉

@vestimir

🖖

@robin850
Member

(ping #20612)

@yosriady

Brilliant! 🎊

@robertomiranda
Contributor

❤️

@Justin-lu

🤘

@spuyet
spuyet commented Jun 29, 2015

👍

@aalizzwell58

Good job 👍

@Theminijohn

👍

@hapiben
hapiben commented Jun 30, 2015

Sweet!

@seemsindie

Ok, i have to ask, how i can use this now?

@bf4
Contributor
bf4 commented Jul 1, 2015

@dhh @spastorino lock comments, please?

@robin850
Member
robin850 commented Jul 1, 2015

@seemsindie : You have to use Rails' master ; this is not yet released.

@bf4 : Yes, you're right!

@robin850 robin850 locked and limited conversation to collaborators Jul 1, 2015
@spastorino spastorino unlocked this conversation Jul 2, 2015
@spastorino spastorino locked and limited conversation to collaborators Jul 2, 2015
@dhh dhh commented on the diff Dec 4, 2015
...s/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
@dhh
dhh Dec 4, 2015 Member

We should replace this with an ActionDispatch::IntegrationTest generator instead. ActionController::TestCase is legacy ware.

@spastorino
spastorino Dec 7, 2015 Member

👍 will take a look at it

@rafaelfranca rafaelfranca modified the milestone: 5.0.0 [temp], 5.0.0 Dec 30, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.