meetteem: tech addict

software development, etc.

Viewing posts tagged 'rails'

Using ActiveRecord Migrations in Django Apps?

In Episode 40 of the Rails Envy podcast, using Rails ActiveRecord migrations in Django apps was mentioned.

I'm comfortable with AR, and AR migrations are more readable and convenient than using plain SQL to migrate the models, so I decided to give it a try. (I use plain SQL when I migrate the database schemas in my Django projects and I haven't used Django Evolution yet.)

Here's what I did. (If you wanna see the AR migration script now, click here.)

Test Bed

I created a Django project, set up my database, and updated settings.py.

$ django-admin.py startproject mysite
$ cd mysite
$ mysqladmin create mysite
$ mate .                   # open the project in TextMate and update the db connection info in setting.py

Next, I created a Django app called blog. Yeeeehaw! Blog!

$ ./manage.py startapp blog

And added the Post model in blog/models.py

# blog/models.py
from django.db import models

# Create your models here.
class Post(models.Model):

    title = models.CharField(max_length=60)
    body = models.TextField()

    date_created = models.DateTimeField()
    date_published = models.DateTimeField()

Then I added the blog application in the INSTALLED_APPS tuple in settings.py, validate, and created the blog tables.

$ ./manage.py validate
$ ./manage.py syncdb

Check out the CREATE TABLE statements for the blog app.

$ ./manage.py sql blog
BEGIN;
CREATE TABLE `blog_post` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(60) NOT NULL,
    `body` longtext NOT NULL,
    `date_created` datetime NOT NULL,
    `date_published` datetime NOT NULL
)
;
COMMIT;

A New Attribute

Uh. Okay. Now, for the fun part. I added date_updated to the Post model. Kept it simple.

class Post(models.Model):
    .
    .
    .

    date_updated = models.DateTimeField()

And checked the newer CREATE TABLE statements for the blog app.

$ ./manage.py sqlall blog
BEGIN;
CREATE TABLE `blog_post` (
    `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY,
    `title` varchar(60) NOT NULL,
    `body` longtext NOT NULL,
    `date_created` datetime NOT NULL,
    `date_published` datetime NOT NULL,
    `date_updated` datetime NOT NULL
)
;
COMMIT;

Hmm.. A new NOT NULL column date_updated.

The ActiveRecord Migration Script

Now for the standalone ActiveRecord migration script.

# File: add_date_updated.rb
require 'rubygems'
gem 'activerecord'
require 'activerecord'

# Setup database connection
ActiveRecord::Base.establish_connection(
  :adapter => "mysql",
  :database => "mysite",
  :username => "teem",
  :password => "passw0rd",
  :host => "localhost"
)

# Migration script
class AddDateUpdated < ActiveRecord::Migration
  def self.up
    add_column :blog_post, :date_updated, :datetime, :null => false
  end

  def self.down
    remove_column :blog_post, :date_updated
  end
end

# Migrate
AddDateUpdated.migrate(:up)

The script is pretty straight-forward. It sets up the database connection, defines the migration script, and finally runs the migration. You can always migrate down by replacing the :up with down, but that's a little obvious, right? Also, note that the table name is blog_post, and also the :null => false condition.

Notes, Etc.

  1. It's not really DRY, since the db connection information is in the script itself. Maybe creating a database.yml and putting db connection info there could make things DRYer. The Ruby script and the settings.py can simply load the yaml instead.
  2. I didn't bother with the schema information, versioning stuff. Should I?
  3. If you wanna try it out, the ActiveRecord documentation is here and the ActiveRecord Migration documentation here.

Factory Girl!

Last month, factory_girl was featured in the RailsEnvy podcast. factory_girl is one of the best things that happened to my Rails TDD/BDD life. It's the only other thing I use with rspec and rspec-rails, though I'm pretty open, so suggestions to make making tests easier are very much welcome. The one con is that factory_girl is tied to ActiveRecord, but it's not really an issue for me because all my non-personal projects use AR. If you are still using fixtures, take a look at factory_girl. It's really nice. Anyway...

factory_girl has a nice feature that lets you define a sequence which you can use to generate unique things. For example, to generate an email address, you can do this (taken from factory_girl's rdoc):

# define a sequence
Factory.sequence :email {|n| "user#{n}@example.com" }

# calling the `next` method
Factory.next :email
# => "user1@example.com"

Factory.next :email
# => "user2@example.com"

However, one of the behaviors in my projects that is not so easy to do with factory_girl is generating unique strings that cannot contain numbers.

I added this behavior in my projects using the following monkey patch I made.

class Factory

  def self.sequence(name, initial_value = 1, &block)
    self.sequences[name] = Sequence.new(initial_value, &block)
  end

  class Sequence

    def initialize(initial_value = 1, &proc)
      @proc = proc
      @value = initial_value
    end

    def next
      prev, @value = @value, @value.next
      @proc.call(prev)
    end

  end

end

This would make the sequence more generic such that anything that has a next method can be passed. A string object has next method so we can use that to generate unique and sequential strings, with no numeric characters. For example,

Factory.sequence :email, 'aaaa' {|s| "user#{s}@example.com"}

# calling the `next` method
Factory.next :email
# => "useraaaa@example.com"

Factory.next :email
# => "useraaab@example.com"

We can even implement a PrimeSequence class and pass it to Factory.sequence to generate a prime number sequence. ;)

Last night, I forked factory_girl in GitHub and applied this monkey patch to the fork. The clone url is git://github.com/teem/factory_girl.git. You can check it out here. I've included specs as well, which has the prime number sequence I mentioned earlier.

Hope this helps you in making the world a better place.

P.S. I recently found Rick's model_stubbing. I haven't looked at it so atm, I have no idea how it works, but the features look promising.