7 min read

ActiveRecord, Migrations, and Models

ActiveRecord is the ORM layer (see the section Connecting Rails to a Database in the previous article) used in Rails. It is used by controllers as a proxy to the database tables. What’s really great about this is that it protects you against having to code SQL. Writing SQL is one of the least desirable aspects of developing with other web-centric languages (like PHP): having to manually build SQL statements, remembering to correctly escape quotes, and creating labyrinthine join statements to pull data from multiple tables. ActiveRecord does away with all of that (most of the time), instead presenting database tables through classes (a class which wraps around a database table is called a model) and instances of those classes (model instances). The best way to illustrate the beauty of ActiveRecord is to start using it.

Model == Table

The base concept in ActiveRecord is the model. Each model class is stored in the app/models directory inside your application, in its own file. So, if you have a model called Person, the file holding that model is in app/models/person.rb, and the class for that model, defined in that file, is called Person. Each model will usually correspond to a table in the database. The name of the database table is, by convention, the pluralized (in the English language), lower-case form of the model’s class name. In the case of our Intranet application, the models are organized as follows:

Table

Model class


File containing class definition (in app/models)

people

Person

person.rb

companies

Company

company.rb

addresses

Address

address.rb

We haven’t built any of these yet, but we will shortly.

Which Comes First: The Model or The Table?

To get going with our application, we need to generate the tables to store data into, as shown in the previous section. It used to be at this point where we would reach for a MySQL client, and create the database tables using a SQL script. (This is typically how you would code a database for a PHP application.) However, things have moved on in the Rails world.

The Rails developers came up with a pretty good (not perfect, but pretty good) mechanism for generating databases without the need for SQL: it’s called migrations, and is a part of ActiveRecord. Migrations 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. The “operations” part of that last sentence is important: migrations are not just for creating tables, but also for dropping tables, altering them, and even adding data to them.

It is this multi-faceted aspect of migrations which makes them useful, as they can effectively be used to version a database (in much the same way as Subversion can be used to version code). A team of developers can use migrations to keep their databases in sync: when a change to the database is made by one of the team and coded into a migration, the other developers can apply the same migration to their database, so they are all working with a consistent structure.

When you run a migration, the Ruby script is converted into the SQL code appropriate to your database server and executed over the database connection. However, migrations don’t work with every database adapter in Rails: check the Database Support section of the ActiveRecord::Migration documentation to find out whether your adapter is supported. At the time of writing, MySQL, PostgreSQL, SQLite, SQL Server, Sybase, and Oracle were all supported by migrations.

Another way to check whether your database supports migrations is to run the following command in the console (the output shown below is the result of running this using the MySQL adapter):

  >> ActiveRecord::Base.connection.supports_migrations?
=> true

We’re going to use migrations to develop our database, so we’ll be building the model first. The actual database table will be generated from a migration attached to the model.

Building a Model with Migrations

In this section, we’re going to develop a series of migrations to recreate the database structure outlined in Chapter 2 of the book Ruby on Rails Enterprise Application Development: Plan, Program, Extend.

First, we’ll work on a model and migration for the people table. Rails has a generate script for generating a model and its migration. (This script is in the script directory, along with the other Rails built-in scripts.) The script builds the model, a base migration for the table, plus scripts for testing the model. Run it like this:

  $ ruby script/generate model Person
exists app/models/
  exists test/unit/
    exists test/fixtures/
    create app/models/person.rb
    create test/unit/person_test.rb
    create test/fixtures/people.yml
    exists db/migrate
    create db/migrate/001_create_people.rb

Note that we passed the singular, uppercase version of the table name (“people” becomes “Person”) to the generate script. This generates a Person model in the file app/models/person.rb; and a corresponding migration for a people table (db/ migrate/001_create_people.rb). As you can see, the script enforces the naming conventions, which connects the table to the model.

The migration name is important, as it contains sequencing information: the “001” part of the name indicates that running this migration will bring the database schema up to version 1; subsequent migrations will be numbered “002…”, “003…” etc., each specifying the actions required to bring the database schema up to that version from the previous one.

The next step is to edit the migration so that it will create the people table structure. At this point, we can return to Eclipse to do our editing. (Remember that you need to refresh the file list in Eclipse to see the files you just generated).

Once, you have started Eclipse, open the file db/migrate/001_create_people.rb. It should look like this:

    class CreatePeople         def self.up
            create_table :people do |t|
                # t.column :name, :string
            end
        end
        def self.down
            drop_table :people
        end
    end

This is a migration class with two class methods, self.up and self.down. The self.up method is applied when migrating up one database version number: in this case, from version 0 to version 1. The self.down method is applied when moving down a version number (from version 1 to 0).

You can leave self.down as it is, as it simply drops the database table. This migration’s self.up method is going to add our new table using the create_table method, so this is the method we’re going to edit in the next section.

Ruby syntax
Explaining the full Ruby syntax is outside the scope of this book. For our purposes, it suffices to understand the most unusual parts. For example, in the create_table method call shown above:,

    create_table :people do |t|
        t.column :title, :string
        ...
    end

The first unusual part of this is the block construct, a powerful technique for creating nameless functions. In the example code above, the block is initialized by the do keyword; this is followed by a list of parameters to the block (in this case, just t); and closed by the end keyword. The statements in-between the do and end keywords are run within the context of the block.

Blocks are similar to lambda functions in Lisp or Python, providing a mechanism for passing a function as an argument to another function. In the case of the example, the method call create_table:people is passed to a block, which accepts a single argument, t; t has methods called on it within the body of the block. When create_table is called, the resulting table object is “yielded” to the block; effectively, the object is passed into the block as the argument t, and has its column method called multiple times.

One other oddity is the symbol: that’s what the words prefixed with a colon are. A symbol is the name of a variable. However, in much of Rails, it is used in contexts where it is functionally equivalent to a string, to make the code look more elegant. In fact, in migrations, strings can be used interchangeably with symbols.

 

LEAVE A REPLY

Please enter your comment!
Please enter your name here