codedecoder

breaking into the unknown…


4 Comments

raising custom exception in ruby rails

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 🙂

Advertisements


2 Comments

memcached session store for devise in rails 3

session can be stored in cookies on client side or in database or memcached or any other store on server side. By default, rails store the session in cookies store. You can configure, your rails app to use any other store. I will explain it to configure with memcached.

memcached is a cache server, which can cache your resources. It can have maximum size of 64 MB, good enough to provide a excellent cache feature to your application. Some good insight on its benefits and pitfall is explained here .

NOTE : you do not need to do any thing special to change devise session store other then telling about the new cache store. It is smart enough to work with any cache store your rails app is configured to work with.

So, here we will configure our rails app to use memcached as cache store rather then the default cookies store.

STEP 1: Installing memcached

memcached is supported by all linux server ubuntu, redhat,centos etc. I am using ubuntu machine as my server, if you are using any other machine , change the command accordingly.

$ sudo apt-get install memcached

$ sudo /etc/init.d/memcached start # strat the memcahed server, the other options are {start|stop|restart|force-reload|status}

STEP 2: Installing the gem to handle memcached

A no of gems available there acting as memcached client, we will use the most populer one : dalli gem

Add below to gemfile and run bundle install

gem ‘dalli’

STEP 3: configure the cache store in environment file

Add below line to the environment file, in which you want to use memcahed, say I added it to staging.rb and production.rb. Be sure that, in whatever environment you have set mecached store, on that server memcached is installed and running, otherwise your session will not work.

config.cache_store = :dalli_store # It will manage cache for us with memcached

STEP 4: configuring devise to use the new cache store

We need to tell, devise, where to store its session data. With rails 3 the session configuration is present in

config/initializers/session_store.rb   file. You can see below line there

MyFinance::Application.config.session_store :cookie_store, key: ‘_my_finance_session’ # my_finance is my project name, the key can have any random string, but what you are seeing is generated by devise by default when it is installed.

Replace, it with the new configuration as below

MyFinance::Application.config.session_store ActionDispatch::Session::CacheStore, :expire_after => 30.minutes

This line will set devise session store to what ever cache_store set in the environment file

This, is the simplest configuration, where session is set to expire in 30 minute, there is a number of other option you can pass.You can get complete list of option here

That’s all, now whenever a user session is validated, you can see line like below on console

Cache read: _session_id:7ef18e9eae1e2d595d405820bf5b0b5d

This show that, your session is now not read from cookies but the memcached…so things working as expected 🙂


1 Comment

rails on nginx or apache with passenger gem

I have explained deploying your rails app with rvm, nginx and passenger in this post, but it was through brightbox repository for ubuntu. Here, I will explain, installing nginx + passenger and nginx + appache using passenger gem . The detail instruction guide for nginx+passenger is available here and for apache+passenger  here  .

NOTE : this post explained in terms of nginx + passenger but flow will remain same for nginx + appache. The only difference will be in step 5 where you need to run passenger-install-apache2-module instead of passenger-install-nginx-module

STEP 1: Understanding the Flow

-> You need to have nginx or apache installed on your server

-> You need to have passenger installed on your server

-> You need to configure nginx or apache to load your app and suites your other need

-> You need to tell nginx or apache to work with passenger as application server

-> You need to tell passenger, where is your ruby and gemset

STEP 2:Prerequisite

=> You should have access to your server. For, learning you can try all the steps below, on your local machine also , considering it as a server

=> You should have rvm installed on your server. It is good, if you have installed it as root user. For non root user, you need to grant some permission to some files and folders.

=> Set the default RVM. Say, you are using ruby ruby-1.9.3-p194 and gemset threepiller for your app, set them as default, so that, the correct ruby and gemset get loaded automatically , whenever you login to the terminal. You can do it with below command.

$ rvm –default ruby-1.9.3-p194@threepiller # replace ruby-1.9.3-p194 with your ruby version and threepiller with your gemset

