Using Mongoid3, I'm trying to add polymorphism to my embedded relations.
I have a class Item
that must embed_one object containing my informations.
The deal is that :
- my object's type can be one of those : Calendar
, Sticker
, Picture
;
- regardless my object's type, I want to access it by a unique 'key' : detail
e.g. :
pry> my_item1.detail
=> `<Picture _id: 1234>`
pry> my_item2.detail
=> `<Sticker _id: 8964>`
pry>
First, I tried using the keywords as and polymorphic like described here: https://github.com/mongoid/mongoid/issues/902
e.g. :
class Item
include Mongoid::Document
embeds_one :detail, as: :item_slot
end
class Picture
include Mongoid::Document
embedded_in :item_slot, polymorphic: true
end
class Calendar
include Mongoid::Document
embedded_in :item_slot, polymorphic: true
end
class Sticker
include Mongoid::Document
embedded_in :item_slot, polymorphic: true
end
Then I'm trying to access to my detail but unhappily, I got this message error :
pry(main)> i = Item.new
pry(main)> i.detail
=> nil
pry(main)> i.detail = Picture.find('50b864').dup
pry(main)> i.save
=> true
pry(main)> i = Item.find i._id
pry(main)> i.detail
NameError: uninitialized constant Detail
from /home/jg/.rvm/gems/ruby-1.9.3-p448/gems/activesupport-3.2.14/lib/active_support/inflector/methods.rb:230:in `block in constantize'
It says mongoid didn't find any .detail
into my item
. Why not.
Then, I found this topic mongoid polymorphic association error telling to add class_name. So I updated my code like so :
class Item
include Mongoid::Document
embeds_one :detail, class_name: "Picture", class_name: "Calendar", class_name: "Sticker"
end
Let's try:
pry(main)> i.detail = Picture.find('50b864').dup
pry(main)> i.save
=> true
pry(main)> i = Item.find i._id
pry(main)> i.detail
=> #<Sticker _id: 52961d>
That's embarrassing because I was expecting to get a Picture
, not a Sticker
(it has my picture's values inside). (The result is the same if I put each different class_name value on new lines.)
For my third try, I used Custom Relation Names like so :
class Item
include Mongoid::Document
embeds_one :detail, class_name: "Picture", class_name: "Calendar", class_name: "Sticker", inverse_of: :item_slot
end
class Picture # for Calendar and Sticker too ofc
include Mongoid::Document
embedded_in :item_slot, polymorphic: true, inverse_of: :detail
end
But it gave me a Sticker too.
I even tried :
class Item
include Mongoid::Document
embeds_one :detail, inverse_of: :item_slot
end
But it send me again the NameError seen previously.
My last try was to use inheritance :
class Item
include Mongoid::Document
embeds_one :detail
end
class Detail
include Mongoid::Document
embedded_in :item
end
# Same again for Calendar and Sticker
class Picture < Detail
... # regular other fields
end
But it gives me awful messages when lunching pry for sticker.rb and calendar.rb
DEVEL - Failed to load .../models/sticker.rb; removing partially defined constants
DEVEL - Problem while loading .../models/sticker.rb: uninitialized constant Detail
I have no more idea..
==> Have anyone a tips ?
EDIT :
It would be nice to have an equivalent to Hash[e.attributes]
like here Extract `Moped::BSON::Document` attributes in Ruby hash and then do like so :
class Item
include Mongoid::Document
field :detail
end
that keep my Picture
, Calendar
and Sticker
as class instances (because I have different methods for each to apply after saving).
JG
EDIT: Alternative way
edit 08/08/2014, split that part as the answer of that topic.