rails snippets

Installing Rails, mongrel and mongrel_cluster on Debian

Tagged rails, ruby, debian, install, sqlite3, mongrel, mongrel_cluster  Languages bash

DRAFT...

Install RubyGems

http://rubyforge.org/frs/download.php/29548/rubygems-1.0.1.tgz

tar zxvf rubygems-1.0.1.tgz

cd rubygems-1.0.1

ruby setup.rb

Install Rails

gem install rails

Install sqlite3 (optional)

apt-get install sqlite3 libsqlite3-dev
gem install sqlite3-ruby

Install mongrel and mongrel_cluster

$ gem install mongrel mongrel_cluster

$ mongrel_rails cluster::configure -e production \
  -p 8000 \
  -a 127.0.0.1 \
  -N 3 \
  -c /var/www/xyz/current


$ mongrel_rails cluster::start


$ useradd -g www-data -d /var/www mongrel

Surviving reboots

sudo mkdir /etc/mongrel_cluster

sudo ln -s /var/www/xyz/config/mongrel_cluster.yml /etc/mongrel_cluster/xyz.yml

sudo cp /usr/local/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.5/resources/mongrel_cluster /etc/init.d/

sudo chmod +x /etc/init.d/mongrel_cluster

sudo /usr/sbin/update-rc.d -f mongrel_cluster defaults

mongrel_cluster_ctl status

Stale pids

If your mongrels crash or if you kill them, mongrel_cluster won't start your mongrels because mongrel_cluster believes the processes are still running, instead mongrel_cluster complains and does nothing:

** !!! PID file tmp/pids/mongrel.8000.pid already exists.  Mongrel could be running already.  Check your log/mongrel.8000.log for errors.
** !!! Exiting with error.  You must stop mongrel and clear the .pid before I'll attempt a start.

To fix this simply add the --clean switch to the /usr/local/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.5/resources/mongrel_cluster startup script:

mongrel_cluster_ctl start -c $CONF_DIR --clean

Using backgroundrb to execute tasks asynchronously in Rails

Tagged backgroundrb, rails, ruby, distributed, messaging  Languages ruby

Draft...

Planning on using BackgroundDRB? Take a long look at the alternatives first

Ask yourself, do you really need a complex solution like BackgroundDRB? Most likely you don't, so use a simple daemonized process instead, see this snippet about the daemons gem for more information.

Heck, even a simple Ruby script run by cron every 5 minutes will be more stable than BackgroundDRB and require less work.

Even if you really need to process a lot of data asynchronously in the background, I wouldn't recommend BackgroundDRB, it's riddled with bugs and unstable in production, so use the BJ plugin instead.

Anyway, continue reading if you want to use BackgroundDRB...

Installing the prerequisites:

$ sudo gem install chronic packet

Installing backgroundrb

$ cd rails_project
$ git clone git://gitorious.org/backgroundrb/mainline.git vendor/plugins/backgroundrb

You can also get the latest stable version from the Subversion repository:

svn co http://svn.devjavu.com/backgroundrb/trunk  vendor/plugins/backgroundrb

Setup backgroundrb

rake backgroundrb:setup

Create a worker

./script/generate worker feeds_worker
class FeedsWorker < BackgrounDRb::MetaWorker
  set_worker_name :feeds_worker
  
  def create(args = nil)
    # this method is called, when worker is loaded for the first time
    logger.info "Created feeds worker"
  end
  
  def update(data)
    logger.info "Updating #{Feed.count} feeds."
    
    seconds = Benchmark.realtime do
      thread_pool.defer do
        Feed.update_all()
      end
    end

    logger.info "Update took #{'%.5f' % seconds}."
  end
end

Starting backgroundrb

First configure backgroundrb by opening config/backgroundrb.yml in your editor:

:backgroundrb:
  :ip: 0.0.0.0

:development:
  :backgroundrb:
    :port: 11111     # use port 11111
    :log: foreground # foreground mode,print log messages on console

:production:
  :backgroundrb:
    :port: 22222      # use port 22222

Next, start backgroundrb in development mode:

./script/backgroundrb -e development &

Call your worker

From the command line:

$ script/console
Loading development environment (Rails 2.0.2)
>> MiddleMan.worker(:feeds_worker).update()

When things go wrong

Asynchronous programming is complex, so expect bugs...

