I have explained handling ruby exception in this post. Read this post for exception handling basics. Here, we will look into creating and raising custom exception in ruby. So, when we need custom exception ??. Well, I give an example from my recent experience for user registration or creation for my application. It will help you to understand when and how to create custom exception, how to raise it and how to rescue it in your code logic. The requirement, is that, the user need to be registered on my site, only if his account also get created on Openam and a certificate created for him on Citadel.
So basically, it should be a transaction of four independent calls.
=> user registration on my site called myfinance.com
=> user account creation on Openam
=> certificate creation on citadel
=> send email to user with login detail
If any of them fail, I need to rollback all other.
This was my approach:
=> I will create custom exception for Openam , Citadel, Mailer etc
=> I will create openam_api.rb, citadel_api.rb, sendmail.rb file, containing the core logic of different feature
=> I will wrap the openam and citadel call in begin rescue end block
=> within rescue block I will raise custom exception I have defined for Openam , Citadel etc
=> I will catch the exception object in create action of my controller and do the rollback stuff
STEP 1: creating custom exception
Here, You need to understand that, Your custom class, is just like any other class, say for example your user model.
It look like this
class User < ActiveRecord::Base # you define attribute_accessor # you define methods end
You can do all these stuffs in your exception class also and access its attributes and methods on its instance.Here, the instance variable of exception is e in the below code structure
begin # some code to be rescued rescue Exception => e # e is the instance of exception being cached so can call all attributes and methods on e end
The difference is that, here your class inherit from StandardError rather then ActiveRecord::Base.
Lets, create a file my_custom_exception.rb in lib folder, so that it get compiled when ever the server is started. The file content look as below
class OpenamRegistrationFailedError < StandardError attr_accessor :failed_action def initialize @failed_action = OpenAMRegistration end end class CitadelCertificateCreationFailedError < StandardError attr_accessor :failed_action def initialize @failed_action = CitadelCertificateGeneration end end class MailerFailedError < StandardError attr_accessor :failed_action def initialize @failed_action = SendEmail end end
So, here I have defined a no of class in the same file, as I do not have much code to write in each class, but you can put each of them in a separate file as the normal convention is. Anyway, Here what Iam trying to do is to set the name of the class which implement the rollback in a particular case, say If I got CitadelCertificateCreationFailedError exception then failed_action attribute will contain, CitadelCertificateGeneration class set to it and since, I know which strategy failed, I will call rollback on all earlier strategies succeeded before this.
STEP 2: The code raising above custom exception
openam_api.rb file will look like this
class OpenAMRegistration def perform(user) begin # code which implement registration to openam rescue Exception => e # if some exception occur in begin block, controll will #come here and we will raise our openam custom exception as below raise OpenamRegistrationFailedError.new, "OpenaAM User registration failed" end end def rollback(user) # code which will delete the user created on openam end end
Similarly, citadel_api.rb file will look like this
class CitadelCertificateGeneration def perform(user) begin # code which implement certificate creation on citadel rescue Exception => e # if some exception occur in begin block, controll will #come here and we will raise our ciatdel custom exception as below raise CitadelCertificateCreationFailedError.new, "Citadel certificate creation failed" end end def rollback(user) # code which will delete the certificate created on Citadel end end
sendmail.rb file have below content
class SendEmail def perform(user) begin # code which implement sending mail to user rescue Exception => e # if some exception occur in begin block, controll will #come here and we will raise our email failure custom exception as below raise MailerFailedError.new, "Citadel certificate creation failed" end end end
See that, in all the above code, we are raising an instance of Exception class, so that we can access all attributes and methods of the exception class being raised from e . see in the next step , the use of e.failed_action . Similarly, you can call any method or attributes you have defined in your custom exception class.
STEP 3: Defining our create action in users_controller
require 'openam_api' require 'citadel_api' require 'sendmail' class UsersController < ApplicationController # defining a array of strategies I need to follow after my user creation STRATEGIES = [OpenAMRegistration, CitadelCertificateGeneration, SendEmail] def create @user = User.new(params[:user]) if @user.save STRATEGIES.each do |strategy| begin strategy.perform(@user) # it will call perform methods of each strategy rescue Exception => e STRATEGIES.each do |action| if e.failed_action.to_s == action.to_s # e.failed_action give the #name of class which raised the exception and failed break # we will break the roll back loop as since exception occur in #this class, other strategy after it are not called so no need of # rollback for them end action.new.rollback(@user) end end end flash[:notice] = "User created Successfully" else flash[:notice] = "User creation failed" end redirect_to root_path end end
So, in the above code be are looping over each STRATEGIES and calling perform method on them which perform different function according to class in which it is defined. If any of them failed due to exception, we get into rescue block and again loop over each STRATEGIES and call rollback method on it, breaking the loop at the strategy which raised the Exception, thus if Sendmail failed i,e mail failed to deliver, openam and citadel will be rolled back, if citadel failed only openam will be rolled back .
Hope, You have now basic idea of creating, using, and raising custom exception in Ruby🙂