Escaping ensure in console/rake/rails - ruby-on-rails

Given something like this
def infinite
puts Time.now
rescue => err
puts err.message
ensure
infinite
end
When you run this in console/rake and hit ctrl-c - nothing happens. How do you escape this with CTRL-C?

Use catch instead which is an alternative control flow.
catch executes its block. If throw is not called, the block executes normally, and catch returns the value of the last expression evaluated.
Ruby searches up its stack for a catch block whose tag has the same object_id as the throw (symbols are almost always used as the have the same object_id). When found, the block stops executing and returns val (or nil if no second argument was given to throw).
def infinate
catch(:done) do
begin
infinite
rescue SystemExit, Interrupt => e
puts e.message
throw :done
end
end
end
Using ensure with a condition like that is semantically wrong as the whole point of ensure is to run code that always should be run.

Using rescue to create an infinite loop via recursion seem overly complicated and could cause a SystemStackError later on.
Why not use an actual loop:
def infinite
loop do
begin
puts Time.now
rescue => err
puts err.message
end
end
end
With the above, Ctrl-C works just fine, because rescue without an explicit exception class will only handle StandardErrors.

I'm not sure if this is the proper solution but this worked for me:
def infinite
puts Time.now
rescue SystemExit, Interrupt
#skip_ensure = true
puts 'SystemExist/Interrupt'
rescue => err
puts err.message
ensure
infinite unless #skip_ensure
end

Related

Ruby - retry function with different params

I'd like to retry a function with different params depending on the result of the first iteration:
Giving a retry function like follow:
def retry_on_fail(**args)
yield
rescue StandardError => e
args = args.merge(different_param => true) if e.class == `specific_error`
retry
Is there a way to do so? I didn't find it yet...
Thanks!
You can yield however many times you want in a method and the trick is really passing the arguments to the block:
# given
class SpecificError < StandardError; end
def retry_on_fail(**args)
yield(args)
rescue SpecificError
yield(args.merge(different_param: true))
end
retry_on_fail do |args|
raise SpecificError if args.empty?
args
end
# returns { different_param: true }
There is also a slight differnce here flow wise - retry runs the whole method from the top and this will just call the block again. If thats what you want you could do:
def retry_on_fail(**args)
yield(args)
rescue SpecificError
args.merge!(different_param: true)
retry
end
But this has the potential to create an endless loop if the block raises the same exception again.
Try this
def retry_on_fail(**args)
rescue_args = args
begin
yield(rescue_args)
rescue StandardError => e
rescue_args = rescue_args.merge(different_param => true) if e.class == `specific_error`
retry
end
end

How do I add default error handling to a function which produces an iterable?

I have a model, Transaction, and a method, external_evaluation. external_evaluation works its way down the stack and eventually calls out to an out to an AWS lambda. When the response is bad, a BadResponse exception is raised.
There is a pattern in the codebase that gets used frequently that goes something like
def get_some_transactions()
Transaction.where(some_column: some_expression)
end
def do_some_stuff()
get_some_transactions.each do |transaction|
do_something(transaction.external_evaluation)
rescue BadResponse => e
log(e)
next
end
end
def do_some_other_stuff()
get_some_transactions.each_with_object({}) do |transaction, transaction_hash|
transaction_hash[transaction] = do_something_else(transaction.external_evaluation)
rescue BadResponse => e
log(e)
next
end
end
I really dislike the duplication of the error handling code in this pattern, and would like to be able to add default error handling into get_some_transactions which will apply regardless of which iteration function is called (each, each_with_object, each_with_index, ...). Is there an idiomatic way to do this in Ruby?
def with_error_handing(&block)
begin
yield
rescue BadResponse => e
log(e)
end
end
def do_some_stuff()
get_some_transactions.each do |transaction|
with_error_handing do
do_something(transaction.external_evaluation)
end
end
end
def do_some_other_stuff()
get_some_transactions.each_with_object({}) do |transaction, transaction_hash|
with_error_handing do
transaction_hash[transaction] = do_something_else(transaction.external_evaluation)
end
end
end
You can move the rescue to external_evaluation method.

