Yanonoblog!

こつこつと

Railsにおいてmigrationはリバーシブルに設計するべき

背景

Railsの開発で外部キーを追加したあとに削除を行った際に、Rubocopで指摘があり

解決するにあたって色々調べたため内容を書き留めておきたいと思いました。

本記事では、Rubocopの指摘とMigrationに関する説明を簡単に解説していきます。

情報に関しては適当にフェイク入れていますので、ご注意ください

概要

既存のデータベースの外部キーを削除したいケースがあり、以下のmigrationを作成しました。

以下はclubsテーブルから外部キーを削除するmigrationファイルです。

class RemoveUserIdFromClubs < ActiveRecord::Migration[6.1]
  def change
    remove_column :clubs, :user_id
  end
end

change_tableメソッド

change_tableメソッドは、既存のテーブルのスキーマ変更を定義するために使用されます。

既存のテーブルの名前を引数に取り、そのテーブルに対してカラムの追加、変更、削除、インデックスの追加、インデックスの削除などのスキーマ変更を定義するために使用されます。

rubocopの指摘

上記では単純にchange_tableで既存のidカラムを削除したmigrationを作成しただけですが、カラムの削除はrails db:migrate をで問題なく動作していましたがrubocopでエラーが発生しました。

rubocopを実行したときに発生したエラーは以下です。

db/migrate/20230405000000_remove_user_id_from_clubs.rb:5:5: C: Rails/ReversibleMigration: remove_column(without type) is not reversible.
remove_column :clubs, :user_id
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

717 files inspected, 1 offense detected

問題点: 現状のやり方だとロールバックができない

このエラーメッセージは、マイグレーションファイルにある remove_column メソッドが、逆のマイグレーションロールバックできない非可逆的な操作であることを示しています。

つまり、このままでは rails db:rollback コマンドを実行しても、user_idカラムをテーブルに再度追加することができません。

後で削除したカラムを追加することができなくなってしまうため問題があります。

確認

試しに**rails db:rollback**を実行したところ以下のようにエラーが発生しました。

# 実行
$ rails db:rollback

# 結果
== 20230405000000 RemoveUserIdFromClubs: reverting =========================
rails aborted!
StandardError: An error has occurred, all later migrations canceled:

remove_column is only reversible if given a type.

/Users/yano/workspace/vendor/bundle/ruby/3.2.0/gems/activerecord-6.1.7.2/lib/active_record/migration/command_recorder.rb:180:in `invert_remove_column'

解説

以下はclubsテーブルからuser_idを削除することはわかります。

def change
  remove_column :clubs, :user_id
end

ただ、migrationファイルにはrollbackという機能があるため、真逆の処理も考慮してプログラムを書かないと動きません。

remove_columnの逆はadd_columnですが上記のファイルを元にadd_columnをすると必須である型の指定がないことがわかります。

def change
  add_column :clubs, :user_id # 型は?
end

どんな型でカラムを作ればいいかの情報が上記では推測できないのでreversibleではなく、エラーが発生していました。

解決

このエラーを解決するためには、ロールバック可能な方法でカラムを削除する必要があります。

def change
  remove_column :clubs, :user_id, :bigint # 型を指定してあげる
end

このように書くと、rails db:rollback コマンドを実行すると、add_column メソッドで逆の変更を行うロールバック用のマイグレーションが自動的に生成されます。

ロールバックが成功しています。ログを見るとremoveのロールバックではrevertが実行されていることadd_columnメソッドでは型が指定されていることがわかります。

# 実行
rails db:rollback                                                                                               

# 結果
== 20230405000000 RemoveUserIdFromClubs: reverting =========================
-- add_column(:clubs, :user_id, :bigint)
-> 0.3051s
== 20230405000000 RemoveUserIdFromClubs: reverted (0.3052s) ================

rollbackができることがわかったため元に戻しておきます。

# 実行
rails db:migrate

# 結果
== 20230405000000 RemoveUserIdFromClubs: migrating =========================
-- remove_column(:clubs, :user_id, :bigint)
-> 0.0954s
== 20230405000000 RemoveUserIdFromClubs: migrated (0.0955s) ================

おまけ

upメソッドとdownメソッド

このように修正することでも、 upメソッドでカラムを削除することができ、 downメソッドでカラムを再度追加することができます。

class RemoveUserIdFromClubs < ActiveRecord::Migration[6.1]
  def up
    remove_column :clubs, :user_id, :bigint
  end

  def down
    add_column :clubs, :user_id, :bigint
  end
end

rubocop / rails-style-guide - Reversible Migration

上記のrubocopのコードを参照し今回のケースに合わせています。時間ある際にはこのような情報も一読しておくと理解が深まりそうです。

remove_columnとt.removeの違い

remove_columnt.removeは、機能的に同じですが、使用方法が異なります。

change_tableメソッドは、カラムの追加や変更、インデックスの追加や変更など、テーブル全体の変更を実行する場合に便利です。また、change_table メソッドは、複数のカラムの変更をまとめて実行することができるため、コードの見通しを良くすることができます。

def change
  change_table :clubs do |t|
    t.remove :user_id, :bigint
  end
end

remove_columnは、changeメソッドの中で使用され、直接カラムを削除します。

単一のカラムの削除に適していることがわかります。change_tableのブロックでネストしなくて良いためコードが簡潔になり、読みやすくなります。

class RemoveUserIdFromClubs < ActiveRecord::Migration[6.1]
  def change
    remove_column :clubs, :user_id, :bigint
  end
end

おわりに

コメント

本記事の内容は以上になります!

migrationのメソッドについてやrollbackなどの処理の内容やルールについて見直すきっかけになりました。

参考になったり学びのきっかけになりますと幸いです。

間違いがありましたら修正いたしますので、ご指摘ください。

興味があれば他の記事も更新していきますので是非ご覧になってください♪


プログラミングスクールのご紹介 (卒業生より)

お世話になったプログラミングスクールであるRUNTEQです♪

https://runteq.jp/r/ohtFwbjW

ご不明な点ありましたらお気軽にコメントか、TwitterのDMでお答えします♪

https://twitter.com/outputky

参考