give IT a try

プログラミング、リモートワーク、田舎暮らし、音楽、etc.

Rails3.1ではchange_tableメソッドでマイグレーションするとロールバックできない

注意!!

このエントリの内容はRails3.1.3での実行結果を元にしています。
将来的にこの内容が最新のRailsの仕様と異なる可能性も高いので、トラブルシューティングの目的でこのエントリを参照する場合は使用中のRailsバージョンをよく確認してください。


Rails3.1からはマイグレーションでchangeというメソッドが導入されました。
以前はUpメソッドに変更用の処理を、Downメソッドにロールバック用の処理を書く必要があったのですが、changeメソッドを使うとUpに相当する記述だけで済みます。
あとはRailsが自動的にDownに相当する処理を考えてくれます。(つまり、コードがよりDRYになります)

# Ruby 3.0以前
class AddAgeToUsers < ActiveRecord::Migration
  def self.up
    add_column :users, :age, integer
  end

  def self.down
    remove_column :users, :age
  end
end
# Ruby 3.1以降
class AddAgeToUsers < ActiveRecord::Migration
  def change
    # remove_columnに相当する処理はRailsが自動生成してくれる
    add_column :users, :age, integer
  end
end


ただし、いくつもカラムを追加するのであれば、:usersと毎回書くのが面倒です。なので、次のように書けそうです。(というか、その方がよりDRYです)

class AddSomeColumnsToUsers < ActiveRecord::Migration
  def change
    change_table :users do |t|
      t.integer :age
      t.string :nickname
    end
  end
end


実際これでも「rake db:migrate」はすんなり実行できてしまいます。


が!!


何かの理由でDownに相当するような処理を実行しようとするとエラーが発生します。

$ rake db:migrate:redo
==  AddSomeColumnsToUsers: reverting ============================================
rake aborted!
An error has occurred, this and all later migrations canceled:

ActiveRecord::IrreversibleMigration

Tasks: TOP => db:rollback
(See full trace by running task with --trace)


「ActiveRecord::IrreversibleMigration」は「逆の処理(つまりロールバック)を実行できないマイグレーション」であることを示しています。
そうなんです。change_tableメソッドを使うと、ロールバックができなくなってしまうんです。


ググってみると、同じ現象がStack Overflowで質問されていました。
ruby on rails - why IrreversibleMigration occurs? - Stack Overflow


それによると、Railsが自動的にロールバックしてくれるメソッドは以下の9つのメソッドに限定されるそうです。
change_tableメソッドは残念ながらここに含まれていません。

  • add_column
  • add_index
  • add_timestamps
  • create_table
  • create_join_table
  • remove_timestamps
  • rename_column
  • rename_index
  • rename_table


というわけで、changeメソッドを使いたい場合はchange_tableではなく、add_columnメソッドを使ってカラムを追加します(この例の場合)。
DRYさは多少失われますが、Downメソッドに同じカラムを繰り返し書くことに比べればDRYだと思います。

class AddSomeColumnsToUsers < ActiveRecord::Migration
  def change
    add_column :users, :age, :integer
    add_column :users, :nickname, :string
  end
end


きっとそのうちchange_tableメソッドも自動的なロールバックに対応してくれるんじゃないかな〜と思いますが、しばらくはchange_tableメソッドの出番が減りそうですね。