【Rails】ロールバック(rollback)で何が起こっているか?schema_migrationsとは?意味と役割。UPとDOWNとは?それぞれの使い方

rails-prograshi(プロぐらし)-kv Rails
記事内に広告が含まれていることがあります。

Railsで一度マイグレーションを実施してテーブルを作成した後に、内容を修正したい時、ロールバックという便利な機能が存在します。

ですが、このロールバックがどの情報をもとに、何を実行しているのかを理解していないと、マイグレーションファイルを消す順番を間違えて、ロールバックや再マイグレーションができないというエラーが発生するなど、大きな不具合に至ることがあります。

ここでは、ロールバックの処理が具体的に何をしているのかを解説しています。

この記事を読むことで、

  • ロールバックが何をしているか?
  • どのタイミングでマイグレーションファイルを消せばいいか(消してはいけないか)
  • UPとDOWNの意味
  • schema_migrationsテーブルの役割と使い方

がわかります。


ロールバック(rollback)関連のコマンド一覧

参考にロールバック関連のコマンド一覧をまとめておきます。

コマンド内容実例
db:rollback1つ前の状態に戻すrails db:rollback
db:rollback STEP=nn個前の状態に戻すrails db:rollback STEP=3
db:migrateマイグレーション(DOWNをUPにする)rails db:migrate
db:migrate:statusマイグレーションの状態一覧を表示rails db:migrate:status
db:migrate VERSION=xxx指定したバージョンまでマイグレーションを実行(指定したバージョンを含む)rails db:migrate VERSION=20080906120000
db:migrate:up VERSION=xxx指定したバージョンのみマイグレーションを実行(upとchangeメソッドの実行)rails db:migrate:up VERSION=20080906120000
db:migrate:down VERSION=xxx 指定したバージョンのみロールバックを実行(downとchangeメソッドの逆を実行) rails db:migrate:down VERSION=20080906120000
db:migrate:redoロールバックし、再度マイグレーションを行う
(rails db:rollback db:migrate)
rails db:migrate:redo
db:migrate:redo STEP=n指定したバージョンまでロールバックし、再度マイグレーションを行う
(rails db:rollback STEP=2 db:migrate)
rails db:migrate:redo STEP=2
db:dropDBを削除rails db:drop
db:resetDBのリセット。スキーマを作り直しダミーデータを投入する。
(rails db:drop db:setup)
(rails db:drop db:create db:schema:load db:seed)
rails db:reset
db:migrate:resetデータベースの初期化(中身をすべて削除)rails db:migrate:reset

(参考)Rails公式 Active Record マイグレーション


複数のDB利用時

DBが複数ある場合は、ロールバック対象のDBを指定する必要があります。

db:rollback、db:create以外のコマンドはDBを指定することも、しないこともできます。指定しない場合は全てのDBに対して処理を実施します。

コマンド内容実例
db:rollback:DB名対象のDBを1つ前の状態に戻すrails db:rollback:primary
db:rollback:DB名 STEP=n対象のDBをn個前の状態に戻すrails db:rollback:primary STEP=3
db:create:DB名指定したDBを作成(現在の環境に)rails db:create:clients
db:migrate:DB名指定したDBをマイグレーション(DOWNをUPにする)rails db:migrate:primary
db:migrate:DB名:status指定したDBのマイグレーションの状態一覧を表示db:migrate:primary:status
primaryとは?

primaryとは、メインとして登録しているDB名です。それ以外のDBは名前を指定する必要があります。

DB名の命名規則

DB名の命名規則は、スネークケースです。

  • すべて小文字
  • 単語はアンダースコアでつなぐ
判定DB名の例補足
NGClient大文字はNG。(モデル名はこれ)
NGclient単数形はNG。
NGClientNamesアッパーキャメルでつなぐのはNG。 (モデル名はこれ)
OKclients小文字のみ。複数形
OKclient_names小文字のみ。複数形。アンダースコアでつなぐ

なお、このルールはテーブル名にも適用されます。

(参考)Rails公式 Active Recordで複数のデータベース利用


ロールバックとは?

ロールバックとは、テーブルを昔の状態にもどす処理のことです。

マイグレーション(rails db:migrate) を実行するとマイグレーションファイルに沿って、テーブルを作成したり、カラムを追加・変更・削除したりといった、DBやテーブルの操作を行います。

ロールバックを実行すると、DBやテーブルに行った処理を戻してなかったことにできます。マイグレーションファイルは残るので、再度マイグレーションを実行することもできます。


ロールバックの処理の流れ

ロールバックのコマンドの一つである、rails db:rollback を実行すると、直近のマイグレーション済みの処理を、一つ戻すことができます。

処理の流れは次のようになっています。

  1. 直近のマイグレーションファイルの逆の処理を行う
    • changeメソッドの場合はその逆
    • downメソッドを実行
  2. schema_migrationsテーブルから対象のバージョンを削除
  3. マイグレーションのステータスをdownにする


直近のマイグレーションファイルの逆の処理を行う

テーブルに対して処理を行うには rails db:migrateを実行します。このとき、マイグレーション未実施のマイグレーションファイルの内容が実行され、DBに反映されます。

ロールバックでは、この逆の処理を行います。

rails g migrationでマイグレーションファイルを作成すると、changeメソッドを記述したファイルが生成されます。

class <クラス名> < ActiveRecord::Migration[6.1]
  def change
  end
end

changeメソッドとは?

changeメソッドとは、migrationの時にはそのメソッドを実行し、ロールバックの時には、その処理と逆の処理をRailsが自動で判定して行うものです。

ですが、ロールバック時の自動逆戻しが使えるのは、一部の限られたマイグレーション定義かつ、指定された条件に一致する場合のみです。

基本的には、add_〇〇に対して、逆となるremove_〇〇 が用意されている定義など、機械的に逆処理が行えるもののみ該当します。

詳細は【Rails】マイグレーションファイルでchangeメソッドが使える定義一覧表 をご参考ください。

注意点

changeメソッドに対応しているマイグレーション定義(change_columnなど)を指定した場合はエラーが発生します。


changeメソッドに対応していないマイグレーション定義を使う場合は主に以下の方法があります。

  1. upメソッドとdownメソッドを使う
  2. reversibleメソッドを使う
  3. ロールバックを許可しない


upメソッドとdownメソッドとは?

upメソッドとdownメソッドは、マイグレーション時の処理とロールバック時の処理を明示したものです。

changeに対応していないマイグレーション定義は手動でこの形に書き変える必要があります。(他にもreversibleメソッドを使う方法があります)

class <クラス名> < ActiveRecord::Migration[6.1]
  def up
    <マイグレーション実行時の処理>
  end

  def down
    <ロールバック実行時の処理>
  end
end
注意点

changeをupに変更して、downを記載しなくてもエラーが発生することなくロールバックをすることができます。

ですが、これは戻す処理を何もしていないので、ロールバック前後でテーブルの構造が変化しません。

意図的にdownの処理を書かないことはありますが、テーブルのスキーマを元の状態に戻すためにロールバックを行うのであれば、downメソッドも記述する必要があります。


schema_migrationsテーブルから対象のバージョンを削除

ロールバックを実行すると、schema_migrationsテーブルから対象のバージョンが削除されます。

これを理解するために、Railsがマイグレーションの状況をどのように管理しているかを解説します。

マイグレーションのバージョン(version)

rails g modelでモデルとマイグレーションファイルを生成すると、ファイル名に日付や時刻が入ります。これらは全て固有な値になります。

Railsは、この日付や時刻をバージョンとしてマイグレーションの状況を管理しています。

マイグレーションファイルの例

例えば、Userモデル(usersテーブル)を作成すると、次のようになります。

# rails g migration User name:string
Running via Spring preloader in process 835
      invoke  active_record
      create    db/migrate/20210725031548_user.rb
root@a3d49a8a5adf:/rails-vue# rails g model User name:string
Running via Spring preloader in process 845
      invoke  active_record
      create    db/migrate/20210725032240_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

20210725032240_create_users.rb というマイグレーションファイルが生成されます。この 20210725032240 がこのマイグレーションのバージョンになります。

マイグレーションファイルの中身は次のようになっています。

class CreateUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end

changeメソッドに自動で処理が記述されています。このように自動でchangeメソッドの処理が記述されている場合はロールバック可能です。

この時点では、DBにusersテーブルは追加されていません。

rails db:migrateを実行すると、このマイグレーションファイルが処理され、DBにusersテーブルが追加されます。


マイグレーションの状況確認

どのマイグレーションファイルが実行されていて、未実施のマイグレーションファイルがどれかを確認するには、db:migrateの後ろに :status をつけて実行します。

rails db:migrate:status

すると、マイグレーションファイルの一覧が表示され、左端のstatusで状況を確認できます。

statusはupかdownで表示されます。

status内容
upマイグレーション済み(DBに反映済み)
downマイグレーション前(DBに反映していない)

実例

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
   up     20210724083954  Change clients age to integer
  down    20210725032240  Create users

上記の例だと、20210725032240 Create usersのみがdownなので、このマイグレーションファイルの内容がDBに反映されていないことがわかります。

この状態で、rails db:migrateを実行すると、downとなっているマイグレーションが実行され、statusがupに変わります。

このupやdownの管理は、 schema_migrationsテーブル で行われています。


schema_migrationsテーブルとは?

schema_migrationsテーブルとは、マイグレーションの状況を管理するためのテーブルです。

マイグレーションを実施して、内容がDBに反映されると、 schema_migrationsテーブルにそのバージョンが追加されます。

schema_migrationsテーブルの例

Railsは、schema_migrationsテーブルと、マイグレーションファイルの状況を突き合わせて、対象のバージョンがどちらにも存在する場合はupを返します。schema_migrationsテーブルに存在しない場合はdownを返します。

ロールバックと schema_migrationsテーブルの関係

ロールバックを実行すると、schema_migrationsテーブルから対象のバージョンが削除されます。

試しに、 schema_migrationsテーブルが次のようになっている状態で rails db:rollback を実行します。(No.12まであります)

rails db:rollback

# rails db:rollback
== 20210724083954 ChangeClientsAgeToInteger: reverting ========================
-- change_column(:clients, :age, :string)
   -> 0.0634s
== 20210724083954 ChangeClientsAgeToInteger: reverted (0.0636s) ===============

↓ schema_migrationsテーブルを更新

先ほどは、No12まであったものが、最新のバージョンが1つ削除され、No.11までになっています。


マイグレーションのステータスがdownになる

DBのschema_migrationsテーブルからバージョンが削除され、Railsのマイグレーションファイルと差が発生すると、マイグレーションのステータスがdownに変わります。

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
   up     20210724083954  Change clients age to integer

↓ rails db:rollback 実行後

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
  down    20210724083954  Change clients age to integer

schema_migrationsテーブルから削除されたバージョン「20210724083954」に該当するマイグレーションのStatusがdownになっています。

この状態でマイグレーションファイルを手動で削除すれば、このマイグレーションは無かったものにできます。

↓ db\migrate\20210725032240_create_users.rb を削除

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients

なお、マイグレーションファイルを削除せずに、再度 rails db:migrate を実行すれば、Statusがupに変わり、 schema_migrationsテーブルにそのバージョンが再び追加されます。


マイグレーションファイルを削除する方法

上記の流れでも少々触れましたが、実際にマイグレーションファイルを削除するときの注意点と、削除方法について実例で解説します。

マイグレーションを実行したあとに、間違いに気づいたり、修正したいと感じることがあると思います。

そのとき、基本的に、マイグレーションファイルを削除するのはNGです。マイグレーションファイルのみを削除すると、本来実行したはずの処理が見つからずロールバックでエラーが発生します。

マイグレーションファイルを削除したい場合は次のステップを踏む必要があります。

  1. 対象のファイルをマイグレーションしていない状態に戻す(DOWN)
  2. マイグレーションファイルの削除
  3. マイグレーションの実行(必要に応じて)


実例1|直近2つのマイグレーションを削除する

次のように直近2つのマイグレーションファイルを削除したい場合の例です。

  • 最新のマイグレーション:20210724083954 Change clients age to integer2
  • 最後から2番目: 20210724083954 Change clients age to integer
# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
   up     20210724083954  Change clients age to integer
   up     20210724085421  Change clients age to integer2

どちらもStatusがUPになっているので、マイグレーション済みです。この状態でファイルを削除してはいけません。

マイグレーションのバージョンを管理する、schema_migrationsテーブルを確認するとどちらも認識されています。(No.13まであります)

schema_migrationsテーブル


ロールバックする

まずはロールバックして、対象の2つのファイルをDOWNにします。

rails db:rollback STEP=2

# rails db:rollback STEP=2
== 20210724085421 ChangeClientsAgeToInteger2: reverting =======================
-- change_column(:clients, :age, :string)
   -> 0.0447s
== 20210724085421 ChangeClientsAgeToInteger2: reverted (0.0449s) ==============

== 20210724083954 ChangeClientsAgeToInteger: reverting ========================
-- change_column(:clients, :age, :string)
   -> 0.0036s
== 20210724083954 ChangeClientsAgeToInteger: reverted (0.0037s) ===============

ロールバック(reverting 引き戻しの意味)が2回実行されています。

この状態で、マイグレーションの状態を確認すると、削除したい最新の2つのStatusがdownになっていることがわかります。

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
  down    20210724083954  Change clients age to integer
  down    20210724085421  Change clients age to integer2

マイグレーションのバージョンを管理する、schema_migrationsテーブルを確認すると最新の2つのバージョンが削除され、No.11までになっています。

DBはマイグレーションのバージョンをNo.11の20210724082720までしか認識していない状態になりました。

これで、ようやく当初の2つのマイグレーションファイルを削除することができます。

ファイルを手動で削除して、マイグレーションの状態を確認すると先ほどのdownになっていた2つが削除できます。

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients


実例2|指定したバージョンのみロールバックする

次のように既にマイグレーションが複数ある場合に、20210724075541 Drop test camel というcamelテーブルを削除した処理のみロールバックします。

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
   up     20210724083954  Change clients age to integer

どちらもStatusがUPになっているので、マイグレーション済みです。この状態でファイルを削除してはいけません。

マイグレーションのバージョンを管理する、schema_migrationsテーブルを確認するとどちらも認識されています。(No.11まであります。削除したいのはNo.9です)


ロールバックする

対象のバージョンを指定してロールバックします。

rails db:migrate:down VERSION=20210724075541

# rails db:migrate:down VERSION=20210724075541
== 20210724075541 DropTestCamel: reverting ====================================
-- create_table(:test_camels)
   -> 0.2541s
== 20210724075541 DropTestCamel: reverted (0.2880s) ===========================

指定したバージョンでのみロールバックが実行されています。

この状態で、マイグレーションの状態を確認すると、対象のバージョンのみがdownになっていることがわかります。

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
  down    20210724075541  Drop test camel
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
   up     20210724083954  Change clients age to integer

マイグレーションのバージョンを管理する、schema_migrationsテーブルを確認すると対象のバージョンが削除されています。

DBはマイグレーションのバージョン 20210724075541を認識していない状態になりました。

これで、ようやくマイグレーションファイルを削除することができます。

ファイルを手動で削除して、マイグレーションの状態を確認すると先ほどのdownになっていたバージョンが無くなります。

# rails db:migrate:status

database: rails_vue_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20210724031048  Create clients
   up     20210724031617  Add name to client
   up     20210724031825  Add name to client2
   up     20210724033923  Add age to client
   up     20210724070410  Create students
   up     20210724070509  Create test camels
   up     20210724072258  Add project to clients
   up     20210724072900  Remove project from clients
   up     20210724080344  Rename students to people
   up     20210724082720  Rename name to client name in clients
   up     20210724083954  Change clients age to integer

以上で処理は完了です。


schema_migrationsテーブルとマイグレーションファイルがズレる場合

ロールバックとマイグレーションの実行状況によっては、schema_migrationsテーブルとマイグレーションファイルを比較したときに並び順にズレが発生することがあります。

例えば、下記の例だと、 schema_migrationsテーブル の最新のバージョンは 「202107244075541」です。

ですが、マイグレーションファイルだと、最新は「20210724083954」で、 「202107244075541」 は4つ前にあります。

これは、rails db:migrate:down VERSION=20210724075541で、 「202107244075541」 のみdownの状態にした後に、再度、rails db:migrateを実行したため、

直近で実行したマイグレーションのバージョンが 「202107244075541」 となっている状態です。

タイトルとURLをコピーしました