One of the first places where the ActiveRecord pattern first appeared, was in the book Patterns of Enterprise Architecture by Martin Fowler. The ActiveRecord pattern embeds the knowledge of how to interact with the database directly into the class performing the interaction.
Per Wikipedia
With ActiveRecord, a database table or view is wrapped into a class, thus an object instance is tied to a single row in the table. After creation of an object, a new row is added to the table upon save. Any object loaded gets its information from the database; when an object is updated, the corresponding row in the table is also updated. The wrapper class implements accessor methods or properties for each column in the table or view.
This pattern is often implemented by ORM and Object Persistence Tools like Hibernate (Java) and ActiveRecord (ruby). The implementation framework handles generating the necessary SQL appropriate for the database being used and executing them over a database connection.
Recently, in a couple of projects I used ActiveRecord Migrations to manage database setup and migrations.
Below, I shall present a walk though the steps that I followed to get a set of ActiveRecord migrations working on my Ubuntu-9.0.4 desktop.
1. Check if you have ruby installed:
ruby -v
You’d see something like the following: ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
If not, then install ruby by executing the following from a terminal window:
sudo aptitude install ruby
Also, install rake, – ruby’s build program with capabilities similar to make using:
sudo aptitude install rake
2. Install rubygems:
sudo aptitude install rubygems1.9
3. Install the following ruby-gems
- ActiveRecord: sudo gem install activerecord
- Database adapter: I used the MySQL database; so I needed to install the mysql gem sudo gem install mysql
The above command results in the following error:
extconf.rb:8:in `require’: no such file to load — mkmf (LoadError)
Fix: Being a Debian or Ubuntu user you’ll find that the Ruby standard distribution is split into lots of little packages in order to comply with Debian packaging Guidelines. Installing ruby only gives you the ruby binary and a subset of the libraries for Ruby. You’ll need to add more packages if you want to utilize more of Ruby’s standard library.
Hence, we need to install the ruby-dev package prior to installing the mysql gem.
sudo aptitude install ruby-dev
4. Create directories to hold the migration definitions:
mkdir ~/workspace/db_setup
mkdir ~/workspace/db_setup/migrations
That’s all on the installation side. Now it’s time to write the migrations.
With ActiveRecord, each change to the database is termed as a migration. Migrations derive from ActiveRecord::Migration and enable a developer to generate a database structure using a series of Ruby script files (each of which is an individual migration) to define database operations. It’s a database-agnostic representation of the database. It uses generic data types for columns, like :binary, :string, :text, :boolean, etc. to define the kind of data to be stored in a column. A sample migration would look like the following:
File: migrations/001_create_institutions.rb
class CreateInstitutions < ActiveRecord::Migration
def self.up
create_table :institutions, {:id => true} do |inst|
inst.column :name, :string, :limit => 100, :null => false
inst.column :url, :string, :null => false
end
execute(’ALTER TABLE institutions ADD UNIQUE (name) ‘);
end
def self.down
drop_table : institutions
end
end
- The file-naming convention is important here:
- ActiveRecord uses the numeric prefix to determine which migrations it needs to apply. To do this, it creates a table called as SCHEMA_MIGRATIONS in the database in which the VERSION column holds the number from the last migration added. When the migrations are run without a target version specified, it will only execute the migrations that have number greater than that stored in the database, lowest numbered first.
- The class name should correspond to a camel case representation of the migration’s file name.
- Typically your project would have a collection of these migrations, in files with prefixes as 001_xx, 002_xx…. and so on, each defining a change desired on the database.
- The two methods up and down should be functional complements of each other. The up method is executed when upgrading the database while down method is executed while reverting to an earlier version.
- You can do many things in a migration – create and delete tables, add and remove columns, set column constraints (such as nullability and column length) and also run code to initialize the data in a table.
- As a convenience, you also have the execute method available (shown as the last statement in the up method) to run SQL / commands directly. This has many implications: it lets you execute commands that use specific database dialects, like adding database constraints. The following script allows you to execute SQL commands from an external file, say create_db.sql:
sqlCode = File.open(File.dirname(__FILE__) + “/create_db.sql”).read
sqlCode.split(”;\n”).each do |stmt|
execute stmt if stmt =~ /\S/
end
ActiveRecord can read the database configuration from a YAML config file called as database.yml (place it under ~/workspace/db_setup) that looks like:
# The # character signifies a comment.
#
# Define db config per environment here.
# yml files are formatted in a very strict way; you cannot insert spaces and tabs randomly.
# Typically they use two (2) spaces to indent options.
#
# The space between the ‘:’ and the configuration values is required.
dev:
adapter: mysql
database: devdb
username: dev_user
password: dev_pass
host: 127.0.0.1
qa:
adapter: mysql
database: $DB_NAME
username: $USER
password: $PASSWORD
host: $HOST
A set of ActiveRecord migrations can be executed as
1. A part of a Rails application by invoking
rake db:migrate RAILS_ENV=xx VERSION=yy
2. A standalone Ruby application: If it is not a part of a Rails application, there is no default Rakefile to use. So we’d need to create one (under ~/workspace/db_setup) with the following content:
require ‘rubygems’
require ‘active_record’
require ‘yaml’
task :default => :migrate
desc “Migrate the database through scripts in ‘migrations’. Target specific version with VERSION=x”
task :migrate => :environment do
ActiveRecord::Migrator.migrate(’migrations’, ENV["VERSION"] ? ENV["VERSION"].to_i : nil )
end
task :environment do
RAILS_ENV = (ENV['RAILS_ENV'] ||= ‘dev’)
dbconfig = YAML::load(File.open(’database.yml’))[RAILS_ENV]
ActiveRecord::Base.establish_connection(dbconfig)
# Enable diagnostic logging to help debugging.
# Note that ActiveRecord colorizes output for viewing in a terminal.
# But when viewed as a normal file, it isn’t readable. So we disable it.
ActiveRecord::Base.colorize_logging = false
logFile = File.open(’database.log’, ‘w’) # set it to ‘STDERR’ to enable logging to Std. ERROR console
ActiveRecord::Base.logger = Logger.new(logFile)
end
To execute the migrations, run the following command from the directory containing the RakeFile:
rake RAILS_ENV=xx VERSION=yy
Both the above mentioned ways lend themselves to be easily invoked from a multitude of build and CI tools, like ANT, CruiseControl, python scripts etc.
Thus we, saw how to configure ActiveRecord as a database agnostic mechanism of managing database setup and incremental migrations. It is relatively simple to use and highly extensible.
Although, some may argue that ActiveRecord leads to a high degree of coupling between application code and database structure; in many cases the issues due to this coupling is much easier to manage than to adopt an alternate complex solution.
