ActiveRecord, more quickly

Sunday, October 14

A while back I wrote about the ActiveRecord stub I use to easily hack around with AR. I’ve altered it to just create in-memory sqlite databases, and also to silence AR::Migration. Here’s the updated version:

require 'rubygems'
require 'active_record'

ActiveRecord::Base.logger = Logger.new(STDOUT) if 'irb' == $0

ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :dbfile => ':memory:')
ActiveRecord::Migration.verbose = false

ActiveRecord::Base.silence do
  ActiveRecord::Schema.define(:version => 1) do
    with_options :force => true do |m|
      m.create_table 'examples' do |t|
        t.string :title
        t.timestamps
      end
    end
  end
end

class Example < ActiveRecord::Base
end

if __FILE__ == $0
  require 'test/unit'

  class TestExample < Test::Unit::TestCase
    def test_should_flunk
      flunk
    end
  end
end

Faster Fixtures in Rails 2.0

Monday, October 01

Rails 2.0 includes optimizations for the loading of fixtures in tests that offer a significant speedup. After updating one of my projects to the latest from svn, my tests are running up to 50% faster than before.

Units:
  • before: 17s
  • after: 12s
  • improvement: ~ 30%
Functionals:
  • before: 10s
  • after: 5s
  • improvement: ~ 50%

This particular project has a lot of tests, and to be honest, even 12 seconds is too slow for my tastes. But the improvement sure is welcome. In newer projects, I’m using UnitRecord to test without the database. Obviously, this is much faster. But for fixture-dependent projects, upgrading to Rails 2.0 will yield a significant improvement out of the box.

ActiveRecord, quickly

Thursday, September 06

I often want to experiment with a database model using ActiveRecord, from outside the context of a Rails application. Ideally, I want to do this all in one file. Where I can see my migrations and my models at the same time and tweak as needed.

This is the stub I’ve been using lately for that task.

require 'rubygems'
require 'active_record'

ActiveRecord::Base.logger = Logger.new(STDOUT) if 'irb' == $0

ActiveRecord::Base.establish_connection(
  :adapter  => 'mysql',
  :database => 'example',
  :username => 'root',
  :host     => 'localhost'
)

ActiveRecord::Base.silence do
  ActiveRecord::Schema.define(:version => 1) do
    with_options :force => true do |m|
      m.create_table 'examples' do |t|
        t.string :title
        t.timestamps
      end
    end
  end
end

class Example < ActiveRecord::Base
end

if __FILE__ == $0
  require 'test/unit'

  class TestExample < Test::Unit::TestCase
    def test_should_flunk
      flunk
    end
  end
end

I simply define all my migrations and each model class here, and then write a few tests to ensure things are working the way I expect. Changing the schema and adding/removing fields is a snap, and the database tables are dropped and recreated each time the file is run.

The tests are run selectively, and AR::Logger is set to STDOUT when $0 is irb because I often want to play with my creation interactively on the console. This helps me get a sense of how things work, and how my objects feel from an API perspective.

Here’s what the stub above looks like from an irb session:

$ irb
>> require 'example_models'
-- create_table("examples", {:force=>true})
   -> 0.1467s
-- initialize_schema_information()
   -> 0.0006s
-- columns("schema_info")
   -> 0.0027s
=> true

>> Example.create!
  Example Columns (0.064651)   SHOW FIELDS FROM examples
  SQL (0.000134)   BEGIN
  SQL (0.000316)   INSERT INTO examples (`updated_at`, `title`, `created_at`) VALUES('2007-09-06 11:14:05', NULL, '2007-09-06 11:14:05')
  SQL (0.000682)   COMMIT
=> #<Example id: 1, title: nil, created_at: "2007-09-06 11:14:05", updated_at: "2007-09-06 11:14:05">
>> 

I find this to be a really fast way to prototype ideas.

Migrations demystified

Thursday, February 16

Migrations in Rails allow you to incrementally change your database schema using pure Ruby by generating auto-numbered class files that can be checked into your version control system and easily deployed with a rake task. Perhaps the best part about this approach is that it’s database agnostic: it doesn’t matter whether you’re using mysql, or postgresql, or whatever—as long as the adapter supports migrations, you don’t need to worry about writing database specific sql statements anymore.

