About Me

My Photo
Unnikrishnan KP
Kannur, kerala, India
View my complete profile

Friday, February 12, 2010

Give "try" a try !

Rails versions >= 2.3 has a useful "try" method, which can neatly eliminate the need for code blocks like this, which we find often in Rails apps:-

  1. @post = Post.find_by_permalink("hello-world")  
  2. unless @post.nil?   
  3.   @post.body  
  4. end  
Instead of this, we can avoid the 'nil' check by using try as follows:- 

  1. Post.find_by_permalink("hello-world").try(:body)   

This returns body if post is present, else returns nil without throwing any exception.

Monday, October 19, 2009

Adding Gmail support to rails app

./script/plugin install git://github.com/collectiveidea/action_mailer_optional_tls.git

Add these to config/environments/production.rb

config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true

ActionMailer::Base.smtp_settings = {
:tls => true,
:enable_starttls_auto => true,
:address => "smtp.gmail.com",
:port => 587,
:domain => "abc.com",
:authentication => :login,
:user_name => "xyz@gmail.com",
:password => "xyz_password"
}

Tuesday, April 14, 2009

Lazy loading of page fragments

Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed. It can contribute to efficiency in the program's operation if properly and appropriately used. The opposite of lazy loading is Eager Loading.

Home page of one of my websites was taking a little too much time to load, even after applying usual optimization techniques. Then I noticed few sections on that page, which were not so frequently used by the visitors. But these sections were causing lot of load on the server,as some heavy database queries were required to fetch data for these sections.

So I decided to try lazy loading on this page, for loading these sections - load the main first without these sections. Once the main page is loaded fully, send ajax requests to the server, fetch the data and load these sections.

This involved 2 steps :

i. Separating the content to be lazy loaded into independent partials, and enclosing them within named "div"s.

ii. Triggering Ajax requests after the page loads fully.

For this, Prototype provides an Event observer

  1. Event.observe(window, 'load', function() {  
  2. //JS code here  
  3. });  
Following code accomplished lazy loading for me

In the view:
  1. <script>  
  2. Event.observe(window, 'load', function() {  
  3.   new Ajax.Request('/home/active_discussions', {asynchronous:true, evalScripts:true})  
  4.   new Ajax.Request('/home/top_posts', {asynchronous:true, evalScripts:true})  
  5.   new Ajax.Request('/home/popular_people', {asynchronous:true, evalScripts:true})  
  6. });  
  7. </script>  


In the controller:
  1. def active_discussions  
  2.   @active_discussions = Discussion.most_active(20)  
  3.   render :update do |page|  
  4.     page.replace_html "active_discussions",:partial => "home/active_discussions"  
  5.   end   
  6. end  
  7.   
  8. def top_posts  
  9.   @popular_people = Post.top_posts(10)  
  10.   render :update do |page|  
  11.     page.replace_html "top_posts",:partial => "home/top_posts"  
  12.   end   
  13. end  
  14.   
  15. def popular_people  
  16.   @popular_people = User.popular_people(50)  
  17.   render :update do |page|  
  18.     page.replace_html "popular_people",:partial => "home/popular_people"  
  19.   end   
  20. end  

Tuesday, March 17, 2009

Contacts import from standard address books

Blackbook and Contacts are two excellent ruby gems that help us import contacts from standard address books like Gmail,Yahoo,Hotmail and Outlook (csv format).

I am presenting below sample code to use them with a rails application to import contacts.

