Sunday, December 25, 2011

Using Monit to monitor delayed_job in Rails 3

I am developing an application using RubyonRails3 . My application need to send alert notification , reminder via sms , email to hundreds of users. To make my application responsive I have to do these tasks in the background process. After exploring many alternatives I see that delayed_job fits my needs.  Delayed_job run a separate process with my rails app.

Delayed Job workers generally have a lifecycle that is equivalent to an application deployment.Because of this, their memory consumption grows over time and may eventually have high swapusage, causing workers to become unresponsive so  I  decide to use a monitoring tool Monit to watch  jobs. Monit can restart them when their memory usage hits a certain point. The good thing I like from monit is when a pre defined condition is matched, Monit can alert my for example by email by telling us what happen so I can take action accordingly.

I am using Ruby 1.9.2, rvm 1.8.6 , rails 3.07 on Ubuntu 10.10.

my application is located at : /var/www/rails3

/var/www/rails/.rvmrc file contains the following content:
rvm use 1.9.2@dyrm --create

Include the following line in you /var/www/rails3/Gemfile
gem "delayed_job"

Then to install delayed_job gem, in your terminal type:
/var/www/rails3$    bundle install

then
/var/www/rails3$ rails g delayed_job
/var/www/rails3$ rake db:migrate

#inserting job in my delayed_job
def self.alert notification
notification.users.each do |user|
Delayed::Job.enqueue(EmailJob.new(user, message)) unless user.email.blank?
Delayed::Job.enqueue(SmsJob.new(user, message)) unless user.phone_number.blank?
end
end

My Email job:
  class EmailJob
def initialize user, message
@user = user
@message = message
end

def perform
UserMailer.notify_members(@user, @message).deliver
end
end

  class SmsJob
def initialize user, message
@user = user
@message = message
end

def perform
Sms.send do |sms|
sms.to = @user.phone_number
sms.body = @message
end
end
end

To be able to puts your job into delayed_job, you job objects need to response to perform method. because perform method does not require any parameter so I need to pass through the constructor .

Installing Monit


In your terminal :
sudo apt-get install monit

configure monit to start up as daemon by editing the file /etc/default/monit with:

startup=1

Then start the daemon with:
sudo /etc/init.d/monit start

In my case I need to edit file

Some time the monit daemon does not start, however the output from terminal console

telling us the service  already started. I find the command:
sudo monit summary

is very useful to debug this. After all edit the file /etc/monit/monitrc
set alert channa@instedd.org

to set the alert for monit to alert to.
set httpd port 2812 and
# use address localhost # only accept connection from localhost
allow localhost # allow localhost to connect to the server and
allow admin:monit # require user 'admin' with password 'monit'
# allow @monit # allow users of group 'monit' to connect (rw)
# allow @users readonly # allow users of group 'users' to connect readonly