Prior to migrations, I would usually create sql files containing any necessary transformations and keep them under version control. This approach works for a while, and if you’re the only one working on the project you may not notice an immediate need for migrations because your schema is easy to manage. But trust me, as soon as you begin working with others, or if you have to deploy database changes in the production environment, you’ll be glad you took the time to learn about migrations.

Enough talk. Let’s dig in by creating our first migration. From your Rails root, type

./script/generate migration Genesis

This will create a new file in db/migrate called 001_genesis.rb. (Note that the numeric prefix is automatic; since this is our first migration, it is numbered as such). This file has a single class that extends ActiveRecord::Migration containing two empty methods: up and down. For each migration, you define instructions for updating in the up method, and use the down method to roll back any changes. So, if you were to, say, create a new table in the up method, you could drop the table in the down method thereby reversing your changes. Rails provides a number of methods that you use to create and drop tables, add and remove columns, rename columns, and more (see the AR::ConnectionAdapters::SchemaStatements documentation for the complete list of them).

Let’s get to work on on the up method by creating a new table. We’ll let the code speak for itself:


class Genesis < ActiveRecord::Migration
  def self.up
    create_table :messages do |t|
      t.column :name, :string
      t.column :body, :text
    end
  end
  ...
end

As you can see, `create_table` takes a table name and a block. Inside the block, we pass a fieldname and a format type to the column method. That’s all there is to it! If you were to run `rake migrate` right now, this simple migration would create a new table called messages with the columns id, name, and body. You might have noticed that we didn’t even bother creating the ubiquitous auto_increment id column. That’s because we don’t need to. If it’s omitted, Rails is smart enough to go ahead and add it for us, saving us the typing.

Now, creating tables is all right and good if you’re starting from scratch. But if this is an existing project I bet most of us will have already created a db/create.sql file that houses a ddl full of tables and maybe even data to be initialized. Ideally, we’d like our first migration to set all this up for us, but that might be a lot of work depending on the size our our schema. Wouldn’t it be nice to easily generate a our first set of transformations from our existing schema without having to type them all in by hand?

Rake to the rescue. Rails includes a rake task called `db_schema_dump` that will generate a programatic representation of your existing database structure in just the flavor we need for a migration. Go ahead and give it a try:

$ rake db_schema_dump

After rake is all finished, you should find a fresh new file in your db/ directory called schema.rb. Open it up and have a look. See? There are all the calls to create_table already done for you. All you have to do is copy everything inside the block given to “ActiveRecord::Schema.define” into the up method of your migration and you’re ready to rock.

With your initial schema defined in the up method, it’s time to give it a full test. But before we do that, we should probably put something in the down method. Given that this is our first migration we can’t really roll back to anything (other than an empty database, I guess), so you might call this migration irreversible. Well, wouldn’t you know it—there’s a special exception you can raise for rare situations such as this called (you guessed it) IrreversibleMigration. Our down method, then, would look like this:


def self.down
  raise IrreversibleMigration
end

Let’s try out our migration. Drop and recreate your development database so that we have an empty canvas to start with, and then run $ rake migrate. After a few seconds of crunching, rake will unceremoniously complete its task and leave you with a blinking cursor at the command prompt. But if all went according to plan, your database should be chalk full of tables again. Go check it out.

Sometimes our schema definitions initialize data for us. You’ll be happy to know that adding data with migrations is easy and practical. Here’s a quick example:


def self.up
  create_table :messages do |t|
    t.column :name, :string
    t.column :body, :text
  end

  Message.create :name => 'Test',
                 :body => 'Drank my sleep from a can'
end

Notice that we’re using the Message model to create our data (Message.create). So, the Model’s rules (like validations, callbacks, etc.) are always enforced. Since Rails is of the opinion that all access to the database should be through the Model, this is a good thing. You wouldn’t want to execute a sql file that added invalid data to your database, would you? Of course not.

Let’s take a typical example of a User model that uses the before_create callback to hash the password. If I were to add a new user via sql, how would I go about hashing their password? (No, putting the hashed string directly in the sql isn’t a good idea—what if you change the hash’s salt?) Initializing data using the Model means that the callbacks will fire, and the password will get hashed as expected. Nice, huh?