Railsでロールバック(rollback)を実行したときに、マイグレーションファイルの中のchangeメソッドでchange_columnを定義していると、次のようなエラーが発生します。
この原因と対処法について。
原因
通常、 ロールバック(rollback) を実行すると、マイグレーションファイルのchangeメソッドと逆の処理を自動で行ってくれます。
ですが、この自動逆戻しが使えるのは、一部の限られたマイグレーション定義のみです。
カラムの型を変更する、change_columnはchangeメソッドによる自動逆戻しができないため、このエラーが発生しています。
class ChangeClientsAgeToInteger2 < ActiveRecord::Migration[6.1]
def change
change_column :clients, :age, :integer, using: "age::integer"
end
end
このマイグレーションファイルの内容はclietnsテーブルのageカラムの型をintegerに変更する処理です。
対処法
対処法は2つあります。chnageメソッドを次のメソッドに変更して、ロールバック時の処理を明記します。
- upメソッドとdownメソッドを使う
- reversibleメソッドを使う
どちらも、エラーの中に記述してあります。
To make the migration reversible you can either:
- Define #up and #down methods in place of the #change method.
- Use the #reversible method to define reversible behavior.
upメソッドとdownメソッドを使う
upメソッドとdownメソッドを使って修正する場合は、changeをupに変更し、downメソッドにロールバック時に実行する処理を記述します。
マイグレーションファイルの書き方は次のとおりです。
class <クラス名> < ActiveRecord::Migration[6.1]
def up
<マイグレーション実行時の処理>
end
def down
<ロールバック実行時の処理>
end
end
マイグレーションファイルを修正した後で、ロールバックを実行します。
処理が正しく完了したら、対象のマイグレーションファイルを手動で削除します。
実例
次のようなエラーが発生するマイグレーションファイルを書き変えます。
内容はclietnsテーブルのageカラムの型をintegerに変更する処理です。
class ChangeClientsAgeToInteger2 < ActiveRecord::Migration[6.1]
def change
change_column :clients, :age, :integer, using: "age::integer"
end
end
↓ 修正後
ageカラムの元の型はstringなので、カラムの型をstringに戻す処理をdownに記載します。
class ChangeClientsAgeToInteger2 < ActiveRecord::Migration[6.1]
def up
change_column :clients, :age, :integer, using: "age::integer"
end
def down
change_column :clients, :age, :string
end
end
型の種類によってusingを使った指定が必要になります。詳細はこちらをご参考ください。
マイグレーションの実行
マイグレーションファイルの修正が完了したので、rails db:migrate
を実行します。
rails-vue# rails db:rollback
== 20210724085421 ChangeClientsAgeToInteger2: reverting =======================
-- change_column(:clients, :age, :string)
-> 0.0451s
== 20210724085421 ChangeClientsAgeToInteger2: reverted (0.0452s) ==============
エラーの発生なくマイグレーションを行うことができました。
change_column(:clients, :age, :string) とあり、指定したカラムの型もstringに戻っていることがわかります。
この状態になれば、対象のマイグレーションファイルを削除できます。
なお、マイグレーションファイルを削除せず、再度マイグレーションを実行した場合は、ageカラムの型をintegerに戻すことができます。
# rails db:migrate
== 20210724085421 ChangeClientsAgeToInteger2: migrating =======================
-- change_column(:clients, :age, :integer, {:using=>"age::integer"})
-> 0.0404s
== 20210724085421 ChangeClientsAgeToInteger2: migrated (0.0407s) ==============
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】テーブルやカラムの追加・名前の変更・削除・データ型を変更する方法を実例で解説