ruk·si

🛤️ Ruby on Rails
Migrations

Updated at 2015-08-19 01:40

Migrations alter running database schema and schema definition at db/schema.rb.

Keep the schema.rb under version control. Staring a new server uses that to build the database, it doesn't run migrations one by one.

Use rake db:schema:load to initialize an empty database. Not rake db:migrate.

Use generate helpers to create the migrations.

rails generate migration <MIGRATION_NAME>

rails g migration CreateProducts name:text text:text
rails g migration AddPartCodeToProducts part_code:text
rails g migration AddPartCodeToProducts part_code:text:index
rails g migration RemovePartCodeFromProducts part_code:text
rails g migration AddDetailsToProducts part_code:text:index price:decimal

# creating model references
rails g migration AddUserRefToProducts user:references
rails g migration CreateJoinTableCustomerProduct customer product
class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.text :name
      t.text :description

      t.timestamps null: false
    end
  end
end

class AddDetailsToProducts < ActiveRecord::Migration
  def change
    add_column :products, :part_number, :text
    add_column :products, :price, :decimal
    add_index :products, :part_number
  end
end

class AddUserRefToProducts < ActiveRecord::Migration
  def change
    add_reference :products, :user, index: true
  end
end

class CreateJoinTableCustomerProduct < ActiveRecord::Migration
  def change
    create_join_table :customers, :products do |t|
      # Table name becomes table names of the joined classes
      # in alphabetical order and separated with an underscore,
      # customers_projects in this case.

      # if you want indexes...
      # t.index [:customer_id, :product_id]
      # t.index [:product_id, :customer_id]
    end
  end
end

Generating models also creates a migration. More about models in model notes.

rails g model <NAME> <ATTRIBUTE>:<TYPE> <ATTRIBUTE>:<TYPE>
rails g model Product name:text description:text
class CreateProducts < ActiveRecord::Migration
  def change
    create_table :products do |t|
      t.text :name
      t.text :description

      t.timestamps null: false
    end
  end
end

Use type modifiers to get more control for the attribute type. There are numerous type modifiers you can use to customize the types.

rails g migration AddPriceToProducts 'price:decimal{5,2}'
class AddDetailsToProducts < ActiveRecord::Migration
  def change
    add_column :products, :price, :decimal, precision: 5, scale: 2
  end
end

When modifying columns, use the change method. Not up and down methods. Allows reverting changes later.

# bad
class AddNameToPeople < ActiveRecord::Migration
  def up
    add_column :people, :name, :string
  end

  def down
    remove_column :people, :name
  end
end

# good
class AddNameToPeople < ActiveRecord::Migration
  def change
    add_column :people, :name, :string
  end
end
class MigrationName < ActiveRecord::Migration
  def change
    add_column
    add_index
    add_reference
    add_timestamps
    add_foreign_key
    create_table
    create_join_table
    drop_table (must supply a block)
    drop_join_table (must supply a block)
    remove_timestamps
    rename_column
    rename_index
    remove_reference
    rename_table
  end
end

Making columns unique requires generating an index. It's just how Rails rolls.

add_index :table_name, :column_name, :unique => true

# unique pairing
add_index :table_name, [:column_name_a, :column_name_b], :unique => true

Don't use model classes in migrations. The model classes are constantly evolving and at some point in the future migrations that used to work might stop, because of changes in the models used.

Use migrations to rollback changes already in production.

require_relative '2012121212_example_migration'

class FixupExampleMigration < ActiveRecord::Migration
  def change
    revert ExampleMigration # or include instructions to revert
  end
end

Enforce default values in the migrations themselves. Not in the application layer. Allows easier integration with other apps.

# bad - application enforced default value
def amount
  self[:amount] or 0
end

Use seeds for development environment data.

# db/seeds.rb
5.times do |i|
  Product.create(name: "Product ##{i}", description: "A product.")
end

Migrations are for applying changes after initialization. For initialization, use precompiled data in db/schema.rb.

# it's usually best to use `bundle exec rake *`

# running migrations write to db/schema.rb and to the related database
rake db:migrate                         # migrates to the latest version
rake db:migrate VERSION=20080906120000  # migrates to the given version
rake db:rollback                        # rollbacks to previous version

# you should keep db/schema.rb in version control and initialize using that
rake db:create      # creates the database using environmental configurations
rake db:schema:load # applies schema from db/schema.rb
rake db:seed        # inserts seed data to the database

rake db:migrate:redo  # revert latest and migrate it again
rake db:setup         # does db:create, db:schema:load, db:seed
rake db:drop          # deletes the database
rake db:reset         # does db:drop, db:setup

Making a temporary table might help with big migrations. Especially when moving data from a table to another. The table is removed after connection closes.

create_table(
  :long_query,
  temporary: true,
  as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id"
)

Sources