Rails Best Practices 5: Optimize Migration

Migrations, in my opinion, are one of the best things in Rails since these allow the creation and populating the database using ruby code without having to worry about which type of db run below.
That said, even writing the migration is better to follow some best practices.


1. DB Index
The first practice I strongly recommend is to define indices for the external keys and for all those columns on which you will make sort, search and groups.
Let’s take a sample migration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CreateInvoices < ActiveRecord::Migration
  def self.up
    create_table :invoices do |t|
      t.integer :number
      t.integer :year
      t.decimal :total_amount
      t.date :invoice_date
      t.integer :company_id
      t.integer :client_id
      t.timestamps
    end
  end

  def self.down
    drop_table :invoices
  end
end

This is a tipically migration that will be generated by rails after execution of commands like generate Model or generate Scaffold.
Now, let’s add indexes for the foreign keys and the sort fields.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CreateInvoices < ActiveRecord::Migration
  def self.up
    create_table :invoices do |t|
      t.integer :number
      t.integer :year
      t.decimal :total_amount
      t.date :invoice_date
      t.integer :company_id
      t.integer :client_id
      t.timestamps
    end
 
    add_index :invoices, :company_id
    add_index :invoices, :client_id
    add_index :invoices, :number
    add_index :invoices, :year

  end


  def self.down
    drop_table :invoices
  end
end

Indexes definition is important for performance.
A table indexed increase the performance of SQL queries.
My advice therefore is: write indexes!
There are also some plugins that help to identify the fields to index.
By doing a quick search on github you can find different, I will point out just a few:

2. Write data seed
Since version 2.3.4 Rails introduced the concept of seed.
In fact the idea was also present in earlier versions, but starting from release 2.3.4 was created a separate file and also an appropriate command to run the seed (ie the population) of data.
When you start to develop web applications with Ruby on Rails, happen often to write migrations that contains both instructions for creating tables and those for populate the database with default values.
A migration of this type is shown in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CreateCompanies < ActiveRecord::Migration
  def self.up
    create_table :companies do |t|    
      t.string :name
      t.string :street
      t.string :city
      t.string :country
      t.string :email
      t.timestamps
    end
    Company.create(:name => "DevInterface",
                           :street => "Via Postale Vecchia",
                           :city => "Verona",
                           :country => "Italy",
                           :email => "info@devinterface.com",
                           :fax => "045/1234567")
    Company.create(:name => "CDF Tech Solutions",
                           :street => "Via XX Settembre",
                           :city => "Verona",
                           :country => "Italy",
                           :email => "info@cdf.com",
                           :fax => "045/1234567")
  end

  def self.down
    drop_table :companies
  end
end

A migration written in this way will run without error but it is always advisable to keep separate the structure of the db from data. This in order to repopulate the database with test data rather than real data as needed without having to recreate it each time.
We see below how to write the file migration and seed file to separate the data from the schema.
Migration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CreateCompanies < ActiveRecord::Migration
  def self.up
    create_table :companies do |t|    
      t.string :name
      t.string :street
      t.string :city
      t.string :country
      t.string :email
      t.string :fax
      t.timestamps
    end
  end

  def self.down
    drop_table :companies
  end
end

seeds.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
  Company.create(:name => "DevInterface",
                         :street => "Via Postale Vecchia",
                         :city => "Verona",
                         :country => "Italy",
                         :email => "info@devinterface.com",
                         :fax => "045/1234567")

  Company.create(:name => "CDF Tech Solutions",
                         :street => "Via XX Settembre",
                         :city => "Verona",
                         :country => "Italy",
                         :email => "info@cdf.com",
                         :fax => "045/1234567")

Now after you create the database you will simply run the command rake db:seed to populate it.

Use the seed file is also useful to populate the database with large amounts of data generated at random or with specific data to simulate specific conditions.
For this purpose there are many plugins for generating pseudo-random data.
We list the ones I use most:

