rm -rf vendor/bundle
これでランチを食べに行ける。
背景
昨日、Railsの開発中にCI環境のJenkinsさんでRspecを回そうとしたところ、タイトルのエラーが出て解決できずはまった。
環境
Ruby On Rails 4.x.x
触ってるRailsアプリが多すぎて正確なバージョンは覚えていない。
エラー発生の状況
呼び出し元はこんなクラス。普通にhas_many
しているだけ。
class Hoge < ActiveRecord::Base
has_many :fugas
end
それがこのコードを呼んで
def has_many(name, scope = nil, options = {}, &extension)
SchemaMonkey::Middleware::Model::Association::Declaration.start(model: self, name: name, scope: scope, options: options, extension: extension) do |env|
env.result = super(env.name, env.scope, env.options, &env.extension)
end.result
end
こうなる。
NameError: uninitialized constant SchemaMonkey::Middleware::Model
で、このSchemaMonkey::Middleware::Model
の定義は同じgemの中で行われている。
つまり、あるgemの中で定義されたメソッドを呼び出しているのに、同じgemの中で定義されている定数が呼び出せない、ということが起こっていた。
普通にhas_many
しただけなのにヘビーすぎる。
原因は?
has_many
メソッドが呼ばれている以上、このgemの読み込みには成功している。
gemが読み込めている以上、SchemaMonkey::Middleware::Model
は定義されている。
ここで定義されていない可能性を疑う(= gemのバグと考える)のは早計なので、定義されていて、かつ読み込めない状況を考える。
普通、Rubyでは定数を宣言したら以下のように宣言しなおしたからと言って上書きされることはない。
module Hoge; end
puts Object.const_defined?(:Hoge) #=> true
puts Hoge.const_defined?(:Fuga) #=> false
module Hoge
module Fuga
end
end
puts Object.const_defined?(:Hoge) #=> true
puts Hoge.const_defined?(:Fuga) #=> true
module Hoge; end # 宣言しなおしてみる
puts Object.const_defined?(:Hoge) #=> true
puts Hoge.const_defined?(:Fuga) #=> true
ここでSchemaMonkey
の宣言方法を見てみる。
SchemaMonkey.register(SchemaPlus::Core)
こんなメソッドにたどり着く。
def register(mod)
@client_map[mod] ||= Client.new(mod).tap { |client|
client.insert if @inserted
client.insert(dbm: @inserted_dbm) if @inserted_dbm
}
end
この先を追求するのは、ちょっと辛いのでここまででわかったことを整理する。
通常、Rubyのmoduleやclassの宣言(定数の宣言)は元の定数を"開く"だけで上書きはしない。
このgemではmoduleの宣言をHash
を使ってまとめているので上書きされている可能性がある…?
#=> register
の引数の定数がkeyなので可能性は低そう。
何か、よくわからない力が働いているに違いない…。
結局
bunderを信じるしかない。
困った時はvendor/bundle
を消してbundle install
しなおす。
もやもやするのでいつかbundlerを調べて続きを書くかもしれません。