以下のようなモデルを定義して、 User を継承した WrapUser のインスタンスが one アソシエーションのメソッド呼び出しをすると関連モデルではなく nil が返る、というので少しハマった。
class User include MongoMapper::Document one :membership end class Membership include MongoMapper::Document belongs_to :user end class WrapUser < User end
悪い実装のサンプルコードと実行結果: https://gist.github.com/kyanny/cb3cfb759dd0fc8c0ec8e0ad175bfd47
MongoMapper のソースのあちこちに byebug を仕込んだりしたが、 MongoDB のクエリのログを見れば一目瞭然で、 Single Table Inheritance するとインスタンスのクラス名が変わるので、 MongoMapper が内部で MongoDB の find クエリを組み立てる際の条件に使われるキー名も変わってしまう。
testing['memberships'].find({:wrap_user_id=>BSON::ObjectId('583c72606200b03f8b000001')}).limit(-1)
今回の例でいうと、 wrap_user_id
というキーはどこにも定義されておらず、コレクション内のドキュメントもこのキーを持っていないので、このクエリにマッチするドキュメントは無く、結果として nil が返る。
これに対処するには、 one アソシエーションの定義時に :foreign_key オプションを付けて、誤った外部キー名が自動生成されないようにすればよい。
class WrapUser < User one :membership, foreign_key: :user_id end
正しい実装のサンプルコードと実行結果: https://gist.github.com/kyanny/42d961b252a5f1aca797f8482d959ca5
クエリはこうなる testing['memberships'].find({:user_id=>BSON::ObjectId('583c72996200b0416a000001')}).limit(-1)
差分はこれだけ。 gist.github.com
特定の条件を満たしたときだけ MongoMapper なモデルに MixIn されるモジュールがあって、それに対するユニットテストの実装に問題があった。テストスイートの実行中、ターゲットのモデルは MixIn されていない綺麗な状態を保ちたいので、ターゲットのモデルを継承したモデルをテストスクリプトの中で定義し、そのモデルのインスタンスに対して MixIn が提供するインスタンスメソッドのユニットテストを書いていた。その中で one アソシエーションの関連先モデルを呼び出すコードを実行したら、 nil で落ちてしまった、というのがことの発端だった。上に書いたのと同じ処置を施したら nil で落ちなくなった。