ngw
ngw

Reputation: 1282

Mongoid polymorphic associations with embeds

I'm confused on how to make polymorphism work with embeds and mongoid 8.x.

class Entity
  include Mongoid::Document

  embeds_many :custom_fields, as: :item
end

class EmailField
  include Mongoid::Document

  field :name, type: String
  field :value, type: String

  embedded_in :item, polymorphic: true
end

class MultivalueField
  include Mongoid::Document

  field :name
  field :value, type: Array

  embedded_in :item, polymorphic: true
end

I don't understand why this doesn't work. What I expect is the model Entity to have many fields of different types embedded, but it explodes with

 NameError:
   uninitialized constant MetadataService::Models::Entity::CustomField

What am I doing wrong?

Upvotes: 0

Views: 49

Answers (1)

Stefan
Stefan

Reputation: 114248

Mongoid doesn't support polymorphism with embeds_many. Instead, you can use inheritance to embed documents of different classes by referencing their common ancestor:

class Entity
  include Mongoid::Document

  embeds_many :items
end

class Item
  include Mongoid::Document

  embedded_in :entity

  field :name, type: String
end

class EmailItem < Item
  field :value, type: String
end

class MultivalueItem < Item
  field :value, type: Array
end

I'm using "Item" instead of "Field" because the latter has special meaning in Mongoid and is regarded a reserved word. You can probably use embeds_many :items, class_name: 'Field' to work around this limitation, but I wanted to keep the example as simple as possible.

Usage:

e = Entity.new
e.items << EmailItem.new(name: 'email', value: '[email protected]')
e.items << MultivalueItem.new(name: 'email', value: [1, 2, 3])
e.as_document
#=> {
#     "_id"=>BSON::ObjectId('660e7a7e571de609e1c84723'),
#     "items"=>[
#       {
#         "_id"=>BSON::ObjectId('660e7a7e571de609e1c84724'),
#         "name"=>"email",
#         "value"=>"[email protected]",
#         "_type"=>"EmailItem"
#       },
#       {
#         "_id"=>BSON::ObjectId('660e7a7e571de609e1c84725'),
#         "name"=>"email",
#         "value"=>[1, 2, 3],
#         "_type"=>"MultivalueItem"
#       }
#     ]
#   }

You can also pass the item's class via the build and create helpers as a separate argument:

e.items.build({ name: 'email', value: '[email protected]' }, EmailItem)
e.items.build({ name: 'email', value: [1, 2, 3] }, MultivalueItem)

Upvotes: 1

Related Questions