I have problem with handling exceptions. I know how to do it, but I'm not sure what's the correct place to rescue them. For example:
class ExampleService
def call
...
raise ExampleServiceError, 'ExampleService message'
end
end
class SecondExampleService
def call
raise SecondExampleServiceError if something
ExampleService.call
rescue ExampleService::ExampleServiceError => e ---> should I rescue it here?
raise SecondExampleServiceError, e.message
end
end
Class ExampleController
def update
SecondExampleService.call
rescue ExampleService::ExampleServiceError, SecondExampleService::SecondExampleServiceError => e
render json: { error: e.message }
end
end
As you can see in example I have two Services. ExampleService raises his own exception. SecondExampleService call the ExampleService, can raise exception and is used in ExampleController. Where should I rescue ExampleServiceError? In ExampleController or SecondExampleService? If in ExampleController, what about when we use such a Service in multiple controllers (rescue code will be repeated many times)?
When the controller only calls SecondExampleService directly and doesn't know anything about ExampleService, then it should not rescue from ExampleServiceError.
Only SecondExampleService knows that ExampleService is used internally, and therefore SecondExampleService should rescue from ExampleServiceError and translate it into a SecondExampleServiceError.
That is my interpretation of the law of demeter.
Related
I have an ActiveRecord class like:
class DBGroup < ActiveRecord:Base
# ...
scope :example_method, -> { // does something }
# ...
end
And now I have another method in another class, something like
def method1
do #open block for rescue method
DBGroup
.example_method
.group(:some_column)
.having("COUNT(*) > 5")
rescue StandardError => e
e
end
end
And in RSpec I am testing kind of like:
it "my test, want to test rescue block"
expect_any_instance_of(DBGroup).to receive(:example_method).and_raise(StandardError)
expect{subject.method1}.to raise_error(StandardError)
end
But I am getting the error like:
RSpec::Mocks::MockExpectationError: DBGroup does not implement #example_method
Scopes are not instance methods, they are class methods, so you should not use expect_instance_of, but expect.
Also, you are rescuing the StandardError inside the method, so the method itself does not raise a StandardError. Actually, a StandardError is raised inside the method and it's rescued inside of it aswel and your test is all about what's happening at it's exterior.
I hope you can see the difference:
def method_that_raises_an_error
raise(StandardError)
end
def method_that_returns_an_error
raise(StandardError)
rescue StandardError => e
e
end
As you can see, your code is more like the second. So, to test it you should do:
expect(DBGroup).to(receive(:example_method).and_raise(StandardError)
method1_return = subject.method1
expect(method1_return).to(be_instance_of(StandardError))
In our Rails application we use Airbrake connected to a hosted Errbit server.
We use rescue and rescue_from in a lot of places where we want to handle any exceptions in a particular way and then we log the exception ourselves before returning a response.
Some examples we have in our ApplicationController:
rescue_from CanCan::AccessDenied do |e|
Rails.logger.error "CanCan exception: #{e.message}"
render 'errors/401', status: 401
end
rescue_from ActionController::InvalidAuthenticityToken do |e|
Rails.logger.error "Authenticity exception: #{e.message}"
render 'errors/csrf', status: 400
end
And then we also have some in individual methods such as an API:
def try_request
Response.new(yield)
rescue RestClient::Unauthorized,
RestClient::ExceptionWithResponse,
RestClient::InternalServerError,
RestClient::BadRequest => e
Rails.logger.error "API exception: #{e.message}"
Response.new(e.response)
end
However by using rescue we noticed that Errbit was no longer picking up our execeptions because we were capturing them... makes sense... but we want to still see these in our Errbit reports!
You can manually notify Airbrake with:
Airbrake.notify(e)
But we were hoping to avoid having to put this code in every single rescue block.
Is it possible to have a higher-level statement that can notify Airbrake automatically when manually logging errors? Perhaps middleware that is called when Rails.logger.error is called?
rescue is ruby clause, you cannot override it directly (and even if you could - it would not be a great idea)
In all your examples there's Rails.logger.error, you can make yourself a helper like:
module MyErrorNotifier
def self.notify(message, exception, params = {}, &block)
Rails.logger.error("#{message}: #{exception.message}")
Airbrake.notify(exception, params, &block)
end
end
and instead of logger call it:
def try_request
...
rescue e
MyErrorNotifier.notify("Foo exception happened", e)
Response.new(e.response)
end
I am bit unclear about exception handling while using Active record transactions in rails. I have seen many of them using,
Method: 1
def update
ActiveRecord::Base.transaction do
begin
# Some logic
rescue StandardError => e
raise ActiveRecord::Rollback
end
end
end
and have seen the below logics in many of the places.
Method:2
def update
ActiveRecord::Base.transaction do
if object.update(update_params)
# success
else
# error handling
end
end
rescue => e
# error handling
end
What I think is the second method itself is enough. I thought that, Transaction itself will rollback if anything unexpected happens or any logical error inside the transaction and we can catch them and do whatever we want. Is catching exception inside the transaction and raising Rollback manually needed anywhere?. What is the difference between both the methods and in which case?
You don't need to manually to rollback the transaction the code below should be good enough
def update
ActiveRecord::Base.transaction do
foo.update(foo_update_params)
end
rescue ActiveRecord::RecordInvalid
# Handle your exception here
end
Have a look here for a better explanation.
How can I return without return like devise's authenticate! method does?
class GroupsController < ApplicationController
def create
authenticate! # After here, the code below will not be excuted.
#group = Group.create(group_params)
redirect_to groups_path
end
end
I am wondering how to do this.
The devise authenticate! doesn't return on failure, it actually throw exceptions if the user isn't authenticated. The exception will get propagated all through the call chain, until it hits a matched rescue statement. The Rails framework is smart enough that it will rescue this kind of exception and convert specific exceptions to the corresponding HTTP status code, for example, ActiveRecord::RecordNotFound will be converted to 404.
This is common programming tricks to return from deep call hierarchy.
def a
raise "ha"
end
def b
puts "calling a"
a
not executed
end
def c
b rescue nil
end
c #=> calling a
And ruby provides catch/throw which serve for this kind of deeper jump.
catch(:ret) do
(1..5).each do |i|
(1..5).each do |j|
puts i * j
throw :ret if i * j > 3
end
end
end
Are you just asking how to return without using the keyword 'return'?
Ruby will automatically return the result from the last line in the method, if thats what you mean. but it would be the same as using the keyword 'return'.
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