Exception Handling in Ruby - call begin if it goes to rescue - ruby-on-rails

I have a simple exception handling block
begin
<connect to network and make a request>
rescue
<comes here if it didnt connect / whatever other error>
end
I want to modify it such that if it comes to rescue - it goes to begin again. Give 5 tries . If still not connecting - come out of the block

you want the keyword retry. Couple that with a MAX_RETRIES. i.e. if (MAX_RETRIES -= 1) > 0
retry

Increment a counter and retry until that counter hits the value:
MAX_RETRIES = 5
retries = 0
begin
do_something
rescue ex
if retries += 1 <= MAX_RETRIES
retry
else
raise ex
end
end
You'll may want to retry on some Network related exceptions only, like timeout. Sometimes a not found or forbidden will always be, unless you poll until that changes.

This should do :
5.times do |i|
begin
1/0
rescue
puts 'Try #{i+1} failed ...'
next
end
puts 'Try #{i+1} success.'
break
end

write a method to connect to a network like this
def connect_to_server(retry_count)
begin
retry_count += 1
<connect to network and make a request>
rescue
if(retry_count <= 5)
connect_to_server(retry_count)
end
end
end
Call that method
connect_to_server(0)

Related

Ruby: How to test private method?

I have implemented Optimistic Locking for Race Condition. If lock_version doesn't match with the updated lock_version in the database, then it will trigger retry three times. Can you suggest how to test this retry event.
Note: I can't change private method2
Public
def method1
begin
method2
rescue Exception => e
end
end
Private
def method2
tries = 0
begin
raise Product::StaleObjectError.new("Product is changed while you were editing") if stale_object?
// Do some work
raise Exception.new("Total amount used is greater than approved") if total_approved < 0
// Save Product
rescue Product::StaleObjectError => e
if tries < MAX_RETRIES
tries += 1
sleep(1 + tries)
reload
retry
else
raise Product::StaleObjectError("Product is changed while you were editing")
end
end
attributes
end
Test Case:
it 'method1 should call three times if there is stale_object' do
prod_v1 = Car.find(#car.id)
prod_v1.stub(:stale_object?).and_return true
prod_v1.method1
expect{prod_v1}.to receive(:method2).exactly(3).times
end
I am getting following error for test case
Failure/Error: expect{car_v1}.to receive(:method2).exactly(3).times
(#<Proc:).method2(any args)
expected: 2 times with any arguments
received: 0 times with any arguments
You are setting the expectation after the method was already called, that's why you get 0 times, it was called 0 times after that line.
Change the order, move the expectation ABOVE the line prod_v1.method1 and it will work. Using expect works for private and public methods.

Continue assertion after failures in ruby

My assertion example is below,
class test < Test::Unit::TestCase
def test_users
begin
assert_equal(user.name, 'John')
assert_equal(user.age, 30)
assert_equal(user.zipcode, 500002)
rescue Exception
raise
end
end
end
If any one of assertions fails, i should move on to process the next one and collect the failure assertions and show failures the end of the result.
I have used add_failure method, its working for looping condition
rescue Test::Unit::AssertionFailedError => e
add_failure(e.message, e.backtrace)
Can any one help ?
A good unit test should test exactly one thing, specifically to avoid problems like you just face. This test case will report on all failed tests, and not just the first failed test:
class MyTest < Test::Unit::TestCase
def test_user_name
assert_equal(user.name, 'John')
end
def test_user_age
assert_equal(user.age, 30)
end
def test_user_zipcode
assert_equal(user.zipcode, 500002)
end
end
Your main problem is that assert_equal ends up calling assert (as shown below) and assert will raise an ArgumentException.
File test/unit/assertions.rb, line 144
def assert_equal(exp, act, msg = nil)
msg = message(msg) {
# omitted code to save space
}
assert(exp == act, msg)
end
File test/unit/assertions.rb, line 29
def assert(test, msg = UNASSIGNED)
case msg
when UNASSIGNED
msg = nil
when String, Proc
else
bt = caller.reject { |s| s.rindex(MINI_DIR, 0) }
raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
end
super
end
You could extend Test::Unit::Assertions and provide an assertion that does not raise ArgumentError, which is what is stopping continuation past the failed assertion.
See this question for advice on going that direction and adding in safe assertions.
Please find this code for Continue assertion after failures in Ruby :
def raise_and_rescue
begin
puts 'I am before the raise.'
raise 'An error has occured.'
puts 'I am after the raise.'
rescue
puts 'I am rescued.'
end
puts 'I am after the begin block.'
end
Output :
ruby p045handexcp.rb
I am before the raise.
I am rescued.
I am after the begin block.
Exit code: 0

Sidekiq/Airbrake only post exception when retries extinguished

I would like Airbrake to only be notified of errors when the retries are exhausted, but I can't seem to think of a way to implement it...
I can add a sidekiq_retries_exhausted hook to send the error to AirBrake but the only way I can think of catching the actual failures is to add a middleware that swallows the error, but then, the job will be marked as a success if there is no error... then there will never be any retries..
Hope that makes sense!
I managed to implement this with a Sidekiq middleware that is inserted at the start of the list:
class RaiseOnRetriesExtinguishedMiddleware
include Sidekiq::Util
def call(worker, msg, queue)
yield
rescue Exception => e
bubble_exception(msg, e)
end
private
def bubble_exception(msg, e)
max_retries = msg['retries'] || Sidekiq::Middleware::Server::RetryJobs::DEFAULT_MAX_RETRY_ATTEMPTS
retry_count = msg['retry_count'] || 0
last_try = !msg['retry'] || retry_count == max_retries - 1
raise e if last_try
end
def retry_middleware
#retry_middleware ||= Sidekiq::Middleware::Server::RetryJobs.new
end
end
If its the last try and its thrown an exception, it'll let it bubble up (to Airbrake) otherwise it won't. This doesn't affect failure recording as that happens later in the chain.
As shown here (not my code):
Airbrake.configure do |config|
config.api_key = '...'
config.ignore_by_filter do |exception_data|
exception_data[:parameters] &&
exception_data[:parameters]['retry_count'].to_i > 0
end
end
I ran into the exact same thing, and wanted to keep it out of AirBrake. Here is what I did, which is easy to read and simple:
class TaskWorker
include Sidekiq::Worker
class RetryLaterNotAnError < RuntimeError
end
def perform task_id
task = Task.find(task_id)
task.do_cool_stuff
if task.finished?
#log.debug "Nothing to do for task #{task_id}"
return false
else
raise RetryLaterNotAnError, task_id
end
end
end
And then, to get Airbrake to ignore it:
Airbrake.configure do |config|
config.ignore << 'RetryLaterNotAnError'
end
Voila!
Here is how we do it for Bugsnag, which you can customise for Airbrake.
# config/initializers/00_core_ext.rb
class StandardError
def skip_bugsnag?
!!#skip_bugsnag
end
def skip_bugsnag!
#skip_bugsnag = true
return self
end
end
# config/initializers/bugsnag.rb
config.ignore_classes << lambda { |e| e.respond_to?(:skip_bugsnag?) && e.skip_bugsnag? }
# In Sidekiq Jobs
raise ErrorToRetryButNotReport.new("some message").skip_bugsnag!
# Or if the error is raised by a third party
begin
# some code that calls a third-party method
rescue ErrorToRetryButNotReport => e
e.skip_bugsnag!
raise
end
You can then manually choose to send the error from sidekiq_retries_exhausted.

RSpec retry throw exception and then return value

I have a retry block
def my_method
app_instances = []
attempts = 0
begin
app_instances = fetch_and_rescan_app_instances(page_n, policy_id, policy_cpath)
rescue Exception
attempts += 1
retry unless attempts > 2
raise Exception
end
page_n += 1
end
where fetch_and_rescan_app_instances access the network so can throw an exception.
I want to write an rspec test that it throws an exception first time and doesn't throw an exception second time it gets called, so I can test if the second time it doesn't throw an exception, the my_method won't throw an exeption.
I know i can do stub(:fetch_and_rescan_app_instances).and_return(1,3) and first time it returns 1 and second time 3, but I don't know how to do throw an exception first time and return something second time.
You can calculate the return value in a block:
describe "my_method" do
before do
my_instance = ...
#times_called = 0
my_instance.stub(:fetch_and_rescan_app_instances).and_return do
#times_called += 1
raise Exception if #times_called == 1
end
end
it "raises exception first time method is called" do
my_instance.my_method().should raise_exception
end
it "does not raise an exception the second time method is called" do
begin
my_instance.my_method()
rescue Exception
end
my_instance.my_method().should_not raise_exception
end
end
Note that you should really not be rescuing from Exception, use something more specific. See: Why is it a bad style to `rescue Exception => e` in Ruby?
What you do is constrain the times the message should be received (receive counts), i.e. in your case you can
instance.stub(:fetch_and_rescan_app_instances).once.and_raise(RuntimeError, 'fail')
instance.stub(:fetch_and_rescan_app_instances).once.and_return('some return value')
Calling instance.fetch_and_rescan_app_instances first time will raise RuntimeError, and second time will return 'some return value'.
PS. Calling more than that will result in an error, you might consider using different receive count specification https://www.relishapp.com/rspec/rspec-mocks/docs/message-expectations/receive-counts
This has changed a little in RSpec3.x. It seems the best approach is to pass a block to the receive that defines this type of behaviour.
The following is from the docs suggesting how to create this type of transit failure:
(This errors every other time it is called... But is easy to adapt.)
RSpec.describe "An HTTP API client" do
it "can simulate transient network failures" do
client = double("MyHTTPClient")
call_count = 0
allow(client).to receive(:fetch_data) do
call_count += 1
call_count.odd? ? raise("timeout") : { :count => 15 }
end
expect { client.fetch_data }.to raise_error("timeout")
expect(client.fetch_data).to eq(:count => 15)
expect { client.fetch_data }.to raise_error("timeout")
expect(client.fetch_data).to eq(:count => 15)
end
end

How do I make the loop finish when an error is thrown inside?

If I have a loop, and somewhere in the loop, I get an exception or error. How do I keep the loop going?
Foos.each do |foo|
....
# Random error/exception thrown here
....
end
Should I have a rescue block in the loop? Will that make the loop finish? Or is there a better alternative?
You can use add a begin/rescue block. I am not sure there is other ways to do keep loop going if an error is raised.
4.times do |i|
begin
raise if i == 2
puts i
rescue
puts "an error happened but I'm not done yet."
end
end
# 0
# 1
# an error happened but I'm not done yet.
# 3
#=> 4
Since your title in the other hand ask for a way to ends the loop.
If you want the loop to ends in the rescue, you can use break.
4.times do |i|
begin
raise if i == 2
puts i
rescue
puts "an error happened and I'm done."
break
end
end
# 0
# 1
# an error happened and I'm done.
#=> nil

Resources