Rails 5: How to declare HEAD 200 in Rescue - ruby-on-rails

I have a webhook sent to a page on my app, and I am catching SOAP errors from processing the data, but I want to send to the webhook a 200 status. Is this possible in the controller?
controller
def example_method
...
rescue Savon::SOAPFault => error
...
# Respond to the HTTP POST request by giving the 'all clear'
head 200
raise
end

First off don't place soap calls directly into your controller - instead create a client class or service object that handles it.
Your controller should only really know that it calls some kind of service and that it may fail or succeed - if you are catching library specific errors in your controller you are on your way down the rabbit hole.
So how do you rescue exceptions in your controller?
You can rescue them inline:
class SomethingController
def do_something
begin
# Something.this_may_blow_up!
rescue SomeError => error
head :ok and return
end
end
end
The key here is and return which halts the controller flow and prevents double render errors.
The other method is by using rescue_from.
class ThingsController
rescue_from ActiveRecord::RecordNotFound do
render :not_found and return
end
def show
#thing = Thing.find(params[:id])
end
end
The above is an example of how you can create a custom 404 page per controller. What happens is that when the action on the controller is invoked it is wrapped in a begin..rescue block that looks for rescue_from handlers in the controller.

Related

How to test with rspec a returned exception?

def show
begin
#cart = Cart.find(params[:id])
authorize(#cart)
#cart_entries = CartEntry.where(:cart_id => #cart.id)
#products = {}
#pr_references = {}
#cart_entries.each do |cart_entry|
#pr_references[cart_entry.id] = Reference.find(cart_entry.reference_id)
#products[cart_entry.id] = Product.find(#pr_references[cart_entry.id].product_id)
end
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json {render json: {'error': e}, status: :not_found}
end
end
I want to test when Cart.find() doesn't find the cart and I want to test the method return a 404 HTTP code with the test below.
it 'don\'t find cart, should return 404 error status' do
delete :destroy, params: {id: 123, format: 'json'}
expect(response).to have_http_status(404)
end
Have you got some indications or solution to do that ?
I'm a nooby with ruby on rails, if you have some tips with the code I posted I'll take it.
Thank you :)
It seems some other code is raising an exception before your Cart.find statement is executed. For this reason, the ActiveRecord::RecordNotFound exception is never risen, and it is never captured by the rescue block.
Based on the exception that is raising, it seems you are using Pundit gem for dealing with authorization. The authorization rules offered by this gem are surely running before your show method starts. Probably this is happening as a consequence of a before_filter statement, either in this controller or in a parent controller.
You will need to handle this kind of errors in your application. It may be handy to use a rescue_form statement in a base controller that is inherited by all other controllers, so that you don't have to deal with this kind of errors in every controller.

StandardError redirect to page

I was handed a project that another developer worked on, without leaving any documentation behind. The code fetches some purchases from a shopping website, looks for a price and notifies the user.
The app may encounter errors like "no results found" and then I raise a standarderror.
I want to redirect the user to the error page and notify them about it but I can't do that because it isn't a controller, so the redirect_to option doesn't work.
services/purchase_checker.rb is called once an hour:
def call
user.transaction do
store_purchase
if better_purchase?
update_purchase
end
end
rescue MyError=> e
store_error(e)
end
def store_error(error)
user.check_errors.create!(error_type: error.class.name, message: error.message)
end
services/my_error.rb:
class MyError< StandardError
def initialize(error_type, error_message)
super(error_message)
#error_type = error_type
end
attr_reader :error_type
end
services/purchase_fetcher.rb:
def parse_result_page
raise purchase_form_page.error if purchase_form_page.error.present?
offer = purchase_page.map{|proposal_section|
propose(proposal_section, purchase) }
.min_by(&:price)
offer or raise MyError.new("No results", "No results could be found")
end
you should create another err class, eg NotFoundError:
offer or raise NotFoundError.new("No results", "No results could be found")
then in your controller:
begin
parse_result_page
rescue NotFoundError => e
redirect_to err_page, :notice => e.message
end
Since this is running in a job, the best way to notify the user would be by email, or some other async notification method. When an error is detected, an email is sent.
If that's not an option for some reason, you can check if a user has check_errors in any relevant controllers. Looking at the store_error(error) method that is called when an error is found, it seems it's creating a new record in the Database to log the error. You should be able to check if a user has any error logged via the user.check_errors relationship.
You could do it like this, for example:
class SomeController < ActionController::Base
# ...
before_action :redirect_if_check_errors
# ...
def redirect_if_check_errors
# Assuming you're using Devise or something similar
if current_user && current_user.check_errors.exists?
redirect_to some_error_page_you_create_for_this_path
end
end
end
This will check for these errors in every action of SomeController and redirect the user to an error page you should create, where you render the errors in the user.check_errors relationship.
There are multiple ways to do this, but I still think sending an email from the Job is a better option if you want to actively notify the user. Or perhaps add an interface element that warns the user whenever user.check_errors has stuff there, for example.
I propose that you do this synchronously so that the response can happen directly in the request/response cycle. Perhaps something like this:
# controller
def search
# do your searching
# ...
if search_results.blank?
# call model method, but do it synchrously
purchase_check = PurchaseChecker.call
end
if purchase_check.is_a?(MyError) # Check if it's your error
redirect_to(some_path, flash: { warning: "Warn them"})
end
end
# model, say PurchaseChecker
def call
# do your code
rescue MyError => e
store_error(e)
e # return the error so that the controller can do something with it
end