How to test whether any exception was rescued?

Is there a way to find out whether any exception was raised and rescued during the execution of some code?
Preferably in tests written with ActiveSupport::TestCase and not RSpec
Is there any global ruby exception stack or something, which I could check?
If you want to be a monster, you can instrument the errors themselves:
class StandardError
##called = false
def initialize
##called = true
super
end
def self.called
##called
end
end
#test it out like so:
def raise_arg_error
raise ArgumentError
rescue
end
puts ArgumentError.called #false
raise_arg_error
puts ArgumentError.called #true
Great for ad hoc sanity checks. Terrible for production code.
As clarified in the comments, OP needed it for debug purposes as opposed to write tests with it.
Kernel#set_trace_func lets you intercept low level events such as an error being raised:
set_trace_func(proc do |event, *_|
puts 'Raised!' if event == 'raise'
end)
raise 'Oh, no!' rescue :foo
You can run #set_trace_func before the code you were trying to debug. If no exception was raised, but a raise was registered by the hook - someone rescued it.
This can create some noise depending on what you are executing. Fortunately, you can filter it down by file and line:
set_trace_func(proc do |event, file, line, *_|
if event == 'raise' && file == 'test.rb' && line == 42
puts "Raised on #{file}:#{line}!"
end
end)
It even gives you the binding, so you can drop down a debugger:
require 'irb'
set_trace_func(proc do |event, *_, error_binding, _|
error_binding.irb if event == 'raise'
end)
def foo
bar = 42
raise 'Oh, no!' rescue :baz
end
foo
# Opens an irb
# > bar # => 42

Why doesn't calling next within a rescue block within a transaction within a loop work?

I have a loop like this:
# Iterate a list of items
req_wf_list.each do |req_wf|
# Begin a transaction
ReqWf.transaction do # ReqWf is an ActiveRecord model class
# Do some things
# ...
# 1. I want to be able to continue processing with the
# next iteration of the loop if there is an error here
# 2. I also want to rollback the transaction associated with
# this particular iteration if I encounter an error
begin
# Do something that might return an error
rescue
# Do some error processing
puts "Caught such and such error"
# Don't complete transaction (rollback),
# don't "do some more things",
# proceed to next item in req_wf_list
next
end
# Do some more things
# Shouldn't make it here if there is an error but I do indeed make it here
# ...
# End transaction
end
# End loop
end
Now, I would expect that calling "next" within the rescue block would cause the transaction associated with that particular iteration of the loop to rollback and for execution to resume at the top of the next iteration of the loop. Instead, execution appears to resume at the "Do some more things" line. It is as if the "next" statement is completely ignored. What am I missing?
Most likely that in this case next applies to transaction so you are in a nested loop situation.
This is an example of what can be done to solve the issue
req_wf_list.each do |req_wf|
catch :go_here do #:missingyear acts as a label
ReqWf.transaction do
throw :go_here unless something #break out of two loops
end
end #You end up here if :go_here is thrown
end
But in general, it is not a good practice to use next. You should be able to put a global begin .. rescue and have all the conditions inside of it, so that nothing else gets executed once you catch an error.
Update
I did some a small test and the behavior is as you expect it.
loop = [1,2,3]
loop.each do |value|
puts "value => #{value}"
ActiveRecord::Base.transaction do
puts "Start transaction"
begin
raise
rescue
puts "ActiveRecord::StatementInvalid"
next
end
puts "Should not get here!"
end
end
The output is the following:
value => 1
Start transaction
ActiveRecord::StatementInvalid
value => 2
Start transaction
ActiveRecord::StatementInvalid
value => 3
Start transaction
ActiveRecord::StatementInvalid
Is it possible that you had another error in your code before the next was being called ?
In any case, using the next statement is not the best option as I said before.

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