Rule #1 know who you're calling.

If you give your MiddleMan the wrong name of your worker, he'll just spit this crap at you:

You have a nil object when you didn't expect it!
The error occurred while evaluating nil.send_request
/usr/local/lib/ruby/gems/1.8/gems/packet-0.1.5/lib/packet/packet_master.rb:44:in ask_worker'
/Users/christian/Documents/Projects/xxx/vendor/plugins/backgroundrb/server/lib/master_worker.rb:104:in process_work'
/Users/christian/Documents/Projects/xxx/vendor/plugins/backgroundrb/server/lib/master_worker.rb:35:in receive_data'
/usr/local/lib/ruby/gems/1.8/gems/packet-0.1.5/lib/packet/packet_parser.rb:29:in call'
/usr/local/lib/ruby/gems/1.8/gems/packet-0.1.5/lib/packet/packet_parser.rb:29:in extract'
/Users/christian/Documents/Projects/xxx/vendor/plugins/backgroundrb/server/lib/master_worker.rb:31:in receive_data'

So for example this command would generate the above mentioned error:

MiddleMan.worker(:illegal_worker).update()

It's always nice to see a cryptic error messages such as this, it really deserves an award.

Check for bugs and bug fixes

git mainline commits

Going to production

Starting the daemon:

./script/backgroundrb -e production start

Configuring your task to run periodically

The following example makes backgroundrb call the FeedsWorker's update method once every 15 minutes:

:production:
  :backgroundrb:
    :port: 22222      # use port 22222
    :lazy_load: true  # do not load models eagerly
    :debug_log: false # disable log workers and other logging
# Cron based scheduling
:schedules:
  :feeds_worker:
    :update:
      :trigger_args: * */15 * * * *
      :data: "Hello world"

At the time of writing, the cron scheduler seems to be broken, so I prefer hard-coding the interval in the worker's create method:

def create
           add_periodic_timer(15.minutes) { update }
         end

If using Vlad or Capistrano, it's also a good idea to fix script/backgroundrb by changing these lines:

pid_file = "#{RAILS_HOME}/../../shared/pids/backgroundrb_#{CONFIG_FILE[:backgroundrb][:port]}.pid"
SERVER_LOGGER = "#{RAILS_HOME}/../../shared/log/backgroundrb_server_#{CONFIG_FILE[:backgroundrb][:port]}.log"

Resources

Backgroundrb homepage

Backgroundrb best practices

Backgroundrb scheduling

Debugging backgroundrb

Backroundrb's README

topfunky's messaging article

How to add OpenID support to your Rails application with the open_id_authentication plugin

Tagged openid, authentication, rails, ruby, plugin, restful_authentication  Languages ruby

These instructions have been tested with Rails 2.0.2 and ruby-openid 2.0.4. The snippet is an adaptation of the instructions in Ryan Bates' screencast on how to integrate OpenID with Rails.

Installing and configuring the restful_authentication plugin

Follow these instructions: How to install and use the restful_authentication Rails plugin.

Installing the ruby-openid gem

gem install ruby-openid

Installing the open_id_authentication Rails plugin

script/plugin source http://svn.techno-weenie.net/projects/plugins/
script/plugin install open_id_authentication

Create the migration files

rake open_id_authentication:db:create

Add the following to the self.up method in 002_add_open_id_authentication_tables.rb:

add_column :users, :identity_url, :string

Configuring the routes

map.open_id_complete 'session', :controller => "sessions", :action => "create", :requirements => { :method => :get }

Protect the identity_url field

Next protect the identity_url field, by adding the following to user.rb, account.rb or your custom user model:

attr_accessible :login, :email, :password, :password_confirmation, :identity_url

Add the following to the self.down method in 002_add_open_id_authentication_tables.rb:

remove_column :users, :identity_url

Integrating Open-id with the login page

Add the following to sessions/new.html.erb:

<label for="openid_url">OpenID URL</label><br />
<%= text_field_tag "openid_url" %>

Make sure you're showing flash messages, otherwise you won't see the error messages:

<html>
  <head></head>
  <body>
    <%= [:notice, :error].collect {|type| content_tag('div', flash[type], :id => type) if flash[type] } %>

    <%= yield %>
  </body>
</html>

Modifying the sessions controller

