【Rails】テーブル削除(drop_table)の注意点!エラー対処法:ActiveRecord::IrreversibleMigration: To avoid mistakes, drop_table is only reversible if given options or a block (can be empty).

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

RailsでDBのテーブルを削除したいときは、マイグレーションファイルの中でdrop_tableを指定します。

よくありがちな間違いは、changeメソッド(def change)の処理の中でdrop_tableを記述するときの、記述方法の誤りです。

単にdrop_table :テーブル名を記述するのは、基本的にNGです。

これをしてしまうと、ロールバック(rollback)を実行したときにエラーが発生し、ロールバックを適切に行うことができません。

ここでは、テーブルを削除するときに発生するエラーの原因と正しいマイグレーションファイルの書き方について解説しています。

あわせて読みたい

RailsのDBのデータを操作するうえでロールバックは知っておくととても便利です。(というよりも、プロジェクトを推進する上で知らないとまずいです)

ロールバックとは何か?具多的にどんな処理が行われているのか?については下記をご参考ください。

【Rails】ロールバック(rollback)で何が起こっているか?


エラーの原因と内容

ロールバック時のエラーの原因

通常、 ロールバック(rollback) を実行すると、マイグレーションファイルのchangeメソッドと逆の処理を自動で行ってくれます

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

テーブルを削除する、drop_tableはchangeメソッドによる自動逆戻しが可能ですが、ブロックとして記述しなければいけないという制約があります。

このため、ブロックとしてではなく単にテーブルを指定するのみで記載している場合はエラーが発生します。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def change
    drop_table :test_camels
  end
end

このマイグレーションファイルの内容はtest_camelsというテーブルを削除する処理です。


エラーの内容例

エラー例

rails db:rollback

== 20210724075541 DropTestCamel: reverting ====================================
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:

To avoid mistakes, drop_table is only reversible if given options or a block (can be empty).

/rails-vue/db/migrate/20210724075541_drop_test_camel.rb:3:in change' /rails-vue/bin/rails:5:in
/rails-vue/bin/spring:10:in block in <top (required)>' /rails-vue/bin/spring:7:intap’
/rails-vue/bin/spring:7:in `’

Caused by:
ActiveRecord::IrreversibleMigration:

To avoid mistakes, drop_table is only reversible if given options or a block (can be empty).

/rails-vue/db/migrate/20210724075541_drop_test_camel.rb:3:in change' /rails-vue/bin/rails:5:in
/rails-vue/bin/spring:10:in block in <top (required)>' /rails-vue/bin/spring:7:intap’
/rails-vue/bin/spring:7:in `’
Tasks: TOP => db:migrate:down
(See full trace by running task with –trace)


drop_tableの正しい書き方

対処法は4つあります。ブロック形式で書く方法と、chnageメソッドを次のメソッドに変更して、ロールバック時の処理を明記する方法です。

ロールバックを許可しないことを明示する方法もあります。

  1. ブロック形式で記述する
  2. upメソッドとdownメソッドを使う
  3. ロールバックを許可しない
  4. reversibleメソッドを使う

基本的に、ブロック形式で記述しておけばOKです。

ブロック形式を使うという指定は、エラーの中に記述してあります。ブロックの中の処理は空でも問題ありません。

To avoid mistakes, drop_table is only reversible if given options or a block (can be empty).


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

まずは処理を記述するマイグレーションファイルを生成します。

※drop_tableを記述したマイグレーションファイルが既にある場合はこの処理はスキップしてください。既存のマイグレーションファイルの中身を修正します。

rails g migration Drop<テーブル名>

マイグレーションファイルのクラス名は、Drop<テーブル名>とするのが一般的です。

例えば、testsテーブルであれば、DropTestsとします。

生成したマイグレーションファイルの中のchangeメソッドは空なので、手動で処理を追記します。


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

# rails g migration DropTests
Running via Spring preloader in process 85
      invoke  active_record
      create    db/migrate/20210804002221_drop_tests.rb

▼生成されたマイグレーションファイルの内容

class DropTests < ActiveRecord::Migration[6.1]
  def change
  end
end



ブロック形式で記述する

drop_tableをブロック形式で記述する場合の、マイグレーションファイルの書き方は次のとおりです。

class <クラス名> < ActiveRecord::Migration[6.1]
  def change
    drop_table :<テーブル名> do |t|
      t.<型1> :<カラム名1>, <オプション: 値>,,,
      t.<型2> :<カラム名2>, <オプション: 値>,,,
      ,,,
    end
  end
end

対象となるカラムの元の型やオプションを記述しておくことで、再度マイグレーションしたときに、スキーマを復活させることができます。

注意点

カラムや型などのスキーマを記述しても、drop_tableを実行した時点で中のデータは無くなります。

再マイグレーションで復元できるのはスキーマのみです。中のデータは復活しません。