I close this post about migrations with a sample seed file that fill the database with 5 company pseudo-random generated by using the three plugins listed above:

1
2
3
4
5
6
7
8
9
10
11
12
require 'populator'
require 'faker'
require 'random_data'

[Company].each(& :delete_all)

Company.populate(5) do |comp|
  comp.name= Faker::Lorem.words
  comp.street= Faker::Address.street_address
  comp.city= "Verona"
  comp.fax = '045 / ' + Random.number(20).to_s + Random.number(450).to_s
  comp.email = Faker::Internet.email

Tags: , , ,


About Claudio

Claudio Marai is a co-founder of DevInterface.

After graduating in Computer Science has contributed to develop complex web applications based on Java/J2EE and desktop applications with the. NET framework for the Ministry of Justice and ultimately for the banking ambit.

The passion for web in recent years has led him to be interested in more modern frameworks such as Ruby on Rails and Django, and to a development approach based on agile methodologies such as eXtreme Programming and SCRUM.

About DevInterface

We are an information and communication technology agency. Our mission is to provide web application development, design services and communication strategies. We specialize in building web applications with modern and efficient frameworks.

Related Post

6 Responses to “Rails Best Practices 5: Optimize Migration”

  1. Hmmm,

    Don’t those add_index, and Company.create calls in the migrations have to be in the up methods?

    As written they will happen when the migration class is defined before either the up or down method is invoked:

    1. If the migration is being ‘upped’ then the companies table won’t yet exist,

    2. If the migration is being ‘downed’ then the indexes will be ‘re’ added and or the models will be ‘re’ created.

  2. Claudio says:

    Yes Rick it’s true.
    I’ve made a mistake writing the end before add_index instructions.

    Now i fixed the example, thank you for reporting it.

  3. Lars says:

    In my opinion, creating records in a migration file is a bad practice, and so is creating data for development scenarios in the seed file. I normally use the seed file for data that’s needed for a production setup of the app, i.e. add some standard roles, area codes etc. For development data, I prefer using rake tasks.

    And, as Rick noticed, if you insist of having the create calls in the migration, they should be in the up blocks.

  4. [...] See the original post: Rails Best Practices 5: Optimize Migration | DevInterface Blog [...]

  5. Jonathan O'Connor says:

    I’m a relative newbie in Rails, but I would agree with Lars about creating records in migrations. Recently, I have come across some interesting tips for seed data and domain data.

    “Enterprise Rails” by Dan Chak calls a Domain Class a class that has fixed instances, that rarely if ever change. E.g. Gender has 2 instances, male and female. For a very stable data model, he insists on there being a genders table. The Gender class then looks like this:

    1
    2
    3
    4
    class Gender < ActiveRecord::Base
      MALE = Gender.find_by_name("Male")
      FEMALE = Gender.find_by_name("Female")
    end

    However, I prefer using find_or_create_by methods:

    1
    2
    3
    4
    class Gender < ActiveRecord::Base
      MALE = Gender.find_or_create_by_name("Male", :abbrev => 'M')
      FEMALE = Gender.find_by_name("Female", :abbrev => 'F')
    end

    And I also think that seeds.rb should use this find_or_create technique so db:seed can be called multiple times and it will just insert the records that aren’t there.

  6. The biggest thing that I see overlooked in migrations is specifying of :default, :limit, and :null.

    I know there is a camp that contends that those are already taken care of for you at the ActiveRecord level of things, but I have spent countless time, effort and resources dealing with bugs and cleaning bad data that could have been prevented with a simple :null => false option.

    At a very minimum at least you are thinking and putting into contest how those columns are going to used and validated in your application. That extra 10 seconds of thought pays almost immediately.

Leave a Reply

Insert code beetween <code lang="ruby"> and </code>

Copyright 2012 DevInterface s.n.c.

DevInterface Blog is proudly powered by WordPress