Copy & paste the following code in app/controllers/sessions_controller.rb:

class SessionsController < ApplicationController
  # Hack to fix: No action responded to show
  def show
    create
  end

  def create
    if using_open_id?
      open_id_authentication(params[:openid_url])
    else
      password_authentication(params[:login], params[:password])
    end
  end

  def destroy
    self.current_user.forget_me if logged_in?
    cookies.delete :auth_token
    reset_session
    flash[:notice] = "You have been logged out."
    redirect_back_or_default('/')
  end

  protected

  def open_id_authentication(openid_url)
    authenticate_with_open_id(openid_url, :required => [:nickname, :email]) do |result, identity_url, registration|
      if result.successful?
        @user = User.find_or_initialize_by_identity_url(identity_url)
        if @user.new_record?
          @user.login = registration['nickname']
          @user.email = registration['email']
          @user.save(false)
        end
        self.current_user = @user
        successful_login
      else
        failed_login result.message
      end
    end
  end

  def password_authentication(login, password)
    self.current_user = User.authenticate(login, password)
    if logged_in?
      successful_login
    else
      failed_login
    end
  end

  def failed_login(message = "Authentication failed.")
    flash.now[:error] = message
    render :action => 'new'
  end

  def successful_login
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
    end
    redirect_back_or_default('/')
    flash[:notice] = "Logged in successfully"
  end
end

OpenID authentication from behind a proxy

First, set the HTTP_PROXY environment variable to the proxy URL:

export HTTP_PROXY=http://proxy.aktagon.com:8080/

Then add the following to environment.rb:

OpenID::fetcher_use_env_http_proxy

Installing ImageMagick, mini-magick and rmagick on Mac OS X Leopard

Tagged imagemagick, attachment_fu, rails, ruby, mac, osx, leopard, mini-magick, rmagick  Languages ruby

I had no success installing ImageMagick and mini-magick with the instructions I found on this page but after some googling I found this blog post, which had the magic commands that worked for me:

sudo port install tiff -macosx  #disables the linkage with Apple's open gl
sudo port install ImageMagick

sudo gem install rmagick
sudo gem install mini_magick

To test mini-magick, open an irb console and paste in the following code:

require 'rubygems'
require 'mini_magick'

path = "public/images/0000/0003/logo.jpg"
image = MiniMagick::Image.new(path)

#print width and height
puts image[:width]
puts image[:height]

Geolocation with MaxMind's GeoIP and the geoip-city RubyGem

Tagged geoip, geolocation, maxmind, caching, rails  Languages ruby

Install GeoIP library

wget http://www.maxmind.com/download/geoip/api/c/GeoIP.tar.gz
tar -zxvf GeoIP.tar.gz
cd GeoIP
./configure --prefix=/opt/GeoIP
make && sudo make install

Install the geoip-city gem

gem install geoip_city -- --with-geoip-dir=/opt/GeoIP

Test the bindings

curl -O http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz

Fire up IRB and try the following code:

require 'rubygems'
require 'geoip_city'
db = GeoIPCity::Database.new('GeoLiteCity.dat')
result = db.look_up('192.143.34.23')
p result

Another option is to use hostip.info's database, as described in this article.

Create a wrapper

require 'rubygems'
require 'geoip_city'
require 'ostruct'

class Location < OpenStruct
end

class GeoIP
  class << self
    DB = GeoIPCity::Database.new('GeoLiteCity.dat')

    def lookup(ip)
      if result = DB.look_up(ip)
        location = Location.new

        #
        # {:country_code=>"FR", :country_code3=>"FRA", :country_name=>"France", :latitude=>46.0, :longitude=>2.0}
        #
        result.each do |key, val| 
          location.send "#{key}=", val
        end
      end

      location
    end
  end
end

Add some Rails caching

Combined with the above code this will give you cached IP lookups:

class GeoIP

  class << self
    def lookup_with_caching(ip)
      Rails.cache.fetch(ip, :expires_in => 1.month) do 
        lookup_without_caching(ip)
      end
    end

    alias_method_chain :lookup, :caching
  end
end

Alternatives

If you're unable to install the C extension you might want to have a look at the geoip gem, which is a pure Ruby library that can read the MaxMind's geoip database. It's slower but easier to install: http://geoip.rubyforge.org/

How to install and use the Sphinx search engine and acts_as_sphinx plugin on Debian Etch

