give IT a try

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

Railsのdb:migrate:down VERSION=xxxはRails上のスキーマバージョンが古いと実行されない

問題と原因

Rails3.1のマイグレーションでちょっとハマったのでメモしておきます。


マイグレーションのdownを実行する場合は

rake db:migrate:down VERSION=20120324025500

のような形で実行できます。


しかし、VERSIONで指定したバージョンがRailsが管理している現在のスキーマのバージョンよりも新しいと何も実行されないようです。
単純な例を示すとこんな感じです。

$ rails c
>ActiveRecord::Migrator.current_version
=>1 (実際は14桁)
>quit
$rake db:migrate:down VERSION=2
(何も起きない)
$


上の例では現在のバージョン(=1)よりもdownで指定したバージョン(=2)の方が新しいので何も実行されません。
VERSIONを指定すれば強制的に実行できると思っていたのですが、実際そうではありませんでした。


まあ普通に使っているとそういうことは起きないんでしょうが、僕の場合マイグレーションの途中でエラーが出たりしたせいか、DBのスキーマとRailsが管理しているスキーマのバージョンがズレてしまいました。


その時の状況を単純化するとこんな感じです。

# db/migrate/2_add_foo.rb (スクリプトのバージョンは2)
class AddFoo < ActiveRecord::Migration
  def change
    # Rails上のバージョンは1だが、同期がズレて実際のDBにはfoo列が存在している
    add_column :users, :foo, :string
  end
end


いったんDB上のテーブルからfoo列を削除して、もう一度マイグレーションしたかったのですが、前述のように「$rake db:migrate:down VERSION=2」では何も起きません。
なのでこの後に「$rake db:migrate」しても「列名が重複しているのでエラー」と怒られるだけでした。

解決策

仕方がないので苦肉の策でRailsをダマすことにしました。

# db/migrate/2_add_foo.rb
class AddFoo < ActiveRecord::Migration
  def change
    # 一時的にコメントアウト
    # add_column :users, :foo, :string
  end
end


このようにDBへの操作を何もしないことにして、「$rake db:migrate」を実行します。
するとRailsが管理しているバージョンだけが2に上がります。foo列は存在したままです。
続いてさっきのコメントを外します。

# db/migrate/2_add_foo.rb
class AddFoo < ActiveRecord::Migration
  def change
    # 元に戻す
    add_column :users, :foo, :string
  end
end


ここで終わっても理屈的には問題ないのですが、念のためマイグレーションが正しく動作するか確認しておきます。

$rake db:migrate:redo


これでエラーなく列の削除と追加が実行できていれば成功です。


Railsと実際のDBのバージョンがズレてしまった場合に参考にしてみてください。