Handle exception in ruby on rails - 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.

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

How to DRY up Webhook handlers in Rails

I've been developing Stripe Webhook handler to create/update records depending the values.
It's not really hard, if it's a simple like this below;
StripeEvent.configure do |events|
events.subscribe 'charge.succeeded' do |event|
charge = event.data.object
StripeMailer.receipt(charge).deliver
StripeMailer.admin_charge_succeeded(charge).deliver
end
end
However If I need to store the data conditionally, it could be little messier.
In here I extracted the each Webhook handler and defined something like stripe_handlers/blahblah_handler.rb.
class InvoicePaymentFailed
def call(event)
invoice_obj = event.data.object
charge_obj = retrieve_charge_obj_of(invoice_obj)
invoice = Invoice.find_by(stripe_invoice_id: charge_obj[:invoice])
# common execution for subscription
invoice.account.subscription.renew_billing_period(start_at: invoice_obj[:period_start], end_at: invoice_obj[:period_end])
case invoice.state
when 'pending'
invoice.fail!(:processing,
amount_due: invoice[:amount_due],
error: {
code: charge_obj[:failure_code],
message: charge_obj[:failure_message]
})
when 'past_due'
invoice.failed_final_attempt!
end
invoice.next_attempt_at = Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i)
invoice.attempt_count = invoice_obj[:attempt_count].to_i
invoice.save
end
private
def retrieve_charge_obj_of(invoice)
charge_obj = Stripe::Charge.retrieve(id: invoice.charge)
return charge_obj
rescue Stripe::InvalidRequestError, Stripe::AuthenticationError, Stripe::APIConnectionError, Stripe::StripeError => e
logger.error e
logger.error e.backtrace.join("\n")
end
end
end
I just wonder how I can DRY up this Webhook handler.
Is there some best practice to approach this or any ideas?
I suggest re-raising the exception in retrieve_charge_obj_of, since you'll just get a nil reference exception later on, which is misleading. (As is, you might as well let the exception bubble up, and let a dedicated error handling system rescue, log, and return a meaningful 500 error.)
a. If you don't want to return a 500, then you have a bug b/c retrieve_charge_obj_of will return nil after the exception is rescued. And if charge_obj is nil, then this service will raise a NPE, resulting in a 500.
if invoice_obj[:next_payment_attempt] can be !present? (blank?), then what is Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i) supposed to mean?
a. If it was nil, false, or '', #to_i returns 0 -- is that intended? ([]/{} is also blank? but would raise)
Conceptually, this handler needs to issue a state transition on an Invoice, so a chunk of this logic can go in the model instead:
class Invoice < ApplicationRecord
# this method is "internal" to your application, so incoming params should be already "clean"
def mark_payment_failed!(err_code, err_msg, attempt_count, next_payment_at)
transaction do # payment processing usually needs to be transactional
case self.state
when 'pending'
err = { code: err_code, message: err_msg }
self.fail!(:processing, amount_due: self.amount_due, error: err)
when 'past_due'
self.failed_final_attempt!
else
ex_msg = "some useful data #{state} #{err_code}"
raise InvalidStateTransition, ex_msg
end
self.next_attempt_at = next_payment_at
self.attempt_count = attempt_count
self.save
end
end
class InvalidStateTransition < StandardError; end
end
Note: I recommend a formal state machine implementation (e.g. state_machine) before states & transitions get out of hand.
Data extraction, validation, and conversion should happen in the handler (that's what "handlers" are for), and they should happen before flowing deeper in your application. Errors are best caught early and execution stopped early, before any action has been taken.
There are still some other edge cases that I see that aren't really handled.

How to handle multiple exceptions in a Transaction with Ruby on Rails for importing a text file

My ruby environment is: Ruby 2.3.1 and Rails 5.0.0.1.
I'm trying to import a text file for import a lot of purchases items.
Example of purchase file:
data.txt
Customer\tDescription\tUnit Price\tQuantity\tAddress\tSupply company\n
Athos Matthew\tChocolate\t10\t5\tSome address.\tChocolate company\n
The columns are divided by a tab (\t) and it has an enter at the final (\n).
I have the purchase class where all attributes cannot be null. The attributes are:
custumer_name:string
product_id:integer # It has relationship with the Product Resource
product_quantity:integer
supply_company_id:integer # It has relationship with the SupplyCompany Resource
To import the file, I decided to create a PurchaseImporter class to do this job and keep the code cleaner.
My problem is that transaction part:
begin
ActiveRecord::Base.transaction do
purchase = Purchase.new
data = line.force_encoding('UTF-8').split(/\t/)
purchase.customer_name = data[0]
product = Product.find_or_create_by!(description: data[1], price: data[2])
purchase.product_quantity = data[3]
purchase.product = product
supply_company = SupplyCompany.find_or_create_by!(name: data[5], address: data[4])
purchase.supply_company = supply_company
purchase.save!
end
rescue Exception => e
#errors[:import][index] = e.message
end
My problem is that I want to catch all the raised errors from the Product, SupplyCompany and Purchase that could happen inside this transaction.
This is the order of the happenings without the unnecessary code to explain it.
product = Product.find_or_create_by!(description: data[1], price: data[2])
supply_company = SupplyCompany.find_or_create_by!(name: data[5], address: data[4])
purchase.save!
I need to print this errors information to this 3 classes in the screen, but with my code, I can only catch the first exception error, generated by the product. If some error happened in the SupplyCompany or in the Purchase, I lost these errors messages.
Are there other ways to import and log the errors message when importing files?
You can have more specific exception handling... do a rescue for each section you want to trap, at the end raise an error if any previous error was encountered (to get you out of the transaction block) and test in that final error that you're rescuing your own raise otherwise it's some other problem and you need to halt.
begin
ActiveRecord::Base.transaction do
error_encountered = false
purchase = Purchase.new
data = line.force_encoding('UTF-8').split(/\t/)
purchase.customer_name = data[0]
begin
product = Product.find_or_create_by!(description: data[1], price: data[2])
purchase.product_quantity = data[3]
purchase.product = product
rescue Exception => e
#errors[:import][index] = e.message
error_encountered = true
end
begin
supply_company = SupplyCompany.find_or_create_by!(name: data[5], address: data[4])
purchase.supply_company = supply_company
rescue Exception => e
#errors[:import][index] = e.message
error_encountered = true
end
begin
purchase.save!
rescue Exception => e
#errors[:import][index] = e.message
error_encountered = true
end
raise 'break out of transaction' if error_encountered
end
rescue Exception => e
raise unless e.message == 'break out of transaction'
end
Since you're rescuing Exception it's hard to know what error is actually emerging. When rescuing, you should try to use a more specific class when possible.
You also might not need to use rescue at all. The active methods you're using: find_or_create_by! and save! can be written without the exclamation point so that they don't raise errors.
In active record, if you try and save something with validation errors then the <record>.errors.full_messages array is populated. It won't necessarily raise an error if you don't use the exclamation point (although errors can be raised from all sorts of things regardless).
So, for example you can try and save a record and check for errors like this:
product = Product.find_or_initialize_by(description: data[1], price: data[2])
product.save
errors[:import][index] ||= []
errors[:import][index].concat product.errors_full_messages
Actually, in this case I think your approach makes some sense. You're saving a few records in sequence. If the first fails, then probably the others will fail - so is it worth even attempting to save those subsequent records? I'll let you decide.

Resources