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