Change HTTP Response Code using Rails and RABL - ruby-on-rails

I am using RABL for my REST API. However, currently, even if there is an error, the HTTP response code I get is 200. Is there a way to change this to a 4xx level code? It would be great for my client to read.
I am not using the render template: "api/v1/error.rabl” because if I do use it, there is an after filter that sets CORS access control headers which does not get executed.
I tried searching, but didn’t find a way.

Seems like this answer provides a good solution. For example,
class FoosController < ActionController::Base
respond_to :json
class ApiResponder < ActionController::Responder
def to_format
case
when has_errors?
controller.response.status = :unprocessable_entity
when get? && !resource
controller.response.status = :not_found
when post?
controller.response.status = :created
end
default_render
rescue ActionView::MissingTemplate => e
api_behavior(e)
end
end
self.responder = ApiResponder
def show
#foo = Foo.find(params[:id])
#foo.errors.add(:base, "foo bar") # e.g. of errors on the object
respond_with #foo
end
end
Although the wiki has a warning that "setting controller.response.status is like a default value", respond_with or render shouldn't explicitly set status and it seems like a call to render "my_template" triggers that 200 you mentioned. If you do want to reuse a single error template, something like the author's suggested template, this works:
...
self.responder = ApiResponder
def show
#foo = Foo.find(params[:id])
#foo.errors.add(:base, "foo bar") # e.g. of errors on the object
respond_with #foo do |format|
if #foo.errors.empty?
flash[:notice] = 'Foo was successfully created.'
else
format.json { render "error" }
end
end
end
# error.rabl
object false
node :errors do
{
:message => "Sorry, fubar'ed",
:code => 12345
}
end
This works with actions that write as well as read. The original bug discussion also has some interesting examples.

Related

Rails: Render JSON after Controller action results in "missing a template for this request format and variant" error

I have this simple Admin controller and I'm trying to display a message after the #reset action is called to give the admin feedback when the action is completed (as it executes de seed.rb file)
The action performs just fine, but I can't get it to display the msg and I get this message in the server log:
ActionController::UnknownFormat (Api::V1::AdminController#reset is missing a template for this request format and variant.
request.formats: ["application/json"]
request.variant: []):
class Api::V1::AdminController < Api::V1::BaseController
before_action :initialize
def seed
#builder.process_file
end
def reset
Rails.application.load_seed do
msg = { :status => "ok" }
respond_to do |format|
format.json { render :json => msg }
end
end
end
private
def initialize
#builder = PaymentBuilder.new
end
end
What am I doing wrong?
EDIT:
I had to declare respond_to :json at the class level of the controller and change the action block to
def reset
Rails.application.load_seed
msg = { :reset => "OK" }
respond_with do |format|
format.json { render :json => msg }
end
end
And it renders fine now.
I guess this is happening because you are rendering inside of the block, I think you can not do that in that way

Returning a 404 if a user doesn't exist

Why would this not generate a 404 response?
def index
if find_user
#documents = #client.documents
respond_to do |format|
format.html
format.atom { render layout: false }
end
else
flash[:error] = "#{params[:client_code]} is not a client."
render 'error', status: '404'
end
end
def find_user
#client = User.find_by_client_code(params[:client_code]) if valid_user?
end
def valid_user?
User.all.each.map(&:client_code).include?(params[:client_code])
end
Like, if the code is incorrect it should return a 404, right? And not an exception? Can't quite get it to work.
EDIT: sorry, here's the error:
An ActionView::MissingTemplate occurred in share#index:
* Parameters : {"controller"=>"share", "action"=>"index", "client_code"=>"ampNDHEDD", "format"=>"atom"}
If you don't use the valid_user? or the find_user methods elsewhere, they can be removed and you could do the following
def index
#client = User.find_by_client_code(params[:client_code]) # returns nil or a record
if #client
#documents = #client.documents
respond_to do |format|
format.html
format.atom { render layout: false }
end
else
flash[:error] = "#{params[:client_code]} is not a client."
render status: 404
end
end
However, your previous comment states you're getting a template error which indicates that you may not have an index.atom template available to render.
do this
def index
if find_user
#documents = #client.documents
respond_to do |format|
format.html
format.atom { render layout: false }
end
else
flash[:error] = "#{params[:client_code]} is not a client."
raise ActionController::RoutingError.new('Not Found')
end
end
def find_user
#client = User.find_by_client_code(params[:client_code]) if valid_user?
end
def valid_user?
User.where(client_code: params[:client_code]).present?
// User.all.each.map(&:client_code).include?(params[:client_code]) // this is terrible (Read my comment )
end
First, your valid_user? method is a really bad idea - it loads the entire user database just to see if the code is present... which is the same result as what User.find_by_client_code does, but without loading every record! I'd just nuc the method and the if clause. If there is no matching record, it should return nil, which should take the else path and render the error.
As for why it's not rendering the error... I'm not sure if the atom format has anything to do with it, but when code doesn't branch the way I expect, I always put a Rails.logger.debug ... before the branch I have an issue with, and/or put a bad method in the branch it's supposed to take. That helps narrow it down. :D

