Ruby rails: migrations

From wikinotes
Revision as of 12:58, 30 June 2021 by Will (talk | contribs) (→‎Change operations)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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