Rails: Test against and handle ActionController::InvalidAuthenticityToken

I have a Rails web app with some forms users can use to comment etc. They are frequently being used by spammers trying to create spam comments. Nothing new about that.
Sometimes I get CSRF-hack attempts which causes an ActionController::InvalidAuthenticityToken exception. This happens quite a lot and I would like to rescue and send the user/bot to a "You failed to comment"-page.
This has turned out to be a tricky exception to catch though since I can't recreate the error myself. I first put a rescue in the #create when the model (created by the form) was to be saved but it doesn't catch the exception. In order to do so I consider having it cover the entire controller section but that seems over the top.
My two questions:
Is there a way to re-create an ActionController::InvalidAuthenticityToken error by myself, so I can test it?
When is the exception raised? During .save? On Comment.new? Basically, where should I put my begin/rescue?
Thanks!
You can rescue from this exception in your controller. One way you can set this up is to add a rescue in your ApplicationController.
class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, :with => :invalid_auth_token
private
def record_not_found
render :text => "You failed to comment", :status => 422
end
end
You could also just capture the exception locally in your controller action.
def create
begin
# Create your comment
rescue ActionController::InvalidAuthenticityToken
# Render your last view with some error text.
end
end
You can add raise ActionController::InvalidAuthenticityToken inside your action if you want to test it out.

Get exception content in Rails Router

So I handle exceptions with an error controller to display dynamic content to my users in production. I have it in my route file to do:
# Errors
%w( 404 422 500 ).each do |code|
get code, :to => "errors#show", :code => code
end
The only problem is now that I'm routing on errors such as that I lose information in my controller when I want to notify Airbrake. How can I maintain the exception information and send it to Airbrake on a 500? Right now all I get is the env that was occurring at the time of the exception which is less helpful for debugging purposes.
class ErrorsController < ApplicationController
def show
notify_airbrake(env)
render status_code.to_s, :status => status_code
end
protected
def status_code
params[:code] || 500
end
end
Are you handling an error by redirecting to a URL like http://your.site/500? That will be just an HTTP request like any other, losing the exception context you're after. Instead, you probably want to be using ActionController's Rescue functionality. It would look like this:
class ApplicationController < ActionController::Base
rescue_from StandardError, with: :render_error
private
def render_error(error)
notify_airbrake(error)
render text: 500, status: 500
end
end
You can add multiple rescue_from declarations to handle different kinds of error, like the ActiveRecord::RecordNotFound from the Rails guide's example.

How do I disable rescue handlers in Ruby on Rails apps when I'm running functional tests?

I have a number of controllers in my Ruby on Rails apps with a rescue handler at the end of the action that basically catches any unhandled errors and returns some kind of "user friendly" error. However, when I'm doing rake test I'd like to have those default rescue handlers disabled so I can see the full error & stack trace. Is there any automated way to do this?
Update to clarify: I have an action like this:
def foo
# do some stuff...
rescue
render :text => "Exception: #{$!}" # this could be any kind of custom render
end
Now when I functional test this, if the exception is raised then I'm going to get just a little bit of info about the exception, but what I'd like is for it to act as though there's no rescue handler there, so I get the full debug information.
Update: SOLUTION
I did this:
rescue:
raise unless Rails.env.production?
render :text => "Exception: #{$!}" # this could be any kind of custom render
end
Not quite automated, but how modifying your code to re-throw exceptions whenever called within a test?
Perhaps something like this:
def foo
# do some stuff...
rescue
raise if ENV["RAILS_ENV"] == "test"
render :text => "Exception: #{$!}" # this could be any kind of custom render
end
Have you looked at using the assert_raise( exception1, exception2, ... ) { block } call and then printing the exception from the block?
Which method are you using? There are two rescue methods in ActionController.
I have this in my base controller:
def rescue_action_in_public(exception)
response_code = response_code_for_rescue(exception)
status = interpret_status(response_code)
respond_to do |format|
format.html { render_optional_error_file response_code}
format.js { render :update, :status => status do |page| page.redirect_to(:url => error_page_url(status)) end}
end
end
This only displays custom errors in production mode.
I think the easiest thing to do is verify that the correct render was called-- or whatever was different from the regular, non-exceptional case.
You shouldn't need to disable your rescue block. Use the assert_raise method (as suggested by Scott), and in the block, call the method that you expect an exception from.
For example:
def test_throws_exception
assert_raise Exception do
raise_if_true(true)
end
end

Resources