Ruby rails: migrations

From wikinotes

Migrations allow you to version your database schema independently of it's data.
Database Migrations are reversible, for example new data can be ported to an old schema if you revert a database migration.

There are two general types of migrations.

  • migrations (add column, etc.)
  • maintenance_tasks (backfills, etc.)


Documentation

official docs https://guides.rubyonrails.org/active_record_migrations.html
change methods
ActiveRecord::ConnectionAdapters::SchemaStatements
https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html

Locations

{project}/db/migrate/{datetime}_{job_snakecase}.rb database migration files
{project}/app/jobs/maintenance/{job_snakecase}_task.rb maintenance-task files
test/maintenance/{job_snakecase}_task_test.rb maintenance-task tests

Commandline

rails db:migrate  \       # run all un-run migrations
  VERSION=20090408054532  # optionally specify exact migration

rails db:rollback    # roll back the last migration

rails generate migration 'YourMigrationName'               # generate a migration
rails generate maintenance_task 'YourMaintenanceTaskName'  # generate maintenance_task

Database

In the database, when your migration completes,
a row is inserted into the table schema_migrations with the version (ex: 20090408054532) to indicate that it has run.
It will not run again.

Example

# {project}/db/20140120191729_create_articles.rb
class CreateArticles < ActiveRecord::Migration[6.0]
  def change
    create_table :articles do |t|  # ':articles' is database table name
      t.belongs_to :author
      t.string :title
      t.text :text
 
      t.timestamps                 # 'timestamps' is macro, creates 'created_at', 'updated_at' columns
    end
  end
end

Change operations

Change operations are automatically reversible (eg. you don't need to define up/down methods for these).
See docs at https://api.rubyonrails.org/?q=Migration for a complete list of supported changes.

class ChangeUserTable < ActiveRecord::Migration[5.0]
  def change
    add_column(:table, :column, :string)  # aditional options optional
    add_index(:table, :column)

    remove_column(:table, :column)
    rename_column(...)
    change_column :age, null: true

    execute("UPDATE `table` SET id = 1 WHERE id = 2;")
  end
end

Reversible (Complex Changes)

Active Record can generally manage simple changes, like column-names or datatype changes.
If you need to do make complex change, you can inform rails how to reverse it.

See which operations require a down method here https://edgeguides.rubyonrails.org/active_record_migrations.html#using-the-change-method .

class ChangeProductsPrice < ActiveRecord::Migration[5.0]
  def change
    reversible do |dir|
      change_table :products do |t|
        dir.up   { t.change :price, :string }   # when applied
        dir.down { t.change :price, :integer }  # when reverted
      end
    end
  end
end

Transactions

NOTE:

If your rails app is using connections to multiple databases,
you should be explicit and use Model.transaction do instead of
the generic ActiveRecord::Base.transaction do

Transactions can be opened from any model.
They are not specific to that model, but the database connection used by that model.
You do not need a separate transaction for each model.
See http://vaidehijoshi.github.io/blog/2015/08/18/safer-sql-using-activerecord-transactions/

ActiveRecord::Base.transaction do
  # your sql
end

# or
YourModel.transaction do
  # your sql
end

You can force a rollback with raise ActiveRecord::Rollback