View
  1. Bulk Import  
  2. <% form_tag import_user_invitations_path(@user),:multipart => true do %>  
  3.   
  4. <select name="from" id="t" onchange="select_source(this)">    <option value="">Select source</option>    <option value="gmail">Gmail</option>    <option value="yahoo">Yahoo</option>    <option value="hotmail">Hotmail</option>    <option value="csv">Outlook</option>  </select>  
  5.   
  6. <div id="email_import" style="display: none;">  
  7.  <p>  
  8.    Email Address  
  9.    <input name="login" type="text">  
  10.  </p>  
  11.  <p>  
  12.    Enter Your Password  
  13.    <input name="password" type="password">  
  14.  </p>  
  15.  <p>  
  16.    Note : we are not going to save your Password anywhere  
  17.  </p>  
  18.   
  19.  <p>  
  20.    <%= submit_tag "Import contacts" %>  
  21.  </p>  
  22. </div>  
  23.   
  24. <div id="csv_import" style="display: none;">  
  25.  <p>  
  26.    Outlook CSV file  
  27.   
  28.    <input name="csv" type="file">  
  29.  </p>  
  30.   
  31.  <p>  
  32.    <%= submit_tag "Import contacts" %>  
  33.  </p>  
  34. </div>  
  35. <% end %>  
  36.   
  37. <script>  
  38.   function select_source(x)  
  39.   {  
  40.     if(x.value == 'csv')  
  41.     {  
  42.       $('email_import').hide();  
  43.       $('csv_import').show();  
  44.     }  
  45.     else   
  46.      if(x.value == '')  
  47.      {  
  48.       $('csv_import').hide();  
  49.       $('email_import').hide();  
  50.      }  
  51.      else  
  52.      {  
  53.       $('csv_import').hide();  
  54.       $('email_import').show();  
  55.      }  
  56.   }  
  57. </script>  

Controller
  1. def import  
  2.  @user = current_user  
  3.  if params[:from] == "csv"  
  4.    temp_file = "#{RAILS_ROOT}/tmp/#{Time.now.to_i.to_s}.csv"  
  5.    f = open(temp_file,"w")  
  6.    f.write(params[:csv].read)  
  7.    f.close  
  8.    @all_contacts = Blackbook.get :csv,:file => open(temp_file)  
  9.  else  
  10.    case params[:from]  
  11.      when "gmail"  
  12.        @all_contacts = Contacts::Gmail.new(params[:login],params[:password]).contacts      
  13.      when "yahoo"  
  14.        @all_contacts = Contacts::Yahoo.new(params[:login],params[:password]).contacts      
  15.      when "hotmail"  
  16.        @all_contacts = Contacts::Hotmail.new(params[:login],params[:password]).contacts    
  17.    end  
  18.  end  
  19. end  
@all_contacts will be an array of hashes. Each hash will be of the form {:name => name,:email => email}

Monday, March 16, 2009

Fetching emails from Gmail - POP+SSL+Ruby

Download source code

POP (Post Office Protocol) is the protocol used for communication between an email client and email server, for pulling emails from the server.

Ruby has a net/pop library which implements POP. Emails can be fetched from POP servers easily with this.

Gmail's POP server requires SSL support (for extra security through encryption). The net/pop library that comes with Ruby 1.8.6 doesn't have SSL support. But the latest version of net/pop that comes with Ruby 1.9 has SSL support. So we need to download and use net/pop.rb from Ruby1.9 source code for fetching emails from Gmail (provided along with source code at the top).

  1. Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_NONE)  
  2. Net::POP3.start('pop.gmail.com', 995, username, password) do |pop|  
  3. if pop.mails.empty?  
  4.   puts 'No mail.'  
  5. else  
  6.   pop.each_mail do |mail|  
  7.     p mail.header  
  8.     p mail.pop  
  9.   end  
  10. end  
  11. end  


mail.header returns a raw string of all the email headers. mail.pop will return the entire raw email. Results of both mail.header and mail.pop are not in readily usable form.

Tmail library (gem install tmail) will help us parse the email and provide the email attributes like (subject,from,body etc).

  1. Net::POP3.enable_ssl(OpenSSL::SSL::VERIFY_NONE)  
  2. Net::POP3.start('pop.gmail.com', 995, username, password) do |pop|  
  3. if pop.mails.empty?  
  4.   puts 'No mail.'  
  5. else  
  6.   pop.each_mail do |mail|  
  7.     email = TMail::Mail.parse(mail.pop)  
  8.     p email.subject  
  9.     p email.body  
  10.     p email.from  
  11.   end  
  12. end  
  13. end  


Download source code

Thursday, March 12, 2009

Fast and efficient file downloads with rails

send_file is synonymous with file-download for rails programmers. It works fairly well. When we use send_file, the rails application directly streams the file to the client(browser). So if the file to be downloaded is large in size, one rails process has to be dedicated for this streaming. If another request arrives to the application during this time, either another rails process needs to be started, or the the new request has to wait until the streaming of file is over.

This problem can be solved if we can offload the file streaming process to someone else rather than having the rails application do it. Our webserver can do this for us. Rails application processes the download request (checks for authorization etc) and then sends a blank response with path of the file and a specific header set. When the webserver sees this header, it takes the file from the given path and streams it to the client.

This specific header is called "X-Sendfile" for Apache and "X-AccelRedirect" in the case of Enginx.

How to do it

1. Install the apache module mod_xsendfile

2. Add the line below to apache configuration file, to enable apache to access files outside the public folder

  1. XSendFileAllowAbove on  


3. In controller:
  1. def download  
  2. send_file '/home/unni/files/abc.avi':type=>"video/x-msvideo":x_sendfile=>true  
  3. end  


Email queueing with ActionMailer,Rails and Crontab

There may be situtations where our web applications will need to send hundreds of emails together. Eg: Importing email addresses from gmail address book and sending invitations to all emails from imported list.

In such situtations, if we try to send the mails directly from our rails application, sending of all these emails will take some time, and the application waits till all the emails are sent. It would be preferable if we could queue the emails, and send them at a later time, rather than making the application wait for all emails to be sent.


Simple solution - queuing up emails in database, and assigning a cron job to send them in batches.

Model
  1. #need to require all models whose objects will be serialized and saved in QueuedMail.args. This is to enable smooth deserialization  
  2. require 'invitation'  
  3.   
  4. class QueuedMail < ActiveRecord::Base  
  5.     serialize :args  
  6.       
  7.     def self.send_email  
  8.       find(:all:order=> "priority desc, id desc":limit=>50).each do |mail|  
  9.         mailer_class = mail.mailer.constantize  
  10.         mailer_method = ("deliver_" + mail.mailer_method).to_sym  
  11.         mailer_class.send(mailer_method, *mail.args)  
  12.         mail.destroy  
  13.       end   
  14.       true  
  15.     end   
  16.     
  17.     def self.add(mailer, method, args, priority)  
  18.       QueuedMail.create(:mailer=>mailer.to_s, :mailer_method=>method.to_s, :args => args, :priority=> priority)  
  19.     end   
  20. end  


Migration
  1. class CreateQueuedEmails < ActiveRecord::Migration  
  2.   def self.up  
  3.     create_table :queued_mails do |t|   
  4.       t.column :mailer , :string  
  5.       t.column :mailer_method:string  
  6.       t.column :args:text  
  7.       t.column :priority:integer:default=> 0  
  8.     end   
  9.   end   
  10.   
  11.   def self.down  
  12.     drop_table "queued_emails"  
  13.   end   
  14. end  


Queueing an email - Controller
  1. Example 1  
  2.   
  3. args = [subscriber.email,"admin@abc.com",newsletter.subject,newsletter.body]  
  4. QueuedMail.add("UserMailer","queued_email", args, 0)   
  5.   
  6. Example 2  
  7.   
  8. invitation = Invitation.create(:from_id => sender.id, :to_email => email)  
  9. QueuedMail.add("UserMailer","invite_new_user",[invitation],1)  


Sending queued emails - Crontab
  1. */5 * * * * cd /home/unni/public_html/abc/current && ruby script/runner "QueuedMail.send_email"  


I have taken the idea from http://romaniaonrails.com/queue-emails-ruby-rails-actionmailer-cron-job with slight modifications.

Subscribe Now: standard