Use web user interface to monitor my process(on port 2812). Monit comes bundled with a web server using http basic auth.
allow admin:monit to login to web ui with user equal to admin and password monit
include /etc/monit/conf.d/*

I include all my monit script located in side folder: /etc/monit/conf.d

Create a file called notification inside /etc/monit/conf.d. It's name is not important at all.
check process delayed_job
with pidfile /var/www/rails/tmp/pids/delayed_job.pid
stop program = "/usr/bin/env RAILS_ENV=production BUNDLE_GEMFILE=/var/www/rails/Gemfile /home/chenseng/.rvm/bin/bundle-ruby-1.9.2-p290@dyrm exec /var/www/rails/script/delayed_job stop"
start program = "/usr/bin/env RAILS_ENV=production BUNDLE_GEMFILE=/var/www/rails/Gemfile /home/my_user/.rvm/bin/bundle-ruby-1.9.2-p290@dyrm exec /var/www/rails/script/delayed_job start"

if totalmem > 60 MB for 3 cycles then restart
 if cpu usage > 50% for 3 cycles then restart

Here we use rvm wrapper to run delayed_job from a monit process. rvm wrapper give us ability to run any ruby script in context of it's gemset without cd to it's own directory.

Create wrapper


   /var/www/rails$ rvm wrapper 1.9.2@dyrm "" bundle

this will create /home/my_user/.rvm/bin/bundle-ruby-1.9.2-p290@dyrm executable.

Debug


Before making sure that monit can start my delayed_job process correctly, I try to run delayed_job with my rvm wrapper independently.
In terminal cd to your home directory:
/home/my_user$ /usr/bin/env RAILS_ENV=production BUNDLE_GEMFILE=/var/www/rails/Gemfile /home/my_user/.rvm/bin/bundle-ruby-1.9.2-p290@dyrm exec /var/www/rails/script/delayed_job start

check the output if nothing go wrong then it is time to restart my monit:
  /home/my_app$ sudo monit stop all
/home/my_app$ sudo /etc/init.d/monit/restart
/home/my_app$ sudo monit start all
/home/my_app$ sudo monit summary

It is quite helpful to check if my monit config files are correctly written with the correct permission. To do this
sudo monit -c /etc/monit/monitrc
sudo monit -c /etc/monit/conf.d/notification

Web interface:


http://localhost:2832

 

 

Tuesday, November 29, 2011

nl2br, strtr php functions in ruby

For those who are php code know very well how important is strtr and nl2br in the web development.


When developing web application in rails I still need these 2 functions. so I dicide just to keep and implement the same name in ruby:
string.rb


class String
def str_translate values, preserve = false
self.gsub /\{[^\}]*\}/ do |match|
key = match[1..-2]
if preserve
values[key.to_sym] || match
else
values[key.to_sym] || ""
end
end
end

def nl2br
self.gsub /\n/ do |match|
<br />
end
end
end

string_spec.rb


require "spec_helper"

describe String do
describe "#str_translate" do
it "should be translated keys in the string by parameters in the list " do
str = "The message from {user} sent out on {day} "
strtr = str.str_translate(:user => "Ilab", :day => "Monday")
strtr.should eq "The message from Ilab sent out on Monday "
end

it "should preserve keys that not in the string by parameters in the list " do
str = "The message from {user} sent out on {day} "
strtr = str.str_translate({:day => "Monday"}, true)
strtr.should eq "The message from {user} sent out on Monday "
end

it "should remove the missing key from the string" do
str = "The message from {user} sent out on {day} "
strtr = str.str_translate(:day => "Monday")
strtr.should eq "The message from sent out on Monday "
end
end

describe "nl2br -new line to br" do
it "should convert all new line to br" do
original = ["\nthis is a great new\nIn the \n", "blah\n\n\nblah\n"]
converted = ["<br />this is a great new<br />In the <br />", "blah<br /><br /><br />blah"]
original.each_with_index do |str, index|
str.nl2br.should eq converted[index]
end
end
end
end

Wednesday, October 12, 2011

Rubyonrails contains php application in its subdirectory

There are many great web software built in PHP . At some point we need to integrate  one of them with our Rubyonrails app.

This tutorial will guide you how to do this. As an example I will use Wordpress since it is one of the most popular one. This can also be applied to any Php application.

Create project



  • Create an Rails project called "venta-facil.com.kh" in /var/www



  • Create a directory /var/www/venta-facil.com.kh/public/blog


 

this will allow you to access wordpress by http://yourdomain.com/blog . Change to whatever you want your url. I will keep highlighting the word blog so you can keep it consistent.

 

Go to wordpress.org download and extract the files to /var/www/venta-facil.com.kh/public/ blog . This directory is the root of your wordpress.

Create virtual host


Let start configuring virtual host. In your terminal   sudo nano /etc/hosts and add the following

127.0.0.1 venta-facil.com www.venta-facil.com

save it. I used my local ip 127.0.01. You can replace it to your web server ip. make sure the file is saved.

Configure your web server


Create a file /etc/apache2/sites-available/venta-facil.com.kh

<VirtualHost *:80>
ServerName venta-facil.com.kh
DocumentRoot /var/www/venta-facil.com.kh/public
<Directory /var/www/venta.com.kh/public>
AllowOverride all
Options -MultiViews
</Directory>

<Location /blog>
PassengerEnabled off
</Location>

</VirtualHost>

 

We need to tell apache to enable this virtualhost with the following command:    sudo a2ensite venta-facil.com.kh

and then reload apache with the following commant: sudo /etc/init.d/apache2 reload

 

Fire it by typing this: http://www.venta-facil.com.kh/blog in your web browser

 

Done !!!

Thursday, October 6, 2011

Wednesday, October 5, 2011

How to calculate the size of text (in pixel) dinamically with javascript and css

Get the size of text in javascript




Content in here



 

Text to calculate



 

Content in here

 
Text to calculate

We will use div#sizer

Wednesday, September 21, 2011

Uniqueness email with factory_girl

Use sequence to create valid object with uniqueness constraint.
#Model definition
class User < ActiveRecord::Base
validates :emal, :uniqueness => true
end

#define factory girl
Factory.sequence :email do |n|
  “user_email#{n}@domain.com”
end

Factory.define :user do |user|
  user.name "dongo"
  user.email { Factory.next(:email) }
end

Rails in subdirectory of wordpress

I am developing a rails app. the app is not ready so our product manager requires to use wordpress as main page and the application located in subdirectory of wordpress and let uses try the app via wordpress.

Requirement :


Apache with php and passenger.

I am using apache passenger for deploying rails app. Passenger make  Php and Ruby running smoothly in apache web server.

Below is the summary of what I did to make this happened

Create virtualhost


In /etc/apache2/sites-available/ create file call mysite.com (for simplicity mysite.com should be your domain name) containing the following code:

<VirtualHost *:80>
ServerName mysite.com
DocumentRoot /var/www/wordpress
RailsBaseURI /app
RailsBaseURI /playground
RailsBaseURI /staging
</VirtualHost>

Organise your projects directory


In /var/www directory contains 4 directories as below:

  • wordpress (root directory for wordpress => mysite.com ).

  • app (main rails app  => mysite.com/app ).

  • playground (rails app for playground purpose  => mysite.com/playground ).

  • staging (rails app for staging purpose =>  mysite.com/staging ).


Create symlink


inside /var/www/wordpress create the symlink to app, playground and staging.

ln -s /var/www/app                    /var/www/wordpress/app
ln -s /var/www/playground     /var/www/wordpress/playground
ln -s /var/www/staging            /var/www/wordpress/staging

The syntax is:


ln -s [target_source] [link]

We need symlink because when we access: http://mysite.com/app

then apache will go look for  [webroot]/app where our webroot we configure to be in    /var/www/wordpress/ so it will look for /var/www/wordpress/app

then the operating system serves /var/www/app instead because /var/www/wordpress/app is linked to /var/www/app

 

In case we enable SEO in wordpress then we need to tell apache to escape wordpress every request url starting with app, playground, staging

in /var/www/wordpress  modify .htaccess file as bellow

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !app*
RewriteCond %{REQUEST_FILENAME} !playground*
RewriteCond %{REQUEST_FILENAME} !staging*
RewriteRule . /index.php [L]
</IfModule>

In case if you are working on local machine you need to create hosts. in /etc/ edit hosts file and append the following content to it

192.168.1.100 mysite.com

where :

  • 192.168.1.100 is your computer ip. you can check you computer ip with ifconfig command.

  • mysite.com is your domain name


Tell apache that you want to enable your virtualhost


run the following command  a2ensite mysite.com

where :

  • a2ensite (apache2 enable site ) is the apache utility to enable your virtualhost.

  • mysite.com is the filename of virtual host you created previously in /etc/apache2/sites-available


Reload apache


sudo /etc/init.d/apache2 reload

 

Open up your browser and type :

http://mysite.com should be served by wordpress


http://mysite.com/app rails app.

Done !!!