Barefoot Development

Ruby, mongrel_cluster, ferret and Linux PATHs

I have less hair. There's no two ways about it. In a fit of self-pity this weekend, I considered getting some of that great spray paint to solve the issue, but instead, I've decided to swear off various un*x distributions. Can't we all just get along? I digress.

While preparing a recent Rails application for production, I came across some odd issues with various ruby-powered applications and gems. I struggled to understand why these apps failed to launch on startup, but would work without a hitch when run "by hand" at the command prompt. After tearing through boot logs, application logs and a good portion of my hair, I finally figured it out: it's a PATH issue. On each of the boxes in question (a few Fedora Core and two RHE4 machines), ruby was installed in /usr/local/bin, and that, my friends, is not in the default PATH for root. So, I thought, "Easy fix. Simply add /usr/local/bin to the PATH in a system wide script like /etc/profile." Alas, t'was not to be. These newer incarnations of RedHat linux have the added security of SELinux, which, among many positive things, sets and unsets root's PATH multiple times during the boot sequence. In the end, we had to add /usr/local/bin directly within each component's startup script. The Rails (and gems) components we are using are mongrel, mongrel_cluster, and ferret (using the DRb server). For those keeping score at home, that's a 9.7 on the new technology in production (NTIP) scale. So you can keep your hair in tact, here are the startup scripts we're using.

Mongrel cluster:

#!/bin/bash
#
# Copyright (c) 2006 Bradley Taylor, bradley@railsmachine.com
#
# mongrel_cluster Startup script for Mongrel clusters.
#
# chkconfig: - 85 15
# description: mongrel_cluster manages multiple Mongrel processes for use \
# behind a load balancer.
#

CONF_DIR=/etc/mongrel_cluster
RETVAL=0
PATH=/usr/local/bin:$PATH

# Gracefully exit if the controller is missing.
which mongrel_cluster_ctl >/dev/null || exit 0

# Go no further if config directory is missing.
[ -d "$CONF_DIR" ] || exit 0

case "$1" in
start)
mongrel_cluster_ctl start -c $CONF_DIR
RETVAL=$?
;;
stop)
mongrel_cluster_ctl stop -c $CONF_DIR
RETVAL=$?
;;
restart)
mongrel_cluster_ctl restart -c $CONF_DIR
RETVAL=$?
;;
*)
echo "Usage: mongrel_cluster {start|stop|restart}"
exit 1
;;
esac


Ferret DRb server:
#!/bin/bash
#
# This script starts and stops the ferret DRb server
# chkconfig: 2345 89 36
# description: Ferret search engine for ruby apps.
#
# Sean Brown, Partner at Barefoot, Inc.
#
# save the current directory
CURDIR=`pwd`
PATH=/usr/local/bin:$PATH

RORPATH="/path/to/ror_root"

case "$1" in
start)
cd $RORPATH
echo "Starting ferret DRb server."
FERRET_USE_LOCAL_INDEX=1 \
script/runner -e production \
vendor/plugins/acts_as_ferret/script/ferret_start
;;
stop)
cd $RORPATH
echo "Stopping ferret DRb server."
FERRET_USE_LOCAL_INDEX=1 \
script/runner -e production \
vendor/plugins/acts_as_ferret/script/ferret_stop
;;
*)
echo $"Usage: $0 {start, stop}"
exit 1
;;
esac

Labels: , , , ,

The Joy of Rails Automated Testing

Many developers are not in the habit of adding unit and functional tests to their projects. With many frameworks, testing is a tedious addition that can get pushed aside because of time constraints. However, with Ruby on Rails, automated testing is baked right in, so there's no excuse for not using it. And, once you start using unit & functional tests, you will sleep better, have less stress, exercise more, lose weight, improve your marriage, and have more money to save for retirement. Well, all of those benefits may not come directly from automated testing, but you'll certainly build a much more high-quality application, and that can't hurt the rest of your life!

I'm not going to go in-depth on the basics of unit/functional testing here ... many other resources do a great job of that, such as the excellent Agile Web Development with Rails. However, I wanted to show a simple & quick way to add functional tests in response to daily development problems.

It happens all the time during development: you build a controller with a page and see "Action Controller: Exception caught" when you view it in your browser. You immediately move to the task of fixing whatever problem is revealed.

However, it only takes an extra minute to add a new functional test that will give you the added assurance that once you fix the problem, you'll never be surprised by it again because your test will automatically tell you if it happens again.

They key is in the very nice display that Rails renders when an exception is caught. Immediately following the error trace, Rails displays the current Request parameters. For example, I just received an error on a seach page with parameters like this:

Request

Parameters: {"commit"=>"Search", "office_id"=>"0", "school_name"=>nil, "first_name"=>nil, "last_name"=>nil}

The parameters are in a hash that is in the exact form needed by the get or post methods you use within a Rails functional test. So here's what you do:

1. Create a new method within the functional test file for your controller, something like this:

def test_search_empty_params
 # Use all empty fields
 get :search
 assert_response :success
 assert_template 'search'
 assert_not_nil assigns(:search_params)
 assert_not_nil assigns(:results)
end


2. Copy & paste the list of request parameters from the error page in your browser to the second parameter of the get method (this also works with the post method), so your test method ends up like this:


def test_search_empty_params
 # Use all empty fields
 get :search, {"commit"=>"Search", "office_id"=>"0", "school_name"=>nil, "first_name"=>nil, "last_name"=>nil}
 assert_response :success
 assert_template 'search'
 assert_not_nil assigns(:search_params)
 assert_not_nil assigns(:results)
end


Now, go on to fix whatever caused the problem in the first place, and run your functional test to not only make sure your problem is fixed, but that your fix didn't break anything else. Now, you'll be sure that the problem will never show up again, at least, not without a message from your test. Doesn't that bring you joy?

Doug Smith, Senior Developer, Barefoot

Labels: , , ,

Custom Sorting in Ruby

I have been an expert Java developer for many years, but over the past year I've had the opportunity to use Ruby on Rails for a couple real projects. I'm really enjoying Ruby and hope to to use it more often in the future.

My current RoR project has several models with bi-directional has_many :through relationships where two models link to a third join model that has one or more additional property fields. For example, Users link to Albums through a Selections join model. The selections table includes fields for user_id and album_id, with an additional field that contains the rating each user gives to each album. (This example uses different entity names to protect the client.)

These relationships are illustrated (with simplication) here:

class User < ActiveRecord::Base
  has_many :selections, :dependent => :destroy
  has_many :albums, :through => :selections
end

class Album < ActiveRecord::Base
  has_many :selections, :dependent => :destroy
  has_many :users, :through => :selections
end

class Selection < ActiveRecord::Base
  belongs_to :user
  belongs_to :album
end


I needed to be able to list all the albums chosen by a user, sorted by album name. I tried this:

@user.selections.each do |selection|
# ...
end


but the album names were not sorted. I thought that the nifty option :order => 'name' would work on the has_many :through, but alas, it didn't. Then, my Java experience reminded me of the Comparable interface. It turns out that Ruby includes a very similar pattern, but it comes complete with Ruby nice-ness.

When you sort an Array (or anything Enumerable), you can override the <=> method of the Object class to provide your own custom sorting code. (Just like the equals() method in Java.) The <=> method returns -1, 0, or 1 to indicate whether the instance is smaller, equal, or greater than the other object. Here's the method I added to the Selection model to tell it how to sort:

def <=>(o)
   # Compare album name
   album_name_cmp = self.album.name <=> o.album.name
   return album_name_cmp unless album_name_cmp == 0

   # Compare user last name
   user_ln_cmp = self.user.last_name <=> o.user.last_name
   return user_ln_cmp unless user_ln_cmp == 0

   # Compare user first name
   user_fn_cmp = self.user.first_name <=> o.user.first_name
   return user_fn_cmp unless user_fn_cmp == 0

   # Otherwise, compare IDs
   return self.id <=> o.id
end


Then, just change the block slightly to use the sorted version:

@user.selections.sort.each do |selection|
# ...
end


If the model you're trying to sort is not already Comparable, you should add the line include Comparable to include the Comparable mixin. ActiveRecord objects are already Comparable.

Have fun writing your own custom sorting rules in Ruby! Leave a comment if you need more details.

Doug Smith, Senior Developer, Barefoot

Labels: , ,

Automating Rails deployment without Capistrano

Certainly Capostrano is the most elegant way to deploy your Rails apps, but here's an easy way to automate Rails deployment using just rync and rake. And the best part is that it borrows from a previous blog entry.

This first step is optional, but boy will it make your life easier. Start by setting up public key authentication between your local development machine and your server. We've covered this ground before, so just make sure you're doing it for the account on your server that you'd normally use to FTP to your Rails application.