=> When you will try to install nginx module, it will get installed in opt/nginx folder by default. Since we will keep the defaults, we need to give write permission to opt folder. infect, even if you specify separate path say /etc/nginx or usr/local/nginx, these folder should have write permission for the user, with which you are loged in and trying to install the module. If you are root user, you do not need to do anything, as you have all the permissions. For, other users, say you have installed RVM as non root user arun, then you need to give write permission to arun on the opt directory of the file system. So, follow the below step

$ sudo su – # will login you as root user, you can grant permission to opt directory only if U r root user
[sudo] password for arun:

# cd ../../ # It will login you to the File System

# ls -l
total 116
drwxr-xr-x   3 root     root      4096 Aug  6  2012 boot
drwxr-xr-x   2 root     root      4096 Aug  6  2012 cdrom
drwxr-xr-x   4 root     root      4096 Sep 27 15:57 opt # write permission not available to owner and group

So, you can see that opt do not have write permission for group and other, we will assign the permission as below

# chmod 777 -R opt # it will add write permission to all as you can see below

# ls -l
total 116
drwxr-xr-x   3 root     root      4096 Aug  6  2012 boot
drwxr-xr-x   2 root     root      4096 Aug  6  2012 cdrom
drwxrwxrwx  4 root     root      4096 Sep 27 15:57 opt # write permission added to all the users

=>Install, some library needed for passenger

$ sudo apt-get install libcurl4-openssl-dev

STEP 3: Installing the gem

You may, install the gem as usual by adding it to Gemfile and running bundle install. But, I suggest you to not install, it through gem file.I can give below reasons for this

1 -> this gem is not used in your code, but required externally.

2-> if you put it in gemfile, it will be available in scope of your gemset. You need to install it again for some other gemset

3-> You are forcing the user to install the gem, even if he want to install passenger by some other method as in this post.

O.K, so we are not going to add the gem to gem file but add it manually from the terminal. Further, we will add the passenger to global gemset. The global gemset get created by default for ever ruby installed, and any gem within it will be available to all gemset within a particular ruby

$ rvm gemset list
gemsets for ruby-1.9.3-p194 (found in /home/arun/.rvm/gems/ruby-1.9.3-p194)
docusign
global
sinatra_app
=>threepiller

So, we can see 4 gemsets docusign global sinatra_app and threepiller. The threepiller is currently used gemset as indicated by =>. let us switch the gemset to global as below

$ rvm gemset use global # change the gemset to global
Using ruby-1.9.3-p194 with gemset global

$ rvm gemset list # list the gemset again and you will find global being selected now
gemsets for ruby-1.9.3-p194 (found in /home/arun/.rvm/gems/ruby-1.9.3-p194)
docusign
=>global
sinatra_app
myfinance

Now, we will install the gem in global gemset as below

$ gem install passenger
Fetching: fastthread-1.0.7.gem (100%)
Building native extensions.  This could take a while…
Fetching: daemon_controller-1.1.1.gem (100%)
Fetching: rack-1.5.2.gem (100%)
Fetching: passenger-3.0.19.gem (100%)
Successfully installed fastthread-1.0.7
Successfully installed daemon_controller-1.1.1
Successfully installed rack-1.5.2
Successfully installed passenger-3.0.19
4 gems installed
Installing ri documentation for fastthread-1.0.7…
Installing ri documentation for daemon_controller-1.1.1…
Installing ri documentation for rack-1.5.2…
Installing ri documentation for passenger-3.0.19…
Installing RDoc documentation for fastthread-1.0.7…
Installing RDoc documentation for daemon_controller-1.1.1…
Installing RDoc documentation for rack-1.5.2…
Installing RDoc documentation for passenger-3.0.19…

Now, if you list the gem, you can find passenger there

$ gem list # listing the gem within a gemset

*** LOCAL GEMS ***

