LoginSignup
17
15

More than 5 years have passed since last update.

Railsのマイグレーションファイルでモデルに触ってはいけない

Posted at

はじめに

これは migrationファイルのサンプルだが、何が悪いかわかるだろうか。

class AddNameToUser < ActiveRecord::Migration
  def change
    add_column :users, :name, :string
    User.find_each do |u|
      u.name = u.email.split("@")[0]
    end
  end
end

このファイルを使ってマイグレーションした時にはエラーは発生しない。しかし新たな環境にデプロイする時に謎のエラーが発生するかもしれない。

原因

ActiveRecord::Baseを継承したクラスはインスタンスを作成するタイミングで、データベーススキーマを確認する。
上記の例では、find_eachの中でusersテーブルのスキーマを確認し、Userクラスに保存しておく。

このスキーマ情報は各モデルクラスにキャッシュされる。インスタンス化のたびにスキーマを参照するわけにはいかないので当然だろう。
しかしマイグレーション時には、このキャッシュが悪さをする。

さらにユーザにnick_nameというフィールドを追加することにした。

class AddNickNameToUser < ActiveRecord::Migration
  def change
    add_column :users, :nick_name, :string
    User.find_each do |u|
      u.nick_name = u.name
    end
  end
end

これもおかしなプログラムではない。しかし2つのマイグレーションを連続で実行するとエラーが発生する。
最初のAddNameToUserでスキーマがキャッシュされる。この時点ではnick_nameというフィールドは無い。
次のAddNickNameToUserでスキーマはキャッシュされており、読み込まない。この結果Userクラスはnick_nameフィールドを持たないということになり、代入時にエラーが出る。

対応

実際のところdb:migrateが失敗に終わってもAddNameToUserの直前まではマイグレートが完了している。つまりエラーがでなくなるまでrake db:migrateを繰り返し実行すれば問題は回避する。いわゆる「運用でカバー」だ。

しかし問題は解決しておこう。この問題はスキーマがキャッシュされるのが原因なので、キャッシュさせなければよい。つまり、以下のようなコードを書けば良い。

class AddNameToUser < ActiveRecord::Migration
  # キャッシュされるクラス
  class User < ActiveRecord::Base; end

  def change
    add_column :users, :name, :string
    User.find_each do |u|
      u.name = u.email.split("@")[0]
    end
  end
end

こうすることで、スキーマがキャッシュされるのは AddNameToUser::User であり、他のマイグレーションには影響を与えない。複数のテーブルを扱うのであれば、その数だけ定義し、has_manyなどのリレーションをはること。

残念ながらmodelのロジックを流用するには面倒だが、定数の利用くらいならできる。

class AddNameToUser < ActiveRecord::Migration
  class User < ActiveRecord::Base; end

  def change
    add_column :users, :name, :string
    User.find_each do |u|
      u.name = ::User.DEFAULT_NAME
    end
  end
end

まとめ

運用でカバーできるので頑張らなくても良いかもしれない。
しかし、綺麗に一発でマイグレーションできたほうが気持ちが良いし、
bash等のスクリプトで実行する場合 while を書く必要が出たりで面倒なので、そういうもんだと思って上記の方法を取るのが良いように思う。

17
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
15