rake db:migrate:reset
が通らないという現象に見舞われました。
現象
エラーメッセージは
rake aborted!
Mysql2::Error: Table 'myproject.users' doesn't exist: SHOW FIELDS FROMusers
環境は Rails 3.1.0 + Ruby 1.9.2 + devise 1.5.0 です。
原因
原因は、Userモデルに定義した次のスコープでした。
# app/models/user.rb
class User < ActiveRecord::Base
scope :find_by_hoge, lambda{|val|
joins(:foo).where("foos.hoge_id" => val
}
end
# config/routes.rb
devise_for :users
検索を便利に使うためのnamed scopeですが、これがfind_by_*
という名前になってしまっていたのが原因です。
stacktraceを追うとわかるのですが、
- rake db:migrate:resetを起動
- db初期化される
- rake起動のためにroutes.rbが読まれる
devise_for
が実行される- 引数を元にroutingを調べるためのUserモデルが読まれる
- scopeを発見
- active_record/named_scope.rbの
valid_scope_name?
が実行される DynamicFinderMatch.match
が呼ばれるfind_by_*
に一致するのでattribute一覧とconflictしないかチェックされる- attribute一覧を調べるためにMySQLに接続される
- usersテーブルはまだ無い
- エラー
といった流れが発生していました。
Railsでfind_by_*
やfind_all_by_*
などのメソッドは自動生成されるので、scopeが衝突するとワーニングを出す機能があるのですが、そのチェック箇所で上記現象が発生しています。
解決方法
そもそもscopeでfind_by
だと、名前的にActiveRecord::Base
が返ってくると思いきやActiveRecord::Relation
が返ってきて混乱するので、scopeの名前はfind_by_*
にしてはいけないということですね。
同様に、
* scoped_by_*
* find_all_by_*
* find_last_by_*
* find_or_create_by_*
* find_or_initialize_by_*
でも同じ問題が発生します。
わかりやすくて衝突しない名前に変更しましょう。