bundler (1.1.5)
daemon_controller (1.1.1)
fastthread (1.0.7)
passenger (3.0.19)
rack (1.5.2)
rake (0.9.2.2)
rubygems-bundler (1.0.3)
rvm (1.11.3.5)

So, passenger is there, now if you move to any other gemset, it will inherit from global and so passenger available there

STEP 4:  Understanding the passenger location and available modules

$ which passenger # will show where passeger is installed
/home/arun/.rvm/gems/ruby-1.9.3-p194@global/bin/passenger # so passenger is installed in bin folder at the shown path

$ cd /home/arun/.rvm/gems/ruby-1.9.3-p194@global/bin # move to bin folder

$ ls # list the files
bundle          passenger-install-apache2-module  passenger-status  rubygems-bundler-uninstaller
passenger      passenger-install-nginx-module    rackup          ruby_noexec_wrapper
passenger-config  passenger-memory-stats        rake

So, you can see a no of files in the bin folder. The important one for us is passenger-install-apache2-module and  passenger-install-nginx-module, we will install, them depending on we want nginx or apache for passenger

STEP 5: Installing nginx

We, will go with the default, so you just need to keep pressing enter or select proper options ,where ever asked .

$ ./passenger-install-nginx-module # it will install passenger for you, exposing a UI to you

NOTE : If, this module is not installed or broken You will get below error

Unable to start the Phusion Passenger watchdog because its executable (/usr/lib/phusion-passenger/agents/PassengerWatchdog) does not exist. This probably means that your Phusion Passenger installation is broken or incomplete, or that your 'PassengerRoot' directive is set to the wrong value. Please reinstall Phusion Passenger or fix your 'PassengerRoot' directive, whichever is applicable.

Solution : just reinstall the module, with the above command

This will be the first screen you will see

nginx-passenger module step 1

nginx-passenger module step 1

Press Enter, You will see the below screen

nginx-passenger module step 2

nginx-passenger module step 2

Passenger , do everything for you, so go with choice 1 i,e let passenger to install nginx. It will download and install nginx for you, as you can see in the below screen

nginx download and install

nginx download

In the end it will ask you to install nginx at /opt/nginx , the default path or to any other place. just go with the default and press enter.

You will see, the next screen showing that nginx is installed and passenger has been configured for you.

nginx installed

nginx installed

Here, You note down that, your nginx configuration file is present at /opt/nginx/conf/nginx.conf. Also, The passenger root is configured for you in it. Press enter to get the final screen, telling you how to configure your app and other things

configuring rails on nginx

configuring rails on nginx

STEP 5:  Configuring application with nginx

Your nginx configuration file is present at /opt/nginx/conf/nginx.conf file in this case ( it varies depending on how you install).If you open this file, you will see a number of lines, most of them are commented out and more like guidance for different configuration you can apply. But the basic and clean configuration i,e removing all the unwanted line, keeping only those being used currently should look as below.

worker_processes  1;

events {
worker_connections  1024;
}

http {
passenger_root /home/arun/.rvm/gems/ruby-1.9.3-p194@global/gems/passenger-3.0.19;
passenger_ruby /home/arun/.rvm/wrappers/ruby-1.9.3-p194@threepiller/ruby;

include       mime.types;
default_type  application/octet-stream;

sendfile        on;
keepalive_timeout  65;
server {
listen 8081;
server_name  localhost;
root /home/arun/Projects/myfinance/public;
passenger_enabled on;
}
}

NOTE:

If you have purchased a domain name for your server, you can replace localhost with that.
Also, If port is already in use you can use any other port, In my case, 8080 is used by tomcat so I used 8081

STEP 6: creating nginx init.d file

The above installation, do not create the nginx init file for you. You can create it as below

-> download the file by clicking this link.

-> Correcting the path in the downloaded file.

You need to correct the nginx path in the downloaded source. To me the downloaded file have below value

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/local/sbin/nginx

NGINX_CONF_FILE="/usr/local/nginx/conf/nginx.conf"

But our, nginx is installed at /opt/nginx folder , so these three constants are corrected as below

