I was working on a gem a couple months ago, and it came time for my boss to do a code review before we install the gem on another teams system. My boss pointed out that there were areas where I was returning FALSE, and baking in a lot of conditional statements and other handling, instead of using Ruby’s built in feature of error handling, which is designed to bubble exceptions up the call stack. He informed me that in situation that are not expected to occur, it’s best to raise an exception to halt execution and report the issue. He even recommended that I read Exceptional Ruby, a book devoted to the subject of proper exception handling.
I didn’t understand that Ruby exceptions bubble up to the previously calling scripts, and thus can be captured and handled further up the stack. My boss pointed out that since my gem would be used only by a special controller setup on this separate system, in the service of providing an API, I could incorporate exception handling at the controller level which would handle different types of errors.
We ended up defining several custom exception classes, used for different types of error which might occur. For instance, errors resulting from unexpected API calls would raise the custom MyAPI::UsageError (which inherits from standard RuntimeError), while expectations placed on their system while interfacing with its classes would raise one of the standard errors.
Part of the Exceptional Ruby guide informed me that you don’t always have to use a begin..rescue..end block. You can simply insert a rescue block at the end a method, thus rescuing all statements before it. As you can see, we simply rescued the types of exceptions caused by the system calling the API so that it would simply return the error in the response, instead of raising the error in the remote systems Airbrake logs.
def handler raise MyApi::UsageError, "Request must be HTTP POST" unless request.post? @response = Kabam::GmoApi::Server.handler(params[:json_request]) render :text => @response.to_json rescue MyApi::InvalidInputError, MyApi::UsageError => e error_response = MyApi::Response.new(:status => 'failure', :result => e.message) render :text => error_response.to_json end
For reference, here is the hierarchy of standard Ruby exceptions which you can use, or inherit from for your own custom exception classes.
Exception NoMemoryError ScriptError LoadError NotImplementedError SyntaxError SignalException Interrupt StandardError ArgumentError IOError EOFError IndexError LocalJumpError NameError NoMethodError RangeError FloatDomainError RegexpError RuntimeError SecurityError SystemCallError SystemStackError ThreadError TypeError ZeroDivisionError SystemExit fatal