Active Record マイグレーション

マイグレーション(migration)はActive Recordの機能の1つであり、データベーススキーマが長期にわたって進化を安定して繰り返せるようにするための仕組みです。マイグレーション機能のおかげで、スキーマ変更を生SQLで記述せずに、Rubyで作成されたマイグレーション用のDSL(ドメイン固有言語)を用いてテーブルの変更を簡単に記述できます。

このガイドの内容:

  • マイグレーション作成で利用できるジェネレータ
  • Active Recordが提供するデータベース操作用メソッド群の解説
  • マイグレーション実行とスキーマ更新用のrailsタスクの解説
  • マイグレーションとスキーマファイルschema.rbの関係

1 マイグレーションの概要

マイグレーションは、データベーススキーマの継続的な変更(英語)を、統一的かつ簡単に行なうための便利な手法です。マイグレーションではRubyのDSLが使われているので、生のSQLを作成する必要がなく、スキーマおよびスキーマ変更がデータベースに依存しなくなります。

個別のマイグレーションは、データベースの新しい「バージョン」とみなせます。スキーマは空の状態から始まり、マイグレーションによる変更が加わるたびにテーブル、カラム、エントリが追加または削除されます。Active Recordはマイグレーションの時系列に沿ってスキーマを更新する方法を知っているので、履歴のどの時点からでも最新バージョンのスキーマに更新できます。Active Recordはdb/schema.rbファイルを更新し、データベースの最新の構造と一致するようにします。

マイグレーションの例を以下に示します。

class CreateProducts < ActiveRecord::Migration[7.0]
  def change
    create_table :products do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end
end

上のマイグレーションを実行するとproductsという名前のテーブルが追加されます。この中にはnameというstringカラムと、descriptionというtextカラムが含まれています。主キーはidという名前で暗黙に追加されます。idはActive Recordモデルにおけるデフォルトの主キーです。timestampsマクロは、created_atupdated_atという2つのカラムを追加します。これらの特殊なカラムが存在する場合、Active Recordによって自動的に管理されます。

マイグレーションで定義されているのは、時間を先に進めるときに実行したい動作である点にご注目ください。マイグレーションの実行前にはテーブルは1つもありません。マイグレーションを実行すると、テーブルが作成されます。Active Recordは、このマイグレーションを逆進させる方法も知っています。マイグレーションをロールバックさせると、テーブルは削除されます。

トランザクション内でスキーマを変更するステートメントがデータベースでサポートされていれば、マイグレーションはトランザクションでラップされます。この機能がデータベースでサポートされていない場合は、マイグレーションの一部が失敗した場合にロールバックされません。その場合は、変更の逆進を手動で記述する必要があります。

ある種のクエリは、トランザクション内で実行できないことがあります。アダプタがDDLトランザクションをサポートしている場合は、disable_ddl_transaction!を使えば単一のマイグレーションでこれらを無効にできます。

マイグレーションを取り消す(逆進させる)方法をActive Recordが推測できない場合は、reversibleメソッドを利用できます。

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

changeの代わりにupdownを使うこともできます。

class ChangeProductsPrice < ActiveRecord::Migration[7.0]
  def up
    change_table :products do |t|
      t.change :price, :string
    end
  end

  def down
    change_table :products do |t|
      t.change :price, :integer
    end
  end
end

2 マイグレーションを作成する

2.1 単独のマイグレーションを作成する

マイグレーションはdb/migrateディレクトリに保存されます。1つのマイグレーションファイルが1つのマイグレーションクラスに対応します。マイグレーションファイル名はYYYYMMDDHHMMSS_create_products.rbのような形式になります。ファイル名の日時はマイグレーションを識別するUTCタイムスタンプであり、アンダースコアに続いてマイグレーション名が記述されます。マイグレーションのクラス名(CamelCase)は、ファイル名の後半(snake_case)と一致する必要があります。たとえば、20080906120000_create_products.rbではCreateProductsというクラスを定義し、20080906120001_add_details_to_products.rbではAddDetailsToProductsというクラスを定義する必要があります。Railsはマイグレーションの実行順序をファイル名のタイムスタンプで決定するので、マイグレーションを他のアプリケーションからコピーする場合や、自分でマイグレーションを生成する場合は、実行順に注意する必要があります。

タイムスタンプを算出する作業は退屈です。Active Recordにはタイムスタンプを自動生成するジェネレータが用意されています。

$ bin/rails generate migration AddPartNumberToProducts

上を実行すると、空のマイグレーションが適切な名前で作成されます。

class AddPartNumberToProducts < ActiveRecord::Migration[7.0]
  def change
  end
end

ジェネレータは、単にファイル名にタイムスタンプを追加するだけではありません。命名規約や追加の(オプション)引数に基づいて、マイグレーションに肉付けすることもできます。

マイグレーション名が"AddColumnToTable"や"RemoveColumnFromTable"で、かつその後ろにカラム名や型が続く形式になっていれば、適切なadd_column文やremove_column文を含むマイグレーションが作成されます。

$ bin/rails generate migration AddPartNumberToProducts part_number:string

上を実行すると以下のマイグレーションファイルが生成されます。

class AddPartNumberToProducts < ActiveRecord::Migration[7.0]
  def change
    add_column :products, :part_number, :string
  end
end

新しいカラムにインデックスも追加したい場合は以下のようにします。

$ bin/rails generate migration AddPartNumberToProducts part_number:string:index

上を実行すると以下のマイグレーションファイルが生成されます。

class AddPartNumberToProducts < ActiveRecord::Migration[7.0]
  def change
    add_column :products, :part_number, :string
    add_index :products, :part_number
  end
end

同様に、カラムを削除するマイグレーションをコマンドラインで生成するには以下のようにします。

$ bin/rails generate migration RemovePartNumberFromProducts part_number:string

上を実行すると以下のマイグレーションファイルが生成されます。

class RemovePartNumberFromProducts < ActiveRecord::Migration[7.0]
  def change
    remove_column :products, :part_number, :string
  end
end

自動で生成できるカラムは1種類だけではありません。たとえば以下のような指定も可能です。

$ bin/rails generate migration AddDetailsToProducts part_number:string price:decimal

上を実行すると以下が生成されます。

class AddDetailsToProducts < ActiveRecord::Migration[7.0]
  def change
    add_column :products, :part_number, :string
    add_column :products, :price, :decimal
  end
end

マイグレーション名が"CreateXXX"のような形式で、その後にカラム名と型が続く場合、XXXという名前のテーブルが作成され、指定の型のカラム名がその中に生成されます。たとえば次のようになります。

$ bin/rails generate migration CreateProducts name:string part_number:string

上を実行すると以下のマイグレーションファイルが生成されます。

class CreateProducts < ActiveRecord::Migration[7.0]
  def change
    create_table :products do |t|
      t.string :name
      t.string :part_number

      t.timestamps
    end
  end
end

これまでと同様、ここまでに生成したマイグレーションの内容は単なる出発点でしかありません。db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rbファイルを編集して、項目の追加や削除を行えます。

同様に、カラム型にreferencesも指定できます(belongs_toでも可)。たとえば次のようになります。

$ bin/rails generate migration AddUserRefToProducts user:references

上を実行すると以下のadd_reference呼び出しが生成されます。

class AddUserRefToProducts < ActiveRecord::Migration[7.0]
  def change
    add_reference :products, :user, foreign_key: true
  end
end

このマイグレーションを実行すると、user_idが作成されます。reference(参照)は、「カラムの作成」「インデックスの作成」「外部キーの作成」そして「ポリモーフィック関連付けカラムの作成」のショートハンドです。

名前の一部にJoinTableが含まれているとjoinテーブルを生成するジェネレータもあります。

$ bin/rails generate migration CreateJoinTableCustomerProduct customer product

上によって以下のマイグレーションが生成されます。

class CreateJoinTableCustomerProduct < ActiveRecord::Migration[7.0]
  def change
    create_join_table :customers, :products do |t|
      # t.index [:customer_id, :product_id]
      # t.index [:product_id, :customer_id]
    end
  end
end

2.2 モデルを生成する

モデルのジェネレータとscaffoldジェネレータは、新しいモデルを追加するマイグレーションを生成します。このマイグレーションには、関連するテーブルを作成する命令が既に含まれています。必要なカラムを指定すると、それらのカラムを追加する命令も同時に生成されます。たとえば、以下を実行するとします。

$ bin/rails generate model Product name:string description:text

このとき、以下のようなマイグレーションが作成されます。

class CreateProducts < ActiveRecord::Migration[7.0]
  def change
    create_table :products do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end
end

カラム名と型のペアはいくつでも追加できます。

2.3 修飾子を渡す

よく使われる型修飾子の中には、コマンドラインで直接渡せるものもあります。これらの型修飾子はフィールド型の後ろに波かっこ{}で追加します。

たとえば以下を実行したとします。

$ bin/rails generate migration AddDetailsToProducts 'price:decimal{5,2}' supplier:references{polymorphic}

これによって以下のようなマイグレーションが生成されます。

class AddDetailsToProducts < ActiveRecord::Migration[7.0]
  def change
    add_column :products, :price, :decimal, precision: 5, scale: 2
    add_reference :products, :supplier, polymorphic: true
  end
end

詳しくはジェネレータのヘルプを参照してください。

3 マイグレーションを自作する

ジェネレータでマイグレーションを作成できるようになったら、今度は自分で作成してみましょう。

3.1 テーブルを作成する

create_tableメソッドは最も基本的なメソッドであり、ほとんどの場合モデルやscaffoldの生成時に使われます。典型的な利用法は以下のとおりです。

create_table :products do |t|
  t.string :name
end

上によってproductsテーブルが生成され、nameという名前のカラムがその中に作成されます(idというカラムも暗黙で生成されますが、これについては後述します)。

デフォルトでは、create_tableによってidという名前の主キーが作成されます。:primary_keyオプションを指定すれば主キー名も変更できます。主キーを使いたくない場合はid: falseオプションを指定することも可能です。特定のデータベースに依存するオプションが必要な場合は、以下のように:optionsオプションに続けてSQLフラグメントを記述します。

create_table :products, options: "ENGINE=BLACKHOLE" do |t|
  t.string :name, null: false
end

上のマイグレーションでは、テーブルを生成するSQLステートメントにENGINE=BLACKHOLEを追加しています。

以下のように:indexオプションにtrueやオプションハッシュを渡すと、create_tableブロックで作成されるカラムにインデックスを追加できます。

create_table :users do |t|
  t.string :name, index: true
  t.string :email, index: { unique: true, name: 'unique_emails' }
end

:commentオプションを使うと、テーブルを説明するコメントを書いてデータベース自身に保存することも可能です。保存した説明文はMySQL WorkbenchやPgAdmin IIIなどのデータベース管理ツールで表示できます。説明文を追加しておくことでデータモデルが理解しやすくなり、ドキュメントも生成されるので、大規模なデータベースを持つアプリケーションでは、マイグレーションにこのようなコメントを追加しておくことを強くおすすめします。 現時点では、MySQLとPostgreSQLアダプタのみがコメント機能をサポートしています。

3.2 joinテーブルを作成する

マイグレーションのcreate_join_tableメソッドはhas_and_belongs_to_many(HABTM)というjoinテーブルを作成します。典型的な利用法は以下のとおりです。

create_join_table :products, :categories

上によってcategories_productsテーブルが作成され、その中にcategory_idカラムとproduct_idカラムが生成されます。これらのカラムには:nullオプションがあり、デフォルト値はfalseです。:column_optionsオプションを指定すれば、これらを上書きできます。

create_join_table :products, :categories, column_options: { null: true }

デフォルトでは、create_join_tableに渡された引数の最初の2つをつなげたものがjoinテーブル名になります。 独自のテーブル名を使いたい場合は、:table_nameで指定します。

create_join_table :products, :categories, table_name: :categorization

上のようにすることでcategorizationテーブルが作成されます。

create_join_tableにはブロックも渡せます。ブロックはインデックスの追加(インデックスはデフォルトでは作成されません)やカラムの追加に使われます。

create_join_table :products, :categories do |t|
  t.index :product_id
  t.index :category_id
end

3.3 テーブルを変更する

既存のテーブルを変更するchange_tableは、create_tableとよく似ています。基本的にはcreate_tableと同じ要領で使いますが、ブロックでyieldされるオブジェクトではいくつかのテクニックが利用できます。たとえば次のようになります。

change_table :products do |t|
  t.remove :description, :name
  t.string :part_number
  t.index :part_number
  t.rename :upccode, :upc_code
end

上のマイグレーションではdescriptionnameカラムが削除され、stringカラムであるpart_numberが作成されてインデックスがそこに追加されます。そして最後にupccodeカラムをリネームしています。

3.4 カラム名を変更する

マイグレーションでは、remove_columnadd_columnに加えてchange_columnメソッドも利用できます。

change_column :products, :part_number, :text

上は、productsテーブル上のpart_numberカラムの型を:textフィールドに変更しています。 change_columnコマンドは逆進できない(可逆的でない)点にご注意ください。

change_columnの他に、not null制約を変更するchange_column_nullメソッドや、デフォルト値を指定するchange_column_defaultメソッドも利用できます。

change_column_null :products, :name, false
change_column_default :products, :approved, from: true, to: false

上のマイグレーションはproductsテーブルの:nameフィールドにNOT NULL制約を設定し、:approvedフィールドのデフォルト値をtrueからfalseに変更します。

上のchange_column_defaultマイグレーションはchange_column_default :products, :approved, falseと書くことも可能ですが、先ほどの例と異なり、マイグレーションは不可逆的になります。

3.5 カラム修飾子

カラムの作成時や変更時に、カラムの修飾子を適用できます。

  • comment: カラムにコメントを追加します。
  • collation: stringカラムやtextカラムのコレーション(照合順序)を指定します。
  • default: カラムでのデフォルト値の設定を許可します。dateなどの動的な値を使う場合は、デフォルト値は最初(マイグレーションが実行された日付など)しか計算されないことにご注意ください。デフォルト値をNULLにする場合はnilを指定してください。
  • limit: stringフィールドについては最大文字数を、text/binary/integerについては最大バイト数を設定します。
  • null: カラムでNULL値を許可または禁止します。
  • precision: decimal/numeric/datetime/timeフィールドの精度(precision)を定義します。
  • scale: decimal/numericフィールドのスケールを指定します。スケールは小数点以下の桁数で表されます。

アダプタによっては他にも利用できるオプションがあります。詳しくは各アダプタ固有のAPIドキュメントを参照してください。

nulldefaultはコマンドラインで指定できません。

3.6 参照

add_referenceメソッドを使うと、適切な名前のカラムを作成できます。

add_reference :users, :role

このマイグレーションは、usersテーブルにrole_idカラムを作成します。index: falseオプションを明示的に指定しない限り、そのカラムのインデックスも作成します。

add_reference :users, :role, index: false

add_belongs_toメソッドはadd_referenceのエイリアスです。

add_belongs_to :taggings, :taggable, polymorphic: true

polymorphic:オプションは、taggingsテーブルにtaggable_typeカラムおよびtaggable_id`カラムというポリモーフィック関連付け用のカラムを2つ作成します。

foreign_keyオプションを指定すると外部キーを作成できます。

add_reference :users, :role, foreign_key: true

add_referenceオプションについて詳しくはAPIドキュメントを参照してください。

remove_referenceを使って参照を削除できます。

remove_reference :products, :user, foreign_key: true, index: false

3.7 外部キー

参照整合性の保証 に対して外部キー制約を追加することもできます。これは必須ではありません。

add_foreign_key :articles, :authors

上のadd_foreign_key呼び出しは、articlesテーブルに新たな制約を追加します。この制約によって、idカラムがarticles.author_idと一致する行がauthorsテーブル内に存在することが保証されます。

to_table名からfrom_tableカラム名を導出できない場合は、:columnオプションでカラム名を指定できます。参照されている主キーが:id以外の場合は、:primary_keyオプションで主キーを指定してください。

たとえば、articles.reviewerを参照するarticles.reviewerに外部キーを追加するには以下のようにします。

add_foreign_key :articles, :authors, column: :reviewer, primary_key: :email

add_foreign_key APIドキュメントには、nameon_deleteif_not_existsvalidatedeferrableなどのオプションも記載されています。

Active Recordでは単一カラムの外部キーのみがサポートされています。複合外部キーを使う場合はexecutestructure.sqlが必要です。詳しくはスキーマダンプの意義を参照してください。

外部キーの削除も以下のように簡単に行えます。

# 削除するカラム名の決定をActive Recordに任せる場合
remove_foreign_key :accounts, :branches

# カラムを指定して外部キーを削除する場合
remove_foreign_key :accounts, column: :owner_id

3.8 ヘルパーの機能だけでは足りない場合

Active Recordが提供するヘルパーの機能だけでは不十分な場合、executeメソッドで任意のSQLを実行できます。

Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1")

個別のメソッドについて詳しくは、APIドキュメントを確認してください。 特に、ActiveRecord::ConnectionAdapters::SchemaStatementschangeupdownメソッドで利用可能なメソッドを提供)、ActiveRecord::ConnectionAdapters::TableDefinitioncreate_tableで生成されるオブジェクトで利用可能なメソッドを提供)、およびActiveRecord::ConnectionAdapters::Tablechange_tableで生成されるオブジェクトで利用可能なメソッドを提供)を参照してください。

3.9 changeメソッドを使う

changeメソッドは、マイグレーションを自作する場合に最もよく使われます。このメソッドを使えば、Active Recordがマイグレーションを逆進させる(以前のマイグレーションに戻す)方法を自動的に理解してくれるため、多くの場面で利用できます。現時点では、changeでサポートされているマイグレーション定義は以下のものだけです。

ブロックでchangechange_defaultremoveが呼び出されない限り、change_table も逆進可能です。

remove_columnは、第3引数でカラムの型を指定すれば逆進可能になります。この場合、元のカラムオプションも指定しておくこと。そうしないと、マイグレーションの逆進時にカラムを再作成できなくなります。

remove_column :posts, :slug, :string, null: false, default: ''

これ以外のメソッドを使う必要がある場合は、changeメソッドの代わりにreversibleメソッドを利用するか、updownメソッドを明示的に書いてください。

3.10 reversibleを使う

マイグレーションが複雑になると、Active Recordがマイグレーションを逆進できないことがあります。reversibleメソッドを使うと、マイグレーションを通常どおり実行する場合と逆進する場合の動作を以下のように指定できます。

class ExampleMigration < ActiveRecord::Migration[7.0]
  def change
    create_table :distributors do |t|
      t.string :zipcode
    end

    reversible do |dir|
      dir.up do
        # CHECK制約を追加
        execute <<-SQL
          ALTER TABLE distributors
            ADD CONSTRAINT zipchk
              CHECK (char_length(zipcode) = 5) NO INHERIT;
        SQL
      end
      dir.down do
        execute <<-SQL
          ALTER TABLE distributors
            DROP CONSTRAINT zipchk
        SQL
      end
    end

    add_column :users, :home_page_url, :string
    rename_column :users, :email, :email_address
  end
end

reversibleメソッドを使うことで、各命令を正しい順序で実行できます。前述のマイグレーション例を逆転させた場合、downブロックは必ずhome_page_urlカラムが削除された直後、そしてdistributorsテーブルがdropされる直前に実行されます。

自作したマイグレーションが逆進不可能な場合、データの一部が失われる可能性があります。そのような場合は、downブロック内でActiveRecord::IrreversibleMigrationをraiseできます。こうすることで、誰かが後にマイグレーションを逆転させたときに、実行不可能であることを示すエラーが表示されます。

3.11 up/downメソッドを使う

changeの代わりに、従来のupメソッドとdownメソッドも利用できます。 upメソッドにはスキーマに対する変換方法を記述し、downメソッドにはupメソッドによって行われた変換をロールバック(逆転)する方法を記述する必要があります。つまり、upの後にdownを実行した場合、スキーマが元に戻る必要があります。 たとえば、upメソッドでテーブルを作成したら、downメソッドではそのテーブルを削除する必要があります。downメソッド内で行なう変換の順序は、upメソッド内で行なう順序の正確な逆順にするのがよいでしょう。先のreversibleセクションの例は以下と同等になります。

class ExampleMigration < ActiveRecord::Migration [7.0]
  def up
    create_table :distributors do |t|
      t.string :zipcode
    end

    # CHECK制約を追加
    execute <<-SQL
      ALTER TABLE distributors
        ADD CONSTRAINT zipchk
        CHECK (char_length(zipcode) = 5);
    SQL

    add_column :users, :home_page_url, :string
    rename_column :users, :email, :email_address
  end

  def down
    rename_column :users, :email_address, :email
    remove_column :users, :home_page_url

    execute <<-SQL
      ALTER TABLE distributors
        DROP CONSTRAINT zipchk
    SQL

    drop_table :distributors
  end
end

マイグレーションが逆進不可能な場合、downメソッド内でActiveRecord::IrreversibleMigrationエラーを発生させる必要があります。こうすることで、誰かが後にマイグレーションを逆進させたときに、実行不可能であることを示すエラーが表示されます。

3.12 以前のマイグレーションに戻す

revertメソッドを使うと、Active Recordマイグレーションのロールバック機能を利用できます。

require_relative '20121212123456_example_migration'

class FixupExampleMigration < ActiveRecord::Migration[7.0]
  def change
    revert ExampleMigration

    create_table(:apples) do |t|
      t.string :variety
    end
  end
end

revertには、逆進を行う命令を含むブロックも渡せます。これは、以前のマイグレーションの一部のみを逆進させたい場合に便利です。 たとえば、ExampleMigrationがコミット済みになっており、後になって郵便番号を検証するには、CHECK制約よりもActive Recordのバリデーションを使う方がよいことに気付いたとしましょう。

class DontUseConstraintForZipcodeValidationMigration < ActiveRecord::Migration[7.0]
  def change
    revert do
      # ExampleMigrationのコードをコピペ
      reversible do |dir|
        dir.up do
          # CHECK制約を追加
          execute <<-SQL
            ALTER TABLE distributors
              ADD CONSTRAINT zipchk
                CHECK (char_length(zipcode) = 5);
          SQL
        end
        dir.down do
          execute <<-SQL
            ALTER TABLE distributors
              DROP CONSTRAINT zipchk
          SQL
        end
      end

      # 以後のマイグレーションはOK
    end
  end
end

revertを使わなくても同様のマイグレーションは自作できますが、その分余計な手間がかかります(create_tablereversibleの順序を逆にし、create_tabledrop_tableに置き換え、最後にupdownを入れ替える)。 revertはこれらの作業を一手に引き受けてくれます。

4 マイグレーションを実行する

Railsにはマイグレーションを実行するためのrailsコマンドがいくつか用意されています。

マイグレーションを実行するrailsコマンドの筆頭といえば、rails db:migrateでしょう。このタスクは基本的に、まだ実行されていないchangeまたはupメソッドを実行します。未実行のマイグレーションがない場合は何もせずに終了します。マイグレーションの実行順序は、マイグレーションの日付が基準になります。

db:migrateタスクを実行すると、db:schema:dumpコマンドも同時に呼び出されます。このコマンドはdb/schema.rbスキーマファイルを更新し、スキーマがデータベースの構造に一致するようにします。

マイグレーションの特定バージョンを指定すると、Active Recordは指定されたマイグレーションに達するまでマイグレーション(change・up・down)を実行します。マイグレーションのバージョンは、マイグレーションファイル名冒頭の数字で表されます。たとえば、20080906120000というバージョンまでマイグレーションしたい場合は、以下を実行します。

$ bin/rails db:migrate VERSION=20080906120000

20080906120000というバージョンが現在のバージョンより大きい場合(新しい方に進む通常のマイグレーションなど)、20080906120000に到達するまで(このマイグレーション自身も実行対象に含まれます)のすべてのマイグレーションのchange(またはup)メソッドを実行し、その先のマイグレーションは行いません。過去に遡るマイグレーションの場合、20080906120000に到達するまでのすべてのマイグレーションのdownメソッドを実行しますが、上と異なり、20080906120000自身は含まれない点にご注意ください。

4.1 ロールバック

直前に行ったマイグレーションをロールバックする作業はよく発生します(マイグレーションに誤りがあって訂正したい場合など)。この場合、バージョン番号を調べて明示的にロールバックを実行しなくても、以下を実行するだけで済みます。

$ bin/rails db:rollback

これにより、changeメソッドを逆転実行するかdownメソッドを実行する形で直前のマイグレーションにロールバックします。マイグレーションを2つ以上ロールバックしたい場合は、STEPパラメータを指定できます。

$ bin/rails db:rollback STEP=3

これにより、最後に行った3つのマイグレーションがロールバックされます。

db:migrate:redoコマンドは、ロールバックと再マイグレーションを一度に実行できるショートカットです。複数バージョンに対してこれを行いたい場合は、db:rollbackコマンドの場合と同様にSTEPパラメータも指定できます。

$ bin/rails db:migrate:redo STEP=3

ただし、これらのコマンドでできるのは、db:migrateでできることのみです。これらのコマンドは、バージョンを明示的に指定しなくて済むようにdb:migrateタスクを使いやすくした単なるショートカットです。

4.2 データベースを設定する

bin/rails db:setupコマンドは、「データベースの作成」「スキーマの読み込み」「seedデータを用いたデータベースの初期化」をまとめて実行します。

4.3 データベースをリセットする

bin/rails db:resetコマンドは、データベースをdropして再度設定します。このコマンドはrails db:drop db:setupと同等です。

このコマンドは、すべてのマイグレーションを実行することと等価ではありません。このコマンドは単に現在のschema.rbの内容をそのまま使い回します。マイグレーションをロールバックできなくなると、rails db:resetを実行しても復旧できないことがあります。スキーマダンプについて詳しくは、スキーマダンプの意義 セクションを参照してください。

4.4 特定のマイグレーションのみを実行する

特定のマイグレーションをupまたはdown方向に実行する必要がある場合は、db:migrate:upまたはdb:migrate:downタスクを使います。以下に示したように、適切なバージョン番号を指定するだけで、該当するマイグレーションに含まれるchangeupdownメソッドのいずれかが呼び出されます。

$ bin/rails db:migrate:up VERSION=20080906120000

上を実行すると、バージョン番号が20080906120000のマイグレーションに含まれるchangeメソッド(またはupメソッド)が実行されます。このコマンドは、最初にそのマイグレーションが実行済みであるかどうかをチェックし、Active Recordによって実行済みであると認定された場合は何も行いません。

4.5 環境を指定してマイグレーションを実行する

デフォルトでは、rails db:migratedevelopment環境で実行されます。 他の環境に対してマイグレーションを行いたい場合は、コマンド実行時にRAILS_ENV環境変数を指定します。たとえば、test環境でマイグレーションを実行する場合は以下のようにします。

$ bin/rails db:migrate RAILS_ENV=test

4.6 マイグレーション実行結果の出力を変更する

デフォルトでは、マイグレーション実行後に正確な実行内容とそれぞれの所要時間が出力されます。 たとえば、テーブル作成とインデックス追加を行なうと次のような出力が得られます。

==  CreateProducts: migrating =================================================
-- create_table(:products)
   -> 0.0028s
==  CreateProducts: migrated (0.0028s) ========================================

マイグレーションには、これらの出力方法を制御するためのメソッドが提供されています。

メソッド 目的
suppress_messages ブロックを渡すと、そのブロック内で生成される出力をすべて抑制する。
say 第1引数で渡したメッセージをそのまま出力する。第2引数には、出力をインデントするかどうかをboolean値で指定できる。
say_with_time 受け取ったブロックを実行するのに要した時間を示すテキストを出力する。ブロックが整数を1つ返す場合、影響を受けた行数であるとみなす。

以下のマイグレーションを例に説明します。

class CreateProducts < ActiveRecord::Migration[7.0]
  def change
    suppress_messages do
      create_table :products do |t|
        t.string :name
        t.text :description
        t.timestamps
      end
    end

    say "Created a table"

    suppress_messages {add_index :products, :name}
    say "and an index!", true

    say_with_time 'Waiting for a while' do
      sleep 10
      250
    end
  end
end

上によって以下の出力が得られます。

==  CreateProducts: migrating =================================================
-- Created a table
   -> and an index!
-- Waiting for a while
   -> 10.0013s
   -> 250 rows
==  CreateProducts: migrated (10.0054s) =======================================

Active Recordから何も出力したくない場合は、bin/rails db:migrate VERBOSE=falseで出力を完全に抑制できます。

5 既存のマイグレーションを変更する

マイグレーションを自作していると、ときにはミスしてしまうこともあります。いったんマイグレーションを実行してしまった後では、既存のマイグレーションを単に編集してもう一度マイグレーションをやり直しても意味がありません。Railsはマイグレーションが既に実行済みであると認識しているので、rails db:migrateを実行しても何も変更されません。このような場合には、マイグレーションをいったんロールバック(rails db:rollbackなど)してからマイグレーションを修正し、それからbin/rails db:migrateを実行して修正済みバージョンのマイグレーションを実行する必要があります。

一般に、既存のマイグレーションを直接変更するのはよくありません。既存のマイグレーションを変更すると、自分自身はもちろん、共同作業者も余分な作業を強いられます。さらに、既存のマイグレーションがproduction環境で実行中の場合、ひどい頭痛の種になるでしょう。既存のマイグレーションを直接修正するのではなく、修正用のマイグレーションを新たに作成してそれを実行するのが正しい方法です。これまでコミットされてない(より一般的に言えば、これまでdevelopment環境以外にデプロイされたことのない)マイグレーションを新たに生成し、それを編集するのが害の少ない方法です。

revertメソッドは、以前のマイグレーション全体またはその一部を逆進させるためのマイグレーションを新たに書くときにも便利です(上述の以前のマイグレーションに戻すを参照してください)。

6 スキーマダンプの意義

6.1 スキーマファイルの意味について

Railsのマイグレーションは強力ではありますが、データベースのスキーマを作成するための信頼できる情報源ではありません。信頼できる情報源は、やはりデータベースです。Railsは、デフォルトでdb/schema.rbファイルを生成してデータベーススキーマの最新の状態のキャプチャを試みます。

アプリケーションのデータベースの新しいインスタンスを作成する場合、マイグレーションの全履歴を最初から繰り返すよりも、単にrails db:schema:loadでスキーマファイルを読み込む方が、高速かつエラーが起きにくい傾向があります。 マイグレーション内の外部依存性が変更されたり、マイグレーションと異なる進化を遂げたアプリケーションコードに依存していたりすると、古いマイグレーションを正しく適用できなくなる可能性があります。

スキーマファイルは、Active Recordの現在のオブジェクトにある属性を手軽にチェックするときにも便利です。スキーマ情報はモデルのコードにはありません。スキーマ情報は多くのマイグレーションに分かれて存在しており、そのままでは非常に探しにくいものですが、こうした情報はスキーマファイルにコンパクトな形で保存されています。

6.2 スキーマダンプの種類

Railsで生成されるスキーマダンプのフォーマットは、config/application.rbconfig.active_record.schema_format設定で制御されます。デフォルトのフォーマットは:rubyですが、:sqlも指定できます。

:rubyを指定すると、スキーマはdb/schema.rbに保存されます。このファイルを開いてみると、1つの巨大なマイグレーションのように見えます。

ActiveRecord::Schema.define(version: 2008_09_06_171750) do
  create_table "authors", force: true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "products", force: true do |t|
    t.string   "name"
    t.text     "description"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "part_number"
  end
end

このスキーマ情報は、見てのとおりその内容を単刀直入に表しています。このファイルは、データベースを詳細に検査し、create_tableadd_indexなどでその構造を表現することで作成されています。

db/schema.rbでは、「トリガ」「シーケンス」「ストアドプロシージャ」「チェック制約」などのデータベース固有の項目を表現できません。マイグレーションでexecuteを用いれば、RubyマイグレーションDSLでサポートされないデータベース構造も作成できますが、そうしたステートメントはスキーマダンプで再構成されない点にご注意ください。これらの機能が必要な場合は、新しいデータベースインスタンスの作成に有用なスキーマファイルを正確に得るために、スキーマのフォーマットに:sqlを指定する必要があります。

スキーマフォーマットを:sqlにすると、データベース固有のツールを用いてデータベースの構造をdb/structure.sqlにダンプします。たとえばPostgreSQLの場合はpg_dumpユーティリティが使われます。MySQLやMariaDBの場合は、多くのテーブルでSHOW CREATE TABLEの出力結果がファイルに含まれます。

スキーマをdb/structure.sqlから読み込む場合、rails db:structure:loadを実行します。これにより、含まれているSQL文が実行されてファイルが読み込まれます。定義上、これによって作成されるデータベース構造は元の完全なコピーとなります。

6.3 スキーマダンプとソースコード管理

スキーマダンプは一般にデータベースの作成に使われるものなので、スキーマファイルはGitなどのソースコード管理の対象に加えておくことを強く推奨します。

複数のブランチでスキーマを変更すると、マージしたときにスキーマファイルがコンフリクトする可能性があります。 コンフリクトを解決するには、bin/rails db:migrateを実行してスキーマファイルを再生成してください。

7 Active Recordと参照整合性

Active Recordは「高度な機能はデータベースではなくモデルに存在する」というコンセプトを主張しています。そのため、トリガーや制約などは、そうした高度な機能の一部をデータベースに戻すものとみなされており、Active Recordではあまり使われていません。

validates :foreign_key, uniqueness: trueのようなデータベースバリデーション機能は、データ整合性をモデルが強制している例の1つです。モデルに関連付けの:dependentオプションを指定すると、親オブジェクトが削除されたときに子オブジェクトも自動的に削除されます。アプリケーションレベルで実行される他の機能と同様、モデルのこうした機能だけでは参照整合性を維持できないため、開発者によってはデータベースの外部キー制約機能を用いて参照整合性を高めることもあります。

Active Recordだけではこうした外部機能を扱うツールをすべて提供することはできませんが、executeメソッドを使えば任意のSQLを実行できます。

8 マイグレーションとseedデータ

Railsのマイグレーション機能の主要な目的は、スキーマ変更のコマンドを一貫した手順で発行できるようにすることですが、データの追加や変更にも利用できます。これは、productionのデータベースのような削除や再作成を行えない既存データベースで便利です。

class AddInitialProducts < ActiveRecord::Migration[7.0]
  def up
    5.times do |i|
      Product.create(name: "Product ##{i}", description: "A product.")
    end
  end

  def down
    Product.delete_all
  end
end

Railsには、データベース作成後に初期データを素早く簡単に追加するシード(seed)機能があります。シードは、development環境やtest環境で頻繁にデータを再読み込みする場合に特に便利です。 シード機能は、db/seeds.rbにRubyコードを記述してrails db:seedを実行するだけで簡単に利用できます。

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

この方法なら、マイグレーションよりもずっとクリーンに空のアプリケーションのデータベースをセットアップできます。

9 古いマイグレーション

db/schema.rbdb/structure.sqlは、使っているデータベースの最新ステートのスナップショットであり、そのデータベースを再構築するための情報源として信頼できます。これを手がかりにして、古いマイグレーションファイルを削除できます。

db/migrate/ディレクトリ内のマイグレーションファイルを削除しても、マイグレーションファイルが存在していたときにrails db:migrateが実行されたあらゆる環境は、Rails内部のschema_migrationsという名前のデータベース内に保存されている(マイグレーションファイル固有の)マイグレーションタイムスタンプへの参照を保持し続けます。このテーブルは、特定の環境でマイグレーションが実行されたことがあるかどうかをトラッキングするのに用いられます。

マイグレーションファイルを削除した状態でrails db:migrate:statusコマンド(本来マイグレーションのステータス(upまたはdown)を表示する)を実行すると、削除したマイグレーションファイルの後に********** NO FILE **********と表示されるでしょう。これは、そのマイグレーションファイルが特定の環境で一度実行されたが、db/migrate/ディレクトリの下に見当たらない場合に表示されます。

ただし1つ注意点があります。エンジンからマイグレーションをインストールするrakeタスクは「冪等」です(2回以上実行しても結果が変わりません)。以前のインストールによって親アプリケーションに存在するマイグレーションはスキップされ、マイグレーションが見つからない場合は最新のタイムスタンプでコピーされます。古いエンジンのマイグレーションを削除してからインストールタスクを再実行すると、新しいタイムスタンプを持つ新しいファイルが作成され、db:migrateはそれらの新しいファイルを再実行しようとします。

したがって、一般にエンジン由来のマイグレーションは変更されないよう保護しておきましょう。そのようなマイグレーションには以下のようなコメントがあります。

# このマイグレーションはblorghからのもの( 元は20210621082949)

フィードバックについて

Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。

原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨

本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。

Railsガイド運営チーム (@RailsGuidesJP)

支援・協賛

Railsガイドは下記のサポーターから継続的な支援を受けています。支援・協賛にご興味あれば協賛プランから気軽にお問い合わせいただけると嬉しいです。

  1. Star
  2. このエントリーをはてなブックマークに追加