The second, and final, step: using rync with rake. To see full coverge of this topic (or how to do it if you're developing on a Windows machine, see the HOWTO.

Add the following block to the Rakefile in your local application directory:

desc "Deploy basic application directories"
task :deploy => :environment do
dirs = %w{ app lib test public config}
onserver = "login@remotehost:/home/rails-app-directory/"
dirs.each do | dir|
`rsync -avz -e ssh "#{RAILS_ROOT}/#{dir}" "#{onserver}" --exclude ".svn"`
end
end

You'll need to change the onserver line to use your actual username and hostname. Then at the command line, change to your Rails application directory and run:

$ rake deploy

That's it. Enoy your new found productivity.

Sean Brown, Partner, Technology at Barefoot

Labels: , ,

Installing Ruby on Rails with mod_fcgi for Apache 2

After a few hours of trial and error using advice from many different sites/posts, this is the process that I found successful in getting Ruby on Rails working with the Apache 2 fcgi module on linux. I hope this helps someone hang onto a few more hair follicles.

Before we begin, I can say that this was only successful when I did the setup in this order. Perhaps others have done it in a different way, but this worked for me. BTW, we're using Redhat Enterprise 3.

First, we'll install ruby.

curl -O ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.4.tar.gz
tar xvfz ruby-1.8.4.tar.gz
cd ruby-1.8.4
./configure --prefix=/usr/local
make
make install
cd ..


Just to make sure everything installed properly try this:

ruby --version

It should return something like this:

ruby 1.8.4 (2005-12-24) [i686-linux]

Now let's install ruby gems.

curl -O http://rubyforge.org/frs/download.php/5207/rubygems-0.8.11.tgz
tar xvfz rubygems-0.8.11.tgz
cd rubygems-0.8.11
ruby setup.rb
cd ..


Now we need to install the FastCGI development kit.

curl -O http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz
tar xvfz fcgi-2.4.0.tar.gz
cd fcgi-2.4.0
./configure --prefix=/usr/local
make
make install
cd ..


Allrighty then. We're moving now.

Install the bindings between Ruby and FastCGI

curl -O http://sugi.nemui.org/pub/ruby/fcgi/ruby-fcgi-0.8.6.tar.gz
tar xvfz ruby-fcgi-0.8.6.tar.gz
cd ruby-fcgi-0.8.6
ruby install.rb config
ruby install.rb setup
ruby install.rb install
cd ..


Next, install the mod_fcgi Apache 2 module.

curl -O http://fastcgi.coremail.cn/mod_fcgid.1.09.tar.gz
tar xvfz mod_fcgid.1.09.tar.gz
cd mod_fcgid.1.09


Check the Makefile to make sure top_dir points to the right directory. Edit if it doesn't.

make
make install
cd ..


It's finally time to install Rails.

gem install rails --include-dependencies

This will take a few minutes. Grab a refreshing beverage. If you really want to be entertained while improving your ruby skills, read why's (poignant) guide to ruby.

Time to get back to work. Before we go nuts trying to see if Rails and Apache are on speaking terms, let's make sure Rails' built in server, WEBrick works. We'll make a quick dummy application to try it out.

rails dummyapp
cd dummyapp
ruby script/server


You should see output similar to this:

=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2006-05-03 11:56:50] INFO WEBrick 1.3.1
[2006-05-03 11:56:50] INFO ruby 1.8.4 (2005-12-24) [i686-linux]
[2006-05-03 11:56:50] INFO WEBrick::HTTPServer#start: pid=8747 port=3000


Now, let's see what the application shows us. I usually do this in a new terminal window.

lynx http://localhost:3000

Hopefully, you can see a page that has "Welcome aboard, You're riding the Rails!" on it, along with links to documentation and other goodies. If you're not, begin abusing hair follicles. Assuming you did see it, quit out of lynx, then shutdown WEBrick in the other terminal by hitting ctrl-c on your keyboard.

Rails and Apache. Joanie and Chachi. Nick and Jessica. Peas and Carrots.

Let's make a super couple. To test Rails and Apache, we need to enter the appropriate lines into our Apache configuration file. I was doing this as an additional virtual host, so mine looks like so:


<VirtualHost 10.10.10.10:80>
ServerAdmin me@gmail.com
ServerName website.com
UseCanonicalName Off

# Set to development, test, or production
DefaultInitEnv RAILS_ENV production
# set to the "public" directory of your app
DocumentRoot /root/dummyapp/public

<Directory "/root/dummyapp/public">
RewriteEngine On
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
Options Indexes ExecCGI FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
AddHandler fcgid-script .fcgi
</Directory>
</VirtualHost>


Now that you've added the block to your Apache config, restart Apache and browse to you site. You should once again see the familiar "Welcome aboard, You're riding the Rails!"

And there was much rejoicing.

Sean Brown, Partner, Technology at Barefoot

Labels: , ,