Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby rescue exception during multiple methods

Tags:

ruby

rescue

I have built a simple banking application, which is able to perform the usual operations; Deposit, Withdraw etc.

My controller methods perform these operations and rescue exceptions that are raised by the account or other entities.

Here are some of these methods used in the controller code:

def open(type, with:)
    account = create type, (holders.find with)
    add account
    init_yearly_interest_for account
    boundary.render AccountSuccessMessage.new(account)
  rescue ItemExistError => message
    boundary.render message
  end

  def deposit(amount, into:)
    account = find into
    account.deposit amount
    boundary.render DepositSuccessMessage.new(amount)
  rescue ItemExistError => message
    boundary.render message
  end

  def withdraw(amount, from:)
    account = find from
    init_limit_reset_for account unless account.breached?
    account.withdraw amount
    boundary.render WithdrawSuccessMessage.new(amount)
  rescue ItemExistError, OverLimit, InsufficientFunds => message
    boundary.render message
  end

  def get_balance_of(id)
    account = find id
    boundary.render BalanceMessage.new(account)
  rescue ItemExistError => message
    boundary.render message
  end

  def transfer(amount, from:, to:)
    donar = find from
    recipitent = find to
    init_limit_reset_for donar unless donar.breached?
    donar.withdraw amount
    recipitent.deposit amount
    boundary.render TransferSuccessMessage.new(amount)
  rescue ItemExistError, OverLimit, InsufficientFunds => message
    boundary.render message
  end

  def add_holder(id, to:)
    holder = holders.find id
    account = find to
    account.add_holder holder
    boundary.render AddHolderSuccessMessage.new(holder, account)
  rescue ItemExistError, HolderOnAccount => message
    boundary.render message
  end

  def get_transactions_of(id)
    transactions = (find id).transactions
    boundary.render TransactionsMessage.new(transactions)
  rescue ItemExistError => message
    boundary.render message
  end

  def get_accounts_of(id)
    holder = holders.find id
    accounts = store.select { |_, a| a.holder? holder }.values
    boundary.render DisplayAccountsMessage.new(accounts)
  rescue ItemExistError => message
    boundary.render message
  end

As you can see I am rescuing multiple errors during multiple methods, often the same errors are handled.

Although this is working, I wonder if it is possible to refactor and handle these exceptions whenever any of the methods in the controller are called.

So for example:

during: 
  open, deposit, withdraw
rescue ItemExistError => message
  boundary.render message

Any help would be greatly appreciated.

like image 379
Phil Brockwell Avatar asked Jul 13 '15 15:07

Phil Brockwell


People also ask

How do I rescue two errors in Ruby?

Besides specifying a single exception class to rescue, you can pass an array of exception classes to the rescue keyword. This will allow you to respond to multiple errors in the same way. Multiple rescue blocks can be used to handle different errors in different ways.

How do you handle exceptions in Ruby?

Ruby also provides a separate class for an exception that is known as an Exception class which contains different types of methods. The code in which an exception is raised, is enclosed between the begin/end block, so you can use a rescue clause to handle this type of exception.

What happens if in a begin rescue block the rescue code has an error?

The code between “begin” and “rescue” is where a probable exception might occur. If an exception occurs, the rescue block will execute. You should try to be specific about what exception you're rescuing because it's considered a bad practice to capture all exceptions.

Can we use rescue without begin?

The method definition itself does the work of begin , so you can omit it. You can also do this with blocks. Now, there is one more way to use the rescue keyword without begin . Let's see how that works.


2 Answers

You can do this with metaprogramming by defining a method that wraps each of the methods you want to rescue from. It's your call whether or not this is actually cleaner code.

class MyController
  # define a unified exception handler for some methods
  def self.rescue_from *meths, exception, &handler
    meths.each do |meth|
      # store the previous implementation
      old = instance_method(meth)
      # wrap it
      define_method(meth) do |*args|
        begin
          old.bind(self).call(*args)
        rescue exception => e
          handler.call(e)
        end
      end
    end
  end

  rescue_from :open, :deposit, :withdraw, ItemExistError do |message|
    boundary.render message
  end
end

If you aren't going to reuse the method (i.e. if you only want a unified handler for this one set of methods and this one exception class), I would remove the rescue_from definition and put the metaprogramming code right in the class.

like image 199
Max Avatar answered Oct 06 '22 07:10

Max


You could try writing a method like this:

def call_and_rescue
  yield if block_given?
rescue ItemExistError => message
  boundary.render message
end

Then use it: call_and_rescue { open(type, with) }

like image 26
Piotr Kruczek Avatar answered Oct 06 '22 09:10

Piotr Kruczek