ブロック内の処理を空にした場合もロールバックできます。この場合、再度マイグレーションを実行するとテーブルは生成されますが、中のカラムは無い状態になります。


マイグレーションファイルの修正が完了したら、ロールバックを実行します。

処理が正しく完了したら、対象のマイグレーションファイルを手動で削除します。


テーブルを使う予定がない場合

テーブルを使う予定がない場合は、カラムと型をあえて指定せず、ブロック内の処理を空の状態にする方法もあります。

class <クラス名> < ActiveRecord::Migration[6.1]
  def change
    drop_table :<テーブル名> do
    end
  end
end

ロールバックを実行したときは、指定した名前のテーブルがカラムが無い状態で生成されます。


実例

次のようなエラーが発生するマイグレーションファイルを書き変えます。

内容はtest_camelsというテーブルを削除する処理です。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def change
    drop_table :test_camels
  end
end


 ↓ 修正後

元のテーブルは、string型のnameカラムが、nullを許可しない状態なので、これをブロックに記述します。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def change
    drop_table :test_camels do |t|
      t.string :name, null: false
    end
  end
end

ロールバックの実行

マイグレーションファイルを修正したのでロールバックを実行します。

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

ロールバックが無事完了しました。



upメソッドとdownメソッドを使う

先ほどのブロックで記述した処理をupとdownで分けて明示することができます。

upはマイグレーション(rails db:migrate) 実行時に行う処理です。

downはロールバック(rails db:rollback)実行時に行う処理です。

ロールバック時に実行するdownメソッドのマイグレーション定義は、create_table を使います。

マイグレーションファイルの書き方は次のとおりです。

class <クラス名> < ActiveRecord::Migration[6.1]
  def up
    drop_table :<テーブル名>
  end

  def down
    drop_table :<テーブル名> do |t|
      t.<型1> :<カラム名1>, <オプション: 値>,,,
      t.<型2> :<カラム名2>, <オプション: 値>,,,
      ,,,
    end
  end
end
注意点

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

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

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


マイグレーションファイルを修正した後で、ロールバックを実行します。

処理が正しく完了したら、対象のマイグレーションファイルを手動で削除します。


実例

次のようなエラーが発生するマイグレーションファイルを書き変えます。

内容はtest_camelsというテーブルを追加する処理です。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def change
    drop_table :test_camels
  end
end


 ↓ 修正後

元のテーブルは、string型のnameカラムが、nullを許可しない状態です。downメソッドに記述します。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def up
    drop_table :test_camels
  end

  def down
    create_table :test_camels do |t|
      t.string :name, null: false
    end
  end
end


ロールバックを許可しない

ロールバックを許可しないことを明示する方法もあります。

downメソッドの中で、raise(または fail)の引数にActiveRecord::IrreversibleMigration を指定します。

こうすることで、ロールバックしようとしたときに例外を発生させ、意図しないロールバックでスキーマが壊れることを防ぎます。

class <クラス名> < ActiveRecord::Migration[6.1]
  def up
    drop_table :<テーブル名>
  end

  def down
    raise ActiveRecord::IrreversibleMigration    
  end
end
raiseとfailの違い

例外処理を発生するには、raiseとfailがあります。どちらも処理は同じです。

第2引数に例外クラスを渡し、第2引数に例外発生時のエラーメッセージを記述します。省略可能です。省略した場合は第1引数の例外をエラーメッセージとして返します。

raise ActiveRecord::IrreversibleMigration
fail ActiveRecord::IrreversibleMigration  

実例

次のようなエラーが発生するマイグレーションファイルを書き変えます。

内容はtest_camelsというテーブルを追加する処理です。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def change
    drop_table :test_camels
  end
end


 ↓ 修正後

downメソッドにロールバックを許可しない処理を追加します。

class DropTestCamel < ActiveRecord::Migration[6.1]
  def up
    drop_table :test_camels
  end

  def down
    fail ActiveRecord::IrreversibleMigration, "ロールバックを許可していません"
  end
end

ロールバックを実行

ロールバックを行うと例外を発生させて、処理を中止します。

# rails db:migrate:down VERSION=20210724075541
== 20210724075541 DropTestCamel: reverting ====================================
rails aborted!
StandardError: An error has occurred, this and all later migrations canceled:



ロールバックを許可していません

/rails-vue/db/migrate/20210724075541_drop_test_camel.rb:7:in `down'
/rails-vue/bin/rails:5:in `<top (required)>'
/rails-vue/bin/spring:10:in `block in <top (required)>'
/rails-vue/bin/spring:7:in `tap'
/rails-vue/bin/spring:7:in `<top (required)>'

Caused by:
ActiveRecord::IrreversibleMigration:

ロールバックを許可していません

/rails-vue/db/migrate/20210724075541_drop_test_camel.rb:7:in `down'
/rails-vue/bin/rails:5:in `<top (required)>'
/rails-vue/bin/spring:10:in `block in <top (required)>'
/rails-vue/bin/spring:7:in `tap'
/rails-vue/bin/spring:7:in `<top (required)>'
Tasks: TOP => db:migrate:down
(See full trace by running task with --trace)