PATH=/opt/nginx/sbin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/opt/nginx/sbin/nginx

NGINX_CONF_FILE="/opt/nginx/conf/nginx.conf"

Also, there are some comments related to use of the file. remove all the
comments above the line '#! /bin/sh' . this should be the 
first line of the file

-> create nginx file in /etc/init.d folder and copy the modified content to it

-> grant execute permission to this after login as root user

# chmod 777 /etc/init.d/nginx

-> Run below command on terminal

$ sudo /usr/sbin/update-rc.d -f nginx defaults # it will update your system init to include nginx

NOTE : You may get below error while running above command. solution is explained here

System start/stop links for /etc/init.d/nginx already exist.

STEP 7: starting nginx

$ sudo  /etc/init.d/nginx restart
* Stopping Nginx Server…                                                                                                          [ OK ]
* Starting Nginx Server…                                                                                                             [ OK ]

STEP 8: Loading the app in browser

Go to browser and type localhost:8081 , you can see your home page  🙂

In case you get the error : 403 forbidden nginx , check the below things

-> root in nginx.conf file of step 5 should be set to public folder of your project

root /home/arun/Projects/myfinance/public; # will work

root /home/arun/Projects/myfinance; # will throw the above error

-> You Project folder i,e myfinance here should have read and execute permission. You can set it as below.

$ cd /home/arun/Projects # go to directory containing the source code myfinance in this case

$ chmod  777  -R  myfinance


4 Comments

System start/stop links for /etc/init.d/nginx already exist

I have been trying to install nginx with passenger. Things worked fine untill, I copied the nginx init.d script to /etc/init.d. and tried to update update-rc.d file, I got this terrible message  System start/stop links for /etc/init.d/nginx already exist .

This is the command to update-rc.d

$ sudo /usr/sbin/update-rc.d -f nginx defaults
 System start/stop links for /etc/init.d/nginx already exist.

SOLUTION: remove the already existing link… simple 🙂

I know, what is happening here. Actually, I have tried  to install nginx from source code few month back, but get into some problem and so, uninstalled it. But I think some how , System start/stop links for /etc/init.d/nginx remain there. which is now causing the problem.Well, then how to remove, the old link. As usual, to know more about any command, run it will –help option, and it will give you good amount of information, see it yourself below.

$ sudo /usr/sbin/update-rc.d –help
usage: update-rc.d [-n] [-f] <basename> remove
       update-rc.d [-n] <basename> defaults [NN | SS KK]
       update-rc.d [-n] <basename> start|stop NN runlvl [runlvl] […] .
       update-rc.d [-n] <basename> disable|enable [S|2|3|4|5]
        -n: not really
        -f: force

So, the first line give the syntax to remove any file from update-rc.d, in our case it is the nginx file

$ sudo update-rc.d -f nginx remove # -f option will remove the link forcefully
 Removing any system startup links for /etc/init.d/nginx …
   /etc/rc0.d/K20nginx
   /etc/rc1.d/K20nginx
   /etc/rc2.d/S20nginx
   /etc/rc3.d/S20nginx
   /etc/rc4.d/S20nginx
   /etc/rc5.d/S20nginx
   /etc/rc6.d/K20nginx

Now, we have get rid of the old nginx link, Let us update with the new one

$ sudo /usr/sbin/update-rc.d -f nginx defaults
 Adding system startup for /etc/init.d/nginx …
   /etc/rc0.d/K20nginx -> ../init.d/nginx
   /etc/rc1.d/K20nginx -> ../init.d/nginx
   /etc/rc6.d/K20nginx -> ../init.d/nginx
   /etc/rc2.d/S20nginx -> ../init.d/nginx
   /etc/rc3.d/S20nginx -> ../init.d/nginx
   /etc/rc4.d/S20nginx -> ../init.d/nginx
   /etc/rc5.d/S20nginx -> ../init.d/nginx

Well, it work fine…So we are done 🙂