Ruby - retry function with different params - ruby-on-rails

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

Related

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.

Handle exception in ruby on rails

I called a method #txt.watch inside model from worker and Inside watch() there is an array of parameters(parameters = self.parameters). Each parameter have unique reference id.
I want to rescue each exception error for each parameter from inside worker.
class TextWorker
def perform(id)
#txt = WriteTxt.find(id)
begin
#txt.watch
total_complete_watch = if #txt.job_type == 'movie'
#txt.total_count
else
#txt.tracks.where(status:'complete').size
end
#txt.completed = total_completed_games
#txt.complete = (total_complete_games == #txt.total_count)
#txt.completed_at = Time.zone.now if #txt.complete
#txt.zipper if #txt.complete
#txt.save
FileUtils.rm_rf #txt.base_dir if #txt.complete
rescue StandardError => e
#How to find errors for each reference_id here
raise e
end
end
end
Is there any way to do. Thanks u very much.
I assume self.parameters are in your Model class instance. In that case, do as follows and you can reference them.
begin
#txt.watch
rescue StandardError
p #parameters # => self.parameters in the Model context
raise
end
Note:
As a rule of thumb, it is recommended to limit the scope of rescue as narrow as possible. Do not include statements which should not raise Exceptions in your main clause (such as, #txt.save and FileUtils.rm_rf in your case). Also, it is far better to limit the class of an exception; for example, rescue Encoding::CompatibilityError instead of EncodingError, or EncodingError instaed of StandardError, and so on. Or, an even better way is to define your own Exception class and raise it deliberately.

How can you detect when "break" is called in the callers Proc?

I'm writing a library that iterates over a set and calls the caller's proc for every item in the set. Example:
def self.each(&block)
# ... load some data into results_array
results_array.each do |result|
status = block.call(result)
# how do I know to call break if the user calls break?
break if status == false
end
end
Currently, as you can see in my code, I inspect the "last expression evaluated" in order to break. This seems bug-prone as the end-user may have a perfectly valid reason for their last expression evaluating to false. The more appropriate thing would be to detect the caller using "break".
How do I know to call break if the user calls break?
If you use yield instead of block.call, you can use the difference in behavior between next and break. As an example:
def each(&block)
puts "before"
result = yield
puts "after"
result
end
each do
puts "hello"
next
end
# Result:
# before
# hello
# after
each do
puts "hello"
break
end
# Result:
# before
# hello
As you can see, when you use next inside a block, the control is given back to the function calling the block. If you use break however, the calling function will return immediately with nil as a return value. You could now exploit this behavior with some trick:
def each(&block)
# ...
results_array.each do |result|
block_result = yielder(result, &block) || {:status => :break, :value => nil}
if block_result[:status] == :break
# block has called break
#...
else
# block has called either next or the block has finished normally
#...
end
end
end
def yielder(*args, &block)
value = yield *args
{:status => :normal, :value => value}
end
This works because here, the yielder function returns either nil in case the block called break or a hash with a status and the return value of the block. You can thus differentiate between a valid result (which is always different from nil) and an exceptional result which is always nil.
This should work, unless I don't understand what you are trying to do:
def each(&block)
# ... load some data into results_array
results_array= [1, 2, 3]
results_array.each do |result|
block.call(result)
end
end
each do |result|
puts result
break
end

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.

Resources