Active Record is a pattern named by Martin Fowler in his book Patterns of Enterprise Architecture (ISBN 978-0321127426). It is a simple pattern where a class represents a table and an instance represents a row in that class. It is often used by ORM tools such as Hibernate and NHibernate as it provides a straightforward mapping between the world of objects and the world of relational data. Instances of an Active Record are responsible for loading and storing data, as well as the business logic that acts on that data.
What is ActiveRecord?
ActiveRecord is the Ruby implementation of the Active Record pattern. The code for ActiveRecord lives in the ActiveRecord module. This module contains the classes you need to build a database and to both read from and write to that database.
ActiveRecord is tied in many people’s minds to Rails, however you can use the code entirely independently of a Rails application, even down to using migrations to manage the database. I’ll show you how to do that here and how ActiveRecord is used in Rails.
Creating a connection
ActiveRecord is designed to be database independent. For any given database to work with ActiveRecord that database needs an adaptor and that adaptor has to be loaded. For these examples I’m going to use MySql, however other databases such as Oracle, SQL Server and PostgreSQL can also be used. When an ActiveRecord connection is created the adapter must be specified, this can be done in code or through configuration. The ActiveRecord Base class has an establish_connection method that can be called from code. For a Rails application this work is done by the Rails framework, but these are the steps you need to take if using ActiveRecord away from Rails.
The code to establish the connection looks like this:
ActiveRecord::Base.establish_connection( :adapter => "mysql", :host => "localhost", :username => "root", :database => "cricket_development")
We specify the adapter, server, username, password (blank in this case) and database to use.
Rails uses a configuration file to specify the same details. This file is a YAML file in the config directory called database.yml, and we can do the same thing here:
development: adapter: mysql encoding: utf8 database: cricket_development username: root password: socket: /tmp/mysql.sock…and then we could have code like:
config = YAML::load( File.open('database.yml')) ActiveRecord::Base.establish_connection( config["development"])
Creating the database
The ActiveRecord approach to creating and maintaining a database is to be as database independent as possible. This means writing Ruby code rather than DDL; the Ruby code is called a Migration and we write one for every change to the database.
Migrations derive from ActiveRecord::Migration and contain two class methods, up and down. A simple migration would look like this:
require "rubygems" require "active_record" class CreateCountries < ActiveRecord::Migration def self.up create_table :countries do |t| t.column :name, :string, :null => false t.column :short_name, :string, :null => false end def self.down drop_table :countries end end
This defines a class called CreateCountries that derives from Migration and lives in a file called 001_create_countries.rb. The naming convention is important here. We can use ActiveRecord to apply migrations automatically, that is, to only apply the migrations it needs to. To do this ActiveRecord creates a table in the database called schema_migrations in which it holds the number of the last migration applied. It then applies all other migrations whose version is greater than the value stored in the database. This version is the first part of the filename and can be simple integer, as in this case, or a datetime stamp, which is what Rails currently uses. The rest of the filename (create_countries) must ‘match’ the class name used for the migration.
Notice that the migration contains two methods, up and down. The up method is run when creating this part of the database and the down method when reverting to the previous state. The down method should perform the opposite behaviour of the up method.
Our up method creates a table called countries with two columns, name and short_name, both strings and both disallowing nulls. The down method simply drops that table.
The table name is part of Rails ‘convention over configuration’ mantra. Table names are plural and, as we will see, the classes that interact with the tables have singular names, so countries and Country in this case. Also all tables have a primary key field which in the default case we are using here is not specified. The primary key is a column called id with the type of integer.
If this migration was part of Rails then running the migration would simply be a matter of running the Rake command rake db:migrate. (Rake is the Ruby equivalent of Ant, NAnt or Make. It is a pure Ruby tool for executing build scripts.) However, if this is not a Rails application then there is no default Rakefile to use for this. Creating a Rakefile to run migrations is not difficult; it should look something like this:
require "rubygems" require "active_record" require "yaml" require 'rake' task :default => :migrate desc "Migrate the database through scripts in db/migrate. Target specific version with VERSION=x" task :migrate => :environment do ActiveRecord::Migrator.migrate( 'db/migrate', ENV["VERSION"] ? ENV["VERSION"].to_i : nil ) end task :environment do @config = YAML::load( File.open('database.yml')) ActiveRecord::Base.establish_connection( @config["development"]) ActiveRecord::Base.logger = Logger.new(File.open( 'database.log', 'a')) end
This says that the default task is called ‘:migrate’ and this depends on ‘:environment’. The :environment task initialises ActiveRecord using code like that we wrote previously and the :migrate task runs ActiveRecord::Migrator.migrate, specifying the directory containing the migrations and the version to run to.
Running the migration from the command line looks like this:
rake VERSION=xx…where xx is the migration to run up to or down to.
If you run the migrations without a version then ActiveRecord looks at the last version applied to the database and then applies every migration above that. If you run the migrations with a version then ActiveRecord will run the migrations either up to (running the up commands) or down to (running the down commands) that specific version. So you can run
rake VERSION=0…to restore the database to version 0, which means all tables will be deleted.
A migration can do many things – after all it’s just Ruby code. As well as creating and deleting tables you can add and remove columns, set column constraints (such as nullable) and also run code to initialise the data in a table. One thing you cannot do, though, is to specify database constraints such as foreign key constraints. You can add foreign key constraints in the migrations by adding SQL commands to the migration code. This goes against the concept of defining everything in Ruby but is sometimes necessary. For example to add a foreign key constraint you could do something like:
class CreateMatches < ActiveRecord::Migration def self.up create_table :matches do |t| ... end execute "ALTER TABLE matches ADD CONSTRAINT FK_MATCH_COUNTRY_AWAY FOREIGN KEY (away_team) REFERENCES countries (id);" end ... endNotice that that the SQL is in a particular dialect, in this case MySQL, which is one of the things that make this technique less than ideal!
You can also add constraints in the Ruby code. To understand how to do that we need to look at models.
Model classesNow that the database exists, what about access to it? This is achieved by creating one class for each table. Each class derives from ActiveRecord::Base. Instances of these classes represent rows in the tables. A model class would look like this:
class Country < ActiveRecord::Base end
Nothing else is necessary, no datamembers, not even properties.
To load data from a table you can use the finders defined by ActiveRecord. ActiveRecord has a basic ‘find’ method so you can write code like this:
countries = Country.find(:all)
There are other values you could use instead of :all, such as :first, you can also pass clauses or even send SQL directly to the database, for example
country = Country.find( :all, :conditions => "name = 'England'")
You can also amend the find such as:
countries = Country.find(:all)
As well as a standard ‘find’ method, ActiveRecord uses Ruby’s meta programming ability to generate methods on the fly. So, for example, you could do:
country = Country.find_by_name('England')
country = Country. find_by_short_name('Eng')
This means the loading mechanism is very versatile
To create a new record in the table you create a new instance of the class and then save it:
china = Country.new china.name = "China" china.short_name = "chi" china.save # or china.save!
The save options (with and without the !) are either silent or throw an exception if the save fails. For example, if there is a constraint failure in the database.
I mentioned above that you could add constraints to the models. So, for example, a match is between two countries identified by ids in the match table that must exist in the country table. The SQL (in the migration) looks like this:
execute "ALTER TABLE matches ADD CONSTRAINT FK_MATCH_COUNTRY_AWAY FOREIGN KEY (away_team) REFERENCES countries (id);" execute "ALTER TABLE matches ADD CONSTRAINT FK_MATCH_COUNTRY_HOME FOREIGN KEY (home_team) REFERENCES countries (id);"
However we can also add constraints to the models themselves.
Rails has the concepts of ‘has’ and ‘is owned by’ and these can be specified as constraints. These relationships can sometimes be difficult to understand. The ‘parent’ object uses has and the ‘child’ uses ‘belongs to’ where the child object is the table that contains the foreign key. You don’t need to specify both sides of the constraint. The constraints are class methods from the ActiveRecord::Base. Class calls has_one, belongs_to, has_many and has_and_belongs_to_many, let us define 1:1, 1:n and m:n relationships.
So for the matches and countries table the models might look something like:
class Country < ActiveRecord::Base end class Match < ActiveRecord::Base belongs_to :country end
Matches has a foreign key to countries so it contains the belongs_to clause. ActiveRecord uses the association name (:country) to calculate the foreign key details. It assumes the name of the class that Match is associated with is Country and that the foreign key field in Match is call country_id. Note that in this case we’re not specifying the has_one relationship in Country.
The methods also take various options to modify the behaviour. The above example is simplified and doesn’t actually work in the database we’ve defined. The matches table has two foreign key relationships with countries, neither of which is called country_id. To specify those constraints the Match class should look like this:
class Match < ActiveRecord::Base belongs_to :home_team, : class_name => "Country", : foreign_key => :home_team belongs_to :away_team, : class_name => "Country", : foreign_key => :away_team end
Now we specify the name of the association, the related class and the name of the foreign-key column.
Model classes can also specify other constraints, such as validations, for example the names and short names for a country must be unique. The class could be amended to look like this:
class Country < ActiveRecord::Base validates_uniqueness_of :name validates_uniqueness_of :short_name end
ActiveRecord and Rails
All the code above works as part of a standalone Ruby application or as part of a Rails application. You will notice some differences in Rails and there are also some things worth noting. The main difference between Rails code and that shown above is the migration names. We used names of 001_xxx, 002_xxx etc. whereas Rails uses a datetime stamp as the initial integer value. This is a change from earlier versions of Rails where simple integers were used. One advantage of using datetime stamps is that it’s easier to insert new migrations.
Rails also uses a specific structure for the application and places the migrations, classes and config files within it. The directory app/models contains database model classes, config/database.yml contains the configuration and db/migrate contains the migrations.
Rails also provides a large number of Rake tasks to manage the database (amongst other things).
ActiveRecord is the primary database access mechanism used in Rails applications. It is a database agnostic mechanism that is relatively easy to use as well as being highly extensible. It allows us to access records within a database but also to create and manage the database itself.