Rescue all errors of a specific type inside a module
Asked Answered
L

2

7

I have a module in which I am performing all of my encryption/decryption tasks for a project. I would like to catch any OpenSSL::Cipher::CipherError exceptions that occur in this module so that I can handle them.

Is it possible to do something like

rescue_from OpenSSL::Cipher::CipherError, :with => :cipher_error

inside of a module?

Leif answered 15/5, 2013 at 14:4 Comment(3)
are you including that module inside a controller or not? rescue_from is meant to be used inside controllers only, so doing something like this inside plain old ruby objects would include some very dirty hacks.Duwe
Happy to help. Check here if you want to know what I meant by dirty hacks: simonecarletti.com/blog/2009/12/… - I don't like how things look when you do it in Ruby and it seems like adding unnecessary clutter. Extracting exception handling in its own method is a way to improve confidence of your code, which seems to fit the most in your scenario. Read more about it here: avdi.org/talks/confident-code-railsconf-2011Duwe
@Duwe If you'd care to post your comment as an answer I can accept and close this question. Thanks!Leif
D
8

I've investigated a little and came with a solution. You said you have a module in which you do your encryption. I'm guessing that module represents a singleton. My solution, however, requires you have an instance instead.

class Crypto
   def self.instance
      @__instance__ ||= new
   end
end

Extract encryption behavior in a module.

module Encryptable
   def encrypt
      # ...
   end

   def decrypt
      # ...
   end
end

Create a new module that handles exceptions.

module ExceptionHandler
  extend ActiveSupport::Concern

  included do
    include ActiveSupport::Rescuable
    rescue_from StandardError, :with => :known_error
  end

  def handle_known_exceptions
     yield
  rescue => ex
     rescue_with_handler(ex) || raise
  end

  def known_error(ex)
    Rails.logger.error "[ExceptionHandler] Exception #{ex.class}: #{ex.message}"
  end
end

So now you can use the newly defined handle_known_exceptions inside your Crypto. This is not very convenient because you haven't gained much. You still have to call the exception handler inside every method:

class Crypto
  include ExceptionHandler

  def print_bunnies
    handle_known_exceptions do
      File.open("bunnies")
    end
  end
end

No need to do this if we define a delegator that does that for us:

class CryptoDelegator
  include ExceptionHandler

  def initialize(target)
    @target = target
  end

  def method_missing(*args, &block)
    handle_known_exceptions do
      @target.send(*args, &block)
    end
  end
end

Completely override the initialization of Crypto, to use the delegator instead.

class Crypto
  include Encryptable

  def self.new(*args, &block)
    CryptoDelegator.new(super)
  end

  def self.instance
      @__instance__ ||= new
  end
end

And that's it!

Duwe answered 14/6, 2013 at 10:14 Comment(2)
Holy smokes, that's pretty awesome - thanks so much! I'll give this a go!!Leif
Haha, this was bothering me because I've asked myself your question a couple of times. Playing with this was so much fun for me, so I've decided to make a gem that enables rescue_from outside Rails. github.com/shime/rescue_from_rubyDuwe
R
0

I do not understand clearly your use case. But I am writing this answer, so that, someone else can find it helpful.

Scenario: I want to show only Stripe errors, but want to hide other error.

Solution:

begin
  Stripe::PaymentMethod.detach(payment_method_in_db.stripe_pmid)
rescue => e
  if e.class.name[0..7] == "Stripe::"
    flash[:error] = e.message
  else
    flash[:error] = 'Something went wrong. Please Try again.'
  end
  redirect_to billing_url
  return
end

When e is Stripe::InvalidRequestError or Stripe::CardError, I show original error message from Stripe.

But, when e is system error (Like StandardError), I hide this error from user, because, it can reveal system vulnerability.

Ruebenrueda answered 5/7, 2023 at 20:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.