Respond_with templates and status codes

There are some instances where I need to both have a template and return error codes when using respond_with in Rails 3.
I have a before filter that is as follows:
def ensure_premium
respond_with("Must be a premium user!", status: 401, location: nil) unless current_user.is_premium?
end
and a create action that does the following:
def create
#wait_list = #hangout.wait_lists.find_or_create_by(user_id: current_user.id)
respond_with(#wait_list) do |format|
format.json {render 'create', status: 201}
end
end
Even though the before filter trips, it still tries to render the template which results in an error. What am I missing to get it to return the right error and status code and not render the template?
You have multiple respond_with's for the create action. But I think more critically, you might need:
def ensure_premium
respond_with :json => {:error => "Must be a premium user!", :status => :unauthorized } unless....
I don't think this is the problem, but make sure in your controller you have
class SomeController < ApplicationController
respond_to :json
I ended up going a different route completely so this question is no longer valid.

How to return correct HTTP error codes from Ruby on Rails application

I have RoR 3.0 web application which is acting as an OAuth API provider. Now, in API I'd like to return correct HTTP error codes to the API consumer. How do I do this?
Here is example:
def destroy_oauth
#item = Item.find(params[:id])
if(!#item.nil? && #item.user_id == current_user.id)
#item.destroy
respond_to do |format|
format.js
format.xml
end
else
raise ActionController::RoutingError.new('Forbidden')
end
end
So, in case of error I'm trying to return Forbidden 403 code. Still, when running this I'm getting always 404 Not Found returned. How do I return the correct code?
Or is this somehow webserver configurable thing?
When you're just giving a status code and there is no body, a convenient way is
head 403
This method also accepts the symbolic names for status codes, such as
head :forbidden
You should render page with correct status.
render(:file => File.join(Rails.root, 'public/403.html'), :status => 403, :layout => false)
According to ActionController::Head docs just use this pattern in actions
return head([status]) if/unless [some condition here]
Example:
return head(:gone) if #record.deleted?
return head(:forbidden) unless #user.owns?(#record)
return is used to make sure that no remaining code in the action will be run.
I think you have two problems here: first is that your #item = Item.find(params[:id]) line is raising 404 and execution never gets to where intended (if statement). Second is that you are raising exceptions and never catch them. Try:
def destroy_oauth
begin
#item = Item.find(params[:id])
if(!#item.nil? && #item.user_id == current_user.id)
#item.destroy
respond_to do |format|
format.js
format.xml
end
else
raise ActionController::RoutingError.new('Forbidden')
end
rescue ActiveRecord::ResourceNotFound
redirect_to :action => 'not_found', :status => 404 # do whatever you want here
rescue ActionController::RoutingError
redirect_to :action => 'forbidden', :status => 403 # do whatever you want here
end
end
Something along those lines, but you also mentioned that you are building the API, so when you are rescuing the error, you may want to render xml error info. Something like:
# in application_controller.rb
rescue_from ActionController::RoutingError, :with => :render_forbidden_error
private
def render_forbidden_error(e)
render :status => e.status, :xml => e
end
Good luck. Udachi.
well, you can use
:status =>500
But, In default Rails take care of the error type rendering itself.
Errors default pages are in the public directory. 500.html,404.html etc..
For more information on :status , how to use it click here

How to redirect to a 404 in Rails?

I'd like to 'fake' a 404 page in Rails. In PHP, I would just send a header with the error code as such:
header("HTTP/1.0 404 Not Found");
How is that done with Rails?
Don't render 404 yourself, there's no reason to; Rails has this functionality built in already. If you want to show a 404 page, create a render_404 method (or not_found as I called it) in ApplicationController like this:
def not_found
raise ActionController::RoutingError.new('Not Found')
end
Rails also handles AbstractController::ActionNotFound, and ActiveRecord::RecordNotFound the same way.
This does two things better:
1) It uses Rails' built in rescue_from handler to render the 404 page, and
2) it interrupts the execution of your code, letting you do nice things like:
user = User.find_by_email(params[:email]) or not_found
user.do_something!
without having to write ugly conditional statements.
As a bonus, it's also super easy to handle in tests. For example, in an rspec integration test:
# RSpec 1
lambda {
visit '/something/you/want/to/404'
}.should raise_error(ActionController::RoutingError)
# RSpec 2+
expect {
get '/something/you/want/to/404'
}.to raise_error(ActionController::RoutingError)
And minitest:
assert_raises(ActionController::RoutingError) do
get '/something/you/want/to/404'
end
OR refer more info from Rails render 404 not found from a controller action
HTTP 404 Status
To return a 404 header, just use the :status option for the render method.
def action
# here the code
render :status => 404
end
If you want to render the standard 404 page you can extract the feature in a method.
def render_404
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
format.xml { head :not_found }
format.any { head :not_found }
end
end
and call it in your action
def action
# here the code
render_404
end
If you want the action to render the error page and stop, simply use a return statement.
def action
render_404 and return if params[:something].blank?
# here the code that will never be executed
end
ActiveRecord and HTTP 404
Also remember that Rails rescues some ActiveRecord errors, such as the ActiveRecord::RecordNotFound displaying the 404 error page.
It means you don't need to rescue this action yourself
def show
user = User.find(params[:id])
end
User.find raises an ActiveRecord::RecordNotFound when the user doesn't exist. This is a very powerful feature. Look at the following code
def show
user = User.find_by_email(params[:email]) or raise("not found")
# ...
end
You can simplify it by delegating to Rails the check. Simply use the bang version.
def show
user = User.find_by_email!(params[:email])
# ...
end
The newly Selected answer submitted by Steven Soroka is close, but not complete. The test itself hides the fact that this is not returning a true 404 - it's returning a status of 200 - "success". The original answer was closer, but attempted to render the layout as if no failure had occurred. This fixes everything:
render :text => 'Not Found', :status => '404'
Here's a typical test set of mine for something I expect to return 404, using RSpec and Shoulda matchers:
describe "user view" do
before do
get :show, :id => 'nonsense'
end
it { should_not assign_to :user }
it { should respond_with :not_found }
it { should respond_with_content_type :html }
it { should_not render_template :show }
it { should_not render_with_layout }
it { should_not set_the_flash }
end
This healthy paranoia allowed me to spot the content-type mismatch when everything else looked peachy :) I check for all these elements: assigned variables, response code, response content type, template rendered, layout rendered, flash messages.
I'll skip the content type check on applications that are strictly html...sometimes. After all, "a skeptic checks ALL the drawers" :)
http://dilbert.com/strips/comic/1998-01-20/
FYI: I don't recommend testing for things that are happening in the controller, ie "should_raise". What you care about is the output. My tests above allowed me to try various solutions, and the tests remain the same whether the solution is raising an exception, special rendering, etc.
You could also use the render file:
render file: "#{Rails.root}/public/404.html", layout: false, status: 404
Where you can choose to use the layout or not.
Another option is to use the Exceptions to control it:
raise ActiveRecord::RecordNotFound, "Record not found."
The selected answer doesn't work in Rails 3.1+ as the error handler was moved to a middleware (see github issue).
Here's the solution I found which I'm pretty happy with.
In ApplicationController:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :handle_exception
end
def not_found
raise ActionController::RoutingError.new('Not Found')
end
def handle_exception(exception=nil)
if exception
logger = Logger.new(STDOUT)
logger.debug "Exception Message: #{exception.message} \n"
logger.debug "Exception Class: #{exception.class} \n"
logger.debug "Exception Backtrace: \n"
logger.debug exception.backtrace.join("\n")
if [ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction].include?(exception.class)
return render_404
else
return render_500
end
end
end
def render_404
respond_to do |format|
format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500
respond_to do |format|
format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
and in application.rb:
config.after_initialize do |app|
app.routes.append{ match '*a', :to => 'application#not_found' } unless config.consider_all_requests_local
end
And in my resources (show, edit, update, delete):
#resource = Resource.find(params[:id]) or not_found
This could certainly be improved, but at least, I have different views for not_found and internal_error without overriding core Rails functions.
these will help you...
Application Controller
class ApplicationController < ActionController::Base
protect_from_forgery
unless Rails.application.config.consider_all_requests_local
rescue_from ActionController::RoutingError, ActionController::UnknownController, ::AbstractController::ActionNotFound, ActiveRecord::RecordNotFound, with: lambda { |exception| render_error 404, exception }
end
private
def render_error(status, exception)
Rails.logger.error status.to_s + " " + exception.message.to_s
Rails.logger.error exception.backtrace.join("\n")
respond_to do |format|
format.html { render template: "errors/error_#{status}",status: status }
format.all { render nothing: true, status: status }
end
end
end
Errors controller
class ErrorsController < ApplicationController
def error_404
#not_found_path = params[:not_found]
end
end
views/errors/error_404.html.haml
.site
.services-page
.error-template
%h1
Oops!
%h2
404 Not Found
.error-details
Sorry, an error has occured, Requested page not found!
You tried to access '#{#not_found_path}', which is not a valid page.
.error-actions
%a.button_simple_orange.btn.btn-primary.btn-lg{href: root_path}
%span.glyphicon.glyphicon-home
Take Me Home
routes.rb
get '*unmatched_route', to: 'main#not_found'
main_controller.rb
def not_found
render :file => "#{Rails.root}/public/404.html", :status => 404, :layout => false
end
<%= render file: 'public/404', status: 404, formats: [:html] %>
just add this to the page you want to render to the 404 error page and you are done.
I wanted to throw a 'normal' 404 for any logged in user that isn't an admin, so I ended up writing something like this in Rails 5:
class AdminController < ApplicationController
before_action :blackhole_admin
private
def blackhole_admin
return if current_user.admin?
raise ActionController::RoutingError, 'Not Found'
rescue ActionController::RoutingError
render file: "#{Rails.root}/public/404", layout: false, status: :not_found
end
end
Raising ActionController::RoutingError('not found') has always felt a little bit strange to me - in the case of an unauthenticated user, this error does not reflect reality - the route was found, the user is just not authenticated.
I happened across config.action_dispatch.rescue_responses and I think in some cases this is a more elegant solution to the stated problem:
# application.rb
config.action_dispatch.rescue_responses = {
'UnauthenticatedError' => :not_found
}
# my_controller.rb
before_action :verify_user_authentication
def verify_user_authentication
raise UnauthenticatedError if !user_authenticated?
end
What's nice about this approach is:
It hooks into the existing error handling middleware like a normal ActionController::RoutingError, but you get a more meaningful error message in dev environments
It will correctly set the status to whatever you specify in the rescue_responses hash (in this case 404 - not_found)
You don't have to write a not_found method that needs to be available everywhere.
To test the error handling, you can do something like this:
feature ErrorHandling do
before do
Rails.application.config.consider_all_requests_local = false
Rails.application.config.action_dispatch.show_exceptions = true
end
scenario 'renders not_found template' do
visit '/blah'
expect(page).to have_content "The page you were looking for doesn't exist."
end
end
If you want to handle different 404s in different ways, consider catching them in your controllers. This will allow you to do things like tracking the number of 404s generated by different user groups, have support interact with users to find out what went wrong / what part of the user experience might need tweaking, do A/B testing, etc.
I have here placed the base logic in ApplicationController, but it can also be placed in more specific controllers, to have special logic only for one controller.
The reason I am using an if with ENV['RESCUE_404'], is so I can test the raising of AR::RecordNotFound in isolation. In tests, I can set this ENV var to false, and my rescue_from would not fire. This way I can test the raising separate from the conditional 404 logic.
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :conditional_404_redirect if ENV['RESCUE_404']
private
def conditional_404_redirect
track_404(#current_user)
if #current_user.present?
redirect_to_user_home
else
redirect_to_front
end
end
end

Resources