ruby-on-railsruby-on-rails-3exceptionrescue

Rescue all errors of a specific type inside a module


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?


Solution

  • 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!