One of the driving forces of Rails is to make things easier for developers. It does this partly by taking decisions out of developers hands. It's an 'opinionated' framework, and one of the opinions it has is on the pattern to use for database access. Its choice in this case is the 'active record' pattern.
Rails has an ActiveRecord module and the model classes all derive from
ActiveRecord::Base
for example
BlogEntry < ActiveRecord::Base
it is this module that provides the active record support for the framework.
Like much of the rest of Rails, ActiveRecord follows naming conventions. Here for example the BlogEntry class represents a row in the blog_entries table. How does this happen?
Looking back to the Learning Rails - Part 2 post you will see that this script was run
script/generate scaffold blog_entry ...
This script created two files, the file with the model class BlogEntry definition and a "migration". The migrations are "scripts" that help create and mange the database definitions, essentially they are DDL for Rails.
Migrations are used to both set up and tear down databases. The files contain class definitions that specify the steps to take when managing the database.
Migrations are timestamped so that it is easy to apply migrations in the correct order and to rollback those migrations in reverse order if needs be. The migrations live in the db/migrate directory. Currently there are 4 migrations in there
20080925064318_create_sessions.rb
20080925065056_create_blogs.rb
20080925064319_create_users.rb
20080925065210_create_blog_entries.rb
The first is a fairly standard Rails migration that creates the session tables (run rake db:sessions:create to create this), the others are specific to this application. Each migration has a date-time as part of the file name and it's this name that determines the order in which the migrations are run. The 20080925065210_create_blog_entries.rb looks like this
class CreateBlogEntries < ActiveRecord::Migration
def self.up
create_table :blog_entries do |t|
t.string :title, :null => false
t.text :entry, :null => false
t.integer :author_id, :null => false
t.datetime :entry_added_date
t.datetime :entry_last_edited
t.timestamps
end
end
def self.down
drop_table :blog_entries
end
end
So it's a class that derives from ActiveRecord::Migration and provides two class methods (static methods to C#/C++ folks), up and down (it's the "self" that indicates that these are class methods and not instance methods). You can run the migration from the command line by using the Rake command
rake db:migrate
This runs any migrations that have not yet been run. How does it know which migrations to run? Tthere is a database table that holds the information about the migrations that have been run.
$ mysql -u root
mysql> use rblog_development
mysql> show tables;
shows something like
+-----------------------------+
| Tables_in_rblog_development |
+-----------------------------+
| blog_entries |
| blogs |
| schema_migrations |
| sessions |
| users |
+-----------------------------+
and
mysql> select * from schema_migrations;
+----------------+
| version |
+----------------+
| 20080923152418 |
| 20080923152427 |
| 20080923152435 |
| 20080925064318 |
| 20080925064319 |
| 20080925065056 |
| 20080925065210 |
+----------------+
on my machine as I type this. Notice that the last entry in the table matches the datetime portion of the name of the last migration file.
When a migration is run (assuming it has not yet been added to the database), then the self.up
method is executed. This method creates or modifies database entries. In the case of the blog_entries migration it creates the table and adds the eight columns from these five entries.
(title, entry, author_id, entry_added_date, entry_last_edited and timestamps
mysql shows this
mysql> show create table blog_entries;
+--------------+---------------------------------+
| Table | Create Table |
+--------------+---------------------------------+
| blog_entries | CREATE TABLE `blog_entries` (
`id` int(11) NOT NULL auto_increment,
`title` varchar(255) NOT NULL,
`entry` text NOT NULL,
`author_id` int(11) NOT NULL,
`entry_added_date` datetime default NULL,
`entry_last_edited` datetime default NULL,
`created_at` datetime default NULL,
`updated_at` datetime default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+--------------+---------------------------------+
Notice that timestamps turns into two columns, and that an id column has been added as a primary key
A migration can also be rolled back. Running rake db:migrate rollback will rollback the last migration, or a specific version can be specified. For example
rake db:migrate VERSION=20080925065056
Running the migrations this way runs the migrations in reverse order up to the specified migration, on the way the self.down mwthod of each migration is called. For the blog_entries migration that would drop the table. The down method should undo whatever the up method did!
One of the interesting (and frustrating) things about Rails is the way the migrations and the models work together. Running the script/generate scaffold blog_entry creates two files, the migration and the model. Looking in the model file there is ... nothing, just the class definition. The knowledge about the members of this class is in the migrations. This takes DRY (Do Not Repeat Yourself) to the limit but it can mean looking in several files (there maybe more than one migration per model) to find everything that the class uses. If the migrations get too "spread out", i.e. there are three or more migrations with modifications to one table then it is worth amalgamating those migrations into one file.
Posted
Oct 10 2008, 03:37 PM
by
kevin-jones