Saturday, March 8, 2008

Ruby Scripting with ActiveRecord

I'm writing a small Ruby script to pull data from multiple CSV files and I want to store the data in a database using ActiveRecord. I knew projects such as Camping use ActiveRecord directly and after a quick search I came across this DevDaily post.

The example is close but not exactly what I want to do. Instead of MySQL I like to use SQLite3 as a development database. The database is stored in a single file and there is no server that you have to configure and maintain. This allows me to delete, copy, or rename the database and proves useful for having a particular snapshot for testing.

I also want to avoid keeping a separate script or firing up a SQL Client to create a new database. ActiveRecord::Migration provides the methods needed to manage your schema. Using the example from the DevDaily site, I added a new Migration class and changed the database adapter to sqlite3. You'll also notice that you don't need a :username parameter and that the :database parameter points to a file.

require "rubygems"
require "activerecord"

ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:host => "localhost",
:database => "/home/chuck/.ar.db")

class AddEmployee < ActiveRecord::Migration
def self.up
create_table :employees do |t|
t.column :first_name, :string
t.column :last_name, :string
end
end

def self.down
drop_table :employees
end
end

class Employee < ActiveRecord::Base
end

AddEmployee.up unless Employee.table_exists?

employee = Employee.new
employee.first_name = "Fred"
employee.last_name = "Flinstone"
employee.save

arr = Employee.find(:all)
puts arr.inspect


The new AddEmployee class inherits from Migration and creates the table when AddEmployee.up is called. I don't want to call AddEmployee.up each time since an SQLException is thrown if the table already exists. The ActiveRecord::Base#table_exists? method allows you to check this.

Running the script the first time shows that the table is created:


chuck$ ruby lib/ar.rb
== AddEmployee: migrating ====================================================
-- create_table(:employees)
-> 0.0117s
== AddEmployee: migrated (0.0196s) ===========================================

[#<Employee id: 1, first_name: "Fred", last_name: "Flinstone">]


Running the script a second time shows that a duplicate record is created in the existing table.


chuck$ ruby lib/ar.rb
[#<Employee id: 1, first_name: "Fred", last_name: "Flinstone">, #<Employee id: 2, first_name: "Fred", last_name: "Flinstone">]


Now everything I need to do with the database can be accomplished using Ruby.

Thursday, March 6, 2008

Unit Testing Ruby Scripts


I created a ruby script to accept a few parameters, validate the parameters, and abort, with message, if those parameters are invalid. I then ran into a problem when I wanted to unit test the script. The following abort_script.rb is used as an example:

abort "Usage: abort_script dirname filename" unless ARGV.size == 2 



First I tried to require the script file in the unit test. This doesn't work though since the ruby interpreter executes the script. Having just the two require statements in abort_script_test.rb produces the abort message.
require 'test/unit'
require 'lib/abort_script'

 
chuck$ ruby test/abort_script_test.rb
Usage: abort_script dirname filename

To test the script, as it exists, we have to shell out and call the script. Another problem surfaced when I tried this. The abort method sends the message to stderr instead of stdout. It turns out not to be very trivial to figure out how to get stderr from a sub-shell process. Eventually, google and I found two good resources that discuss handling stderr when executing an external program. Process Management and this Pasadena Ruby Bridgade blog entry called 6 Ways to Run Shell Commands in Ruby.


The method that worked for me was redirecting the shells stderr to stdout. The abort message can then be obtained via the returned stdout using the backquote (`) command.
require 'test/unit'
class AbortScriptTest < Test::Unit::TestCase
def test_parms
use_error = "Usage: abort_script dirname filename\n"

exit_status = `ruby lib/abort_script.rb 2>&1`
assert_equal(use_error, exit_status)
end
end

If you have a better way of unit testing ruby scripts (i.e. not classes) please leave a comment.

Thursday, August 23, 2007

Feisty ipscromp

I needed to get ipscromp working on Ubuntu 7.04 (Feisty Fawn) recently and ran into a familiar problem. ipscromp errors out with:

ipscromp: error while loading shared libraries: libcrypto.so.4: cannot open shared object file: No such file or directory


To fix this add a symbolic link:

$ ln -s /usr/lib/libcrypto.so.0.9.7 /usr/lib/libcrypto.so.4


Apparently there is not a debian package for the older version in the Ubuntu 'verse but the newer version is happily backwards compatible.

You will need to install libssl0.9.7 if it is not already installed.

Wednesday, June 6, 2007

Vim on Ubuntu

The following are items I commonly add to /etc/vim/vimrc config:

set tabstop=2
set shiftwidth=2
set expandtab
set visualbell t_vb=""
set nowrap

"key mappings
map <M-c> :tabprevious<CR>
map <M-v> :tabnext<CR>
map <M-n> :bn<CR>
map <M-,> :bp<CR>
map <M-k> :bd<CR>
map <M-;> :buffers<CR>


I'm a bit divided on having a global .vimrc file installed by default but have decided to roll with it. The above just captures settings that annoy me when I have to hunt them down on occasion for a new Ubuntu install.

Tip to pass on is that if you are learning to use Vim you should try to experiment/memorize one new feature a day.

The It's All Text Firefox addon allows you to edit Text Areas in forms with an external editor such as Vim. This extremely helpful if you are editing Wikis left and right.

Saturday, May 12, 2007

May Recap

I added the May Kentucky Ruby Users Group recap to the discussion group.