respond to only json in rails - ruby-on-rails

In my rails app which is json only, I want to send a 406 code whenever someone calls my rails app with accept header set to anything except application/json. I also want it to send a 415 when I get the content type set to anything except application / json
My controllers have respond_to :json put on them. I only render json in all actions. However how do I ensure that I return error code 406/415 for all calls for anything that is called for all other accept headers/content-type and with format set to anything except json.
Eg. If I my resource is books/1 I want to allow
books/1.json or books/1 with application/json in accept header and content type
Any ideas on how I can do these two actions?

Basically, you can limit your responses in two ways.
First, there is respond_to for your controllers. This would automatically trigger a 406 Not Acceptable if a request for a format is made which is not defined.
Example:
class SomeController < ApplicationController
respond_to :json
def show
#record = Record.find params[:id]
respond_with #record
end
end
The other way would be to add a before_filter to check for the format and react accordingly.
Example:
class ApplicationController < ActionController::Base
before_filter :check_format
def check_format
render :nothing => true, :status => 406 unless params[:format] == 'json' || request.headers["Accept"] =~ /json/
end
end

You can do it with a before_filter in ApplicationController
before_filter :ensure_json_request
def ensure_json_request
return if params[:format] == "json" || request.headers["Accept"] =~ /json/
render :nothing => true, :status => 406
end

On rails 4.2+ respond_to has been removed, so unless you want to import the full responders gem just for this, your best bet is to roll your own. This is what I'm using in my rails 5 api:
class ApplicationController < ActionController::API
before_action :force_json
private
def force_json
# if params[_json] it means request was parsed as json
# if body.read.blank? there was no body (GET/DELETE) so content-type was meaningless anyway
head :not_acceptable unless params['_json'] || request.body.read.blank?
end
end

Related

Store response HTTP status code after exception in Rails

I'm developing a REST web service in Ruby on Rails.
After each request I would like to store in the database the response HTTP status code even in presence of some exception. How can I do that?
I have done these two attempts without success:
after_filter in application controller
class Api::ApiController < ActionController::Base
before_action :set_current_rest_request
after_filter :finalize_current_rest_request
private
def set_current_rest_request
#current_rest_request = RestRequest.new
#current_rest_request.request_at = DateTime.now
#current_rest_request.save
end
def finalize_current_rest_request
#current_rest_request.answer_at = DateTime.now
#current_rest_request.http_status_code = response.status
#current_rest_request.save
end
end
Doesn't work because finalize_current_rest_request is not called in case of exceptions
rescue_from in application controller
class Api::ApiController < ActionController::Base
before_action :set_current_rest_request
after_filter :finalize_current_rest_request
rescue_from Exception, :with => :handle_exception
private
def set_current_rest_request
#current_rest_request = RestRequest.new
#current_rest_request.request_at = DateTime.now
#current_rest_request.save
end
def finalize_current_rest_request
#current_rest_request.answer_at = DateTime.now
#current_rest_request.http_status_code = response.status
#current_rest_request.save
end
def handle_exception(exception)
finalize_current_rest_request
raise exception
end
end
Doesn't work because response.status is still 200 when I call finalize_current_rest_request inside handle_exception, before the raise of the exception
You need to wrap each action of the controller with a begin rescue block. Something like:
begin
respond_to do |format|
format.json { redirect_to foos_path, notice: 'Foo was successfully destroyed.' }
rescue
#current_rest_request.http_status_code = response.status
end
end
Normally behavior like this is left up to the logs but I am assuming you have a really good reason for doing this.
If you really want it to be dry as you mentioned you can put in your application controller:
rescue_from Exception, :with => :store_request
def store_request
current_rest_request = RestRequest.new
current_rest_request.request_at = DateTime.now
current_rest_request.http_status_code = response.status
current_rest_request.save
end
Note: It is often considered bad practice to blanket rescue in the application controller. I think the best way to actually handle this is to implement a comprehensive logging scheme.
I finally solved adding a middleware (here some datails on how rails middlewares work).
In detail, in config/application.rb, I added:
config.middleware.insert_before "Rails::Rack::Logger","StoreStatus"
the api controller was
class Api::ApiController < ActionController::Base
before_action :set_current_rest_request
private
def set_current_rest_request
#current_rest_request = RestRequest.new
request.env[:current_rest_request] = #current_rest_request
end
end
and I added the file lib/store_status.rb with the following code:
class StoreStatus
def initialize(app)
#app = app
end
def call(env)
data = #app.call(env)
if (env[:current_rest_request])
rest_request = env[:current_rest_request]
rest_request.http_status_code = data[0]
rest_request.save
end
data
end
end
Please notice that there may be some syntax error because this code has been obtained refactoring a code which contains other useless complexity for this question.

Respond with json format to all formats requests

Im implementing a Rest API on Ruby on Rails. So i want to respond to all requests in json format. I made this:
include ActionController::MimeResponds
before_filter :force_json
def force_json
response.format = "json"
#also tried
# response.content_type = Mime[:json]
end
Those two ways didn't worked. It gives me an html page with errors.
Also is there a way to implement this for the whole api and not for each class?
Thanks!
If you want it to happen application wide, you can do something like this in the application controller.
class ApplicationController < ActionController::Base
before_action :force_json
def force_json
request.format = :json
end
end
If you use the responders gem, you can define this at the top of your class:
class PostsController < ApplicationController
respond_to :json
...
Then this controller will respond using JSON by default.

Default client Content-Type under api-namespace in Rails?

How do I set a default Content-Type for all incoming requests to my Rails app under a given namespace?
In my case I have an /api namespace that always expects JSON, but I don't want to force the clients to always set it (they are of cause welcome to do so if they please in which case the Rails app should adhere to the set Content-Type). So it should just default to application/json.
This will make testing with curl etc A LOT EASIER.
How do I do that?
One way is to specify before action in API controller and set JSON format:
before_filter :default_request_format
private
def default_request_format
request.format = :json
end
or you can specify JSON format in routes.rb.
Let all your API-Controller inherit from a Api::Controller class, which only respond_to :json
Example:
class Api::Controller < ActionController::Base
respond_to :json
before_filter :default_request_format
private
def default_request_format
request.format = :json
end
end
Now your other Api Controllers can Inherit from it and will automatically respond with JSON.
For example you have a model called 'FooBar':
class Api::FooBarsController < Api::Controller
def index
#foobars = FooBar.all
respond_with(#foobars)
end
end

Disable auto rendering in rails

What I need is to disable the automatic page (HTML) rendering in rails and override it with a after_action method. What I'm trying to achieve is an equivalent of CakePHP $this->autoRender = false;
application_controller.rb
class ApplicationController < ActionController::Base
after_action :custom_render
layout nil # Tried this but didn't worked
def custom_render
render #[...]
end
end
some_controller.rb
class SomeController < ApplicationController
def index
# No rendering here
end
end
As shown in the code I tried to add a layout nil to prevent all actions from rendering, but that doesn't seem to affect the behaviour of the action.
Haven't checked whether it works with Rails 4, but this patch works for Rails 5.
According to the code of BasicImplicitRender and ImplicitRender, send_action of is BasicImplicitRender responsible for calling default_render
Documentation says:
For API controllers, the implicit response is always 204 No Content.
For all other controllers, we use ... heuristics to decide whether to
render a template, raise an error for a missing template, or respond with
204 No Content ...
So I suppose redefining default_render method will serve you purpose.
In your controller:
def a
# uses `default_render` unless you call `render` method explicitly
end
def b
render plain: 'Custom text for b' # `default_render` won't be called
end
private
# This does the trick
#
def default_render
render plain: 'Text'
end
You may also hack send_action just like it is done in Rails so as to even skip default_render call at all:
module ActionController
module BasicImplicitRender # :nodoc:
def send_action(method, *args)
# super.tap { default_render unless performed? }
super
end
end
end
To disable rendering (well return nothing) issue.
def index
render :nothing
end
But it's too late to do anything, as it will return response with empty body.
To disable layout:
def index
render layout: false
end
This will render you view without a layout, issue (render layout: 'my_custom_layout') to render default view but with different layout.
We don't know what you want, but the simplest solution is just to render a specific view, f.i.:
def index
render 'my_custom_file.'
end
There are really many options: http://guides.rubyonrails.org/layouts_and_rendering.html#using-render
EDIT - as requested in a comment
class ApplicationController < ActionController::Base
before_action :set_user_template
# ...
def set_user_template
template_name = current_user.template_name
self.class.layout "#{template_name}/application"
end
end

UnknownFormat in Devise::SessionsController#new

I have a Rails 4 app (that was upgraded from Rails 3) in which I decided to delete one of the controllers. I then moved the methods from that deleted controller to the ApplicationController, which included before_filter :authenticate_user!
Here's what my ApplicationController looks like now:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate_user!
respond_to :json
def index
gon.rabl
#user = current_user
gon.rabl "app/views/users/show.json.rabl", as: "current_user"
end
def markdown
require 'redcarpet'
renderer = Redcarpet::Render::HTML.new
extensions = {}
Redcarpet::Markdown.new(renderer, extensions)
end
helper_method :markdown
end
Now, I'm getting this error:
ActionController::UnknownFormat in Devise::SessionsController#new
I think this might be due to the fact that you have set your application controller to respond only to json. If your Devise Controller inherits from ApplicationController (I think this is the default), then it will expect to see a content-type: json header, or your urls must all end in .json
You shouldn't have the index method defined in application_controller. You should move it to the appropriate controller. If this is something you want to do before every action you might want to try something like this:
before_action :gon_user, only: :index
private
def gon_user
gon.rabl
#user = current_user
gon.rabl "app/views/users/show.json.rabl", as: "current_user"
end
Though i've to be honest that i'm not sure about the gon stuff, can't remember if it was for moving data from ruby to javascript or for responding to ajax/json request.
Thanks Slicedpan. Got me thinking about a
respond_to :json
Used in my Rails Application as an API with Angular. As in my Rails controllers I use for requests from my Angular Services.
respond_with
In my case I ended up adding html to the respond_to:
respond_to :json, :html
The Default Mime Types can be seen here:
http://apidock.com/rails/Mime

Resources