🛤️ Ruby on Rails - Migrations
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"
)