指定したエラーメッセージが表示されています。


up&downとreversibleの違いは何か?

up&downメソッドとreversibleメソッドの違いは、書き方の違いだけで処理の記述は同じです。upとdownをひとまとめにしたのがreversibleになります。

例えば、テーブルへのカラム追加やインデックス付与、その逆の削除などをまとめて指定できるchange_tabeをupとdownで記述すると次のようになります。

class ChangeProductsPrice < ActiveRecord::Migration[5.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


これをreversibleで書くと次のようになります。どちらも同じ処理です。

reversibleは dirという変数を作成し、その変数に対してup, downメソッドを実行し、{ 処理 } を記述します。

class ChangeProductsPrice < ActiveRecord::Migration[5.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


reversibleメソッドを使う

reversibleメソッドの書き方は次のようになります。

class <クラス名> < ActiveRecord::Migration[5.0]
  def change
    reversible do |dir|
        dir.up   { マイグレーション時の処理 }
        dir.down { ロールバック時の処理 }
      end
    end
  end
end

reversible複雑な処理を実行するときに使います。changeメソッドと併記してreversibleを記述することもできます。

マイグレーション時は上から順に処理を実行して、ロールバック時は下から順に処理を実行します。reversibleメソッドのところでは、upブロックとdownブロックの一方を実行します。

class ExampleMigration < ActiveRecord::Migration[5.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

マイグレーション時の流れ

  1. change(zipcodeのカラム追加)
  2. reversibleのup(CHECK制約を追加)
  3. add_column(home_page_urlカラムを追加)
  4. rename_column(emailカラムをemail_addressに変更)

ロールバック時の流れ

  1. rename_column(email_addressカラムをemailに変更)
  2. remove_column (home_page_urlカラムを削除)
  3. reversibleのdown(制約を削除)
  4. drop_table(zipchkカラムを削除)

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


changeメソッドが使える定義一覧

ロールバック(rollback)を実行したときに、changeメソッドで自動逆戻し可能なマイグレーション定義は次になります。

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

定義内容実例
add_columnカラムを追加add_column :users, :picture, :binary, limit: 2.megabytes
add_foreign_key指定したテーブルに外部キー制約を追加add_foreign_key :articles, :authors
add_index指定したテーブルにインデックスを追加add_index :users, :name
add_reference既存のテーブルにリファレンスを追加add_reference(:products, :supplier, index: { unique: true })
add_itemstampsタイムスタンプを追加add_timestamps :suppliers, null: true
change_column_defaultカラムの初期値を設定 (:fromと:toの指定は省略できない)change_column_default(:suppliers, :qualification, ‘new’)
chnage_column_nullNULL制約の追加または削除change_column_null(:users, :nickname, false)
create_join_table2つのテーブルを結合して新しいテーブルを作成create_join_table(:assemblies, :parts, options: ‘ENGINE=InnoDB DEFAULT CHARSET=utf8’)
create_tableテーブルを作成create_table :suppliers, options: ‘ENGINE=InnoDB DEFAULT CHARSET=utf8’
drop_join_table結合テーブルを削除drop_join_table(:assemblies, :parts)
drop_table指定したテーブルを削除drop_table :products
remove_column指定したテーブルのカラムを削除(型の指定必須)remove_column :users, :description, :string, limit: 20
remove_foreign_key外部キーの削除(2番目のテーブルを指定しなければならない)remove_foreign_key :accounts, column: :owner_id
remove_index指定したテーブルのインデックスを削除remove_index :accounts, column: :branch_id
remove_reference既存のテーブルのリファレンスを削除remove_reference(:products, :user, index: true)
remove_timestamps既存のテーブルのcreated_atとupdated_atを削除remove_timestamps(:users)
rename_column指定したテーブルのカラム名を変更rename_column :suppliers, :description, :name
rename_index指定したテーブルのインデックスを変更rename_index :people, ‘index_people_on_last_name’, ‘index_users_on_last_name’
rename_table指定したテーブルの名前を変更rename_table :now_table, :new_table

ブロックでchange、change_default、removeが呼び出されない限り、change_tableもロールバック可能です。

remove_columnは、3番目の引数でカラムの型を指定すればロールバック可能になります。元々のカラムオプションも指定しておかないと、ロールバック時にRailsがカラムを再作成できなくなります。

https://railsguides.jp/active_record_migrations.html


また、命名規則に沿って、rails g migration を行ったときに、自動でchangeメソッドが記述されるものは、ロールバック時に逆戻しができます。

(参考)【Rails】テーブルやカラムの追加・名前の変更・削除・データ型を変更する方法を実例で解説



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