RailsでDBのテーブルを削除したいときは、マイグレーションファイルの中でdrop_tableを指定します。
よくありがちな間違いは、changeメソッド(def change
)の処理の中でdrop_tableを記述するときの、記述方法の誤りです。
単にdrop_table :テーブル名
を記述するのは、基本的にNGです。
これをしてしまうと、ロールバック(rollback)を実行したときにエラーが発生し、ロールバックを適切に行うことができません。
ここでは、テーブルを削除するときに発生するエラーの原因と正しいマイグレーションファイルの書き方について解説しています。
エラーの原因と内容
ロールバック時のエラーの原因
通常、 ロールバック(rollback) を実行すると、マイグレーションファイルのchangeメソッドと逆の処理を自動で行ってくれます。
ですが、この自動逆戻しが使えるのは、一部の限られたマイグレーション定義かつ、指定された条件に一致する場合のみです。
テーブルを削除する、drop_tableはchangeメソッドによる自動逆戻しが可能ですが、ブロックとして記述しなければいけないという制約があります。
このため、ブロックとしてではなく単にテーブルを指定するのみで記載している場合はエラーが発生します。
class DropTestCamel < ActiveRecord::Migration[6.1]
def change
drop_table :test_camels
end
end
このマイグレーションファイルの内容はtest_camelsというテーブルを削除する処理です。
エラーの内容例
drop_tableの正しい書き方
対処法は4つあります。ブロック形式で書く方法と、chnageメソッドを次のメソッドに変更して、ロールバック時の処理を明記する方法です。
ロールバックを許可しないことを明示する方法もあります。
- ブロック形式で記述する
- upメソッドとdownメソッドを使う
- ロールバックを許可しない
- 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
対象となるカラムの元の型やオプションを記述しておくことで、再度マイグレーションしたときに、スキーマを復活させることができます。
マイグレーションファイルの修正が完了したら、ロールバックを実行します。
処理が正しく完了したら、対象のマイグレーションファイルを手動で削除します。
テーブルを使う予定がない場合
テーブルを使う予定がない場合は、カラムと型をあえて指定せず、ブロック内の処理を空の状態にする方法もあります。
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
マイグレーションファイルを修正した後で、ロールバックを実行します。
処理が正しく完了したら、対象のマイグレーションファイルを手動で削除します。
実例
次のようなエラーが発生するマイグレーションファイルを書き変えます。
内容は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
実例
次のようなエラーが発生するマイグレーションファイルを書き変えます。
内容は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
マイグレーション時の流れ
- change(zipcodeのカラム追加)
- reversibleのup(CHECK制約を追加)
- add_column(home_page_urlカラムを追加)
- rename_column(emailカラムをemail_addressに変更)
ロールバック時の流れ
- rename_column(email_addressカラムをemailに変更)
- remove_column (home_page_urlカラムを削除)
- reversibleのdown(制約を削除)
- 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_null | NULL制約の追加または削除 | change_column_null(:users, :nickname, false) |
create_join_table | 2つのテーブルを結合して新しいテーブルを作成 | 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】テーブルやカラムの追加・名前の変更・削除・データ型を変更する方法を実例で解説