Tagged sphinx, search, acts_as_sphinx, debian, etch, rails, install, libstemmer  Languages bash

Inspiration for this snippet was taken from this post on the Sphinx forum, plus this blog post.

Compiling Sphinx

First install the prerequisites:

sudo aptitude install libmysql++-dev libmysqlclient15-dev checkinstall

Next download sphinx, libstemmer and install everything and the fish:

cd /usr/local/src

wget http://sphinxsearch.com/downloads/sphinx-0.9.9.tar.gz
tar zxvf sphinx-0.9.9.tar.gz 

cd sphinx-0.9.9/

# Add stemming support for Swedish, Finnish and other fun languages.
wget http://snowball.tartarus.org/dist/libstemmer_c.tgz
tar zxvf libstemmer_c.tgz

./configure --with-libstemmer
make

make install

Configure Sphinx

Create a sphinx.conf file in your Rails config directory, as described here, or use this template.

Install acts_as_sphinx plugin

./script/plugin install http://svn.datanoise.com/acts_as_sphinx

Add acts_as_sphinx to your model:

class Documents
   acts_as_sphinx
end

Indexing content

rake sphinx:index

(in /var/www/xxx.com/releases/20080429144230)
Sphinx 0.9.8-rc2 (r1234)
Copyright (c) 2001-2008, Andrew Aksyonoff

using config file './sphinx.conf'...
indexing index 'xxx.com'...
collected 5077 docs, 0.6 MB
sorted 0.1 Mhits, 100.0% done
total 5077 docs, 632096 bytes
total 0.160 sec, 3950427.25 bytes/sec, 31729.86 docs/sec

Reindexing content

sphinx:index shouldn't be run while the searchd process is running, so use rake sphinx:rotate instead, which restarts the searchd process after indexing.

Starting the daemon

mkdir -m 664 /var/log/sphinx
rake sphinx:start

(in /var/www/xxx.com/releases/20080429144230)
Sphinx 0.9.8-rc2 (r1234)
Copyright (c) 2001-2008, Andrew Aksyonoff

using config file './sphinx.conf'...
Sphinx searchd server started.

Searching

Documents.find_with_sphinx 'why did I write this'

Rolling back database migration one step in Rails

Tagged rails, active record migration  Languages ruby

I found this awesome trick to rollback a Rails database migration one step. Add the following code to Rakefile.

namespace :db do
  desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n'
  task :rollback => :environment do
    step = ENV['STEP'] ? ENV['STEP'].to_i : 1
    version = ActiveRecord::Migrator.current_version - step
    ActiveRecord::Migrator.migrate('db/migrate/', version)
  end
end

And roll back with a breeze.

rake db:migrate
rake db:rollback

How to use jQuery with Rails 2.0 - aka How to fix "ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken)"

Tagged authenticity, rails, rails 2, token, jquery, javascript, ajax  Languages ruby

This is a slight variation of Henrik Nyh's code, which fixes an issue with IE6 that makes all Ajax requests use POST in IE6.

In application.html.erb, or whatever layout file you're using, put:

<%= javascript_tag "window.AUTH_TOKEN = '#{form_authenticity_token}';" %>

In application.js, or whatever JavaScript file you're using, put:

$(document).ajaxSend(function(event, request, settings) {
  if (typeof(window.AUTH_TOKEN) == "undefined") return;
  // IE6 fix for http://dev.jquery.com/ticket/3155
  if (settings.type == 'GET' || settings.type == 'get') return;

  settings.data = settings.data || "";
  settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(window.AUTH_TOKEN);
});

That's all...

Showing ActiveRecord error messages from jQuery Ajax actions and scripts

Tagged ajax, error, jquery, rails  Languages ruby

The HTML, in a layout file, for example application.html.erb:

<div id="error-message" style="display:none">
</div>

The JavaScript, rendered by for example create.js.erb:

<% if [email protected]? %>
<%
  errors = <<ERR
  <p>Please fix the following errors:</p>
  <ul>
    #{@category.errors.collect{|err| "<li>" + err[0] + " " + err[1] + "</li>" } }
  </ul>
ERR
%>
$('#error-message').html('<%= escape_javascript(errors) %>');
$('#error-message').show();
<% else %>
$('#error-message').hide();
<% end %>