Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoid: How to implement a relationship between embedded documents?

Tags:

ruby

mongoid

I have a situation where I have a parent document and I want to have two different types of embedded documents: one as a parent, and another as a child with an optional parent. For example:

class ParentDoc
  include Mongoid::Document
  embeds_many :special_docs
  embeds_many :special_doc_groupings
end

class SpecialDoc
  include Mongoid::Document
  embedded_in :parent_doc
  belongs_to :special_doc_groupings
end

class SpecialDocGrouping
  include Mongoid::Document
  embedded_in :parent_doc
  has_many :special_docs
end

In this example, SpecialDocs and SpecialDocGroupings can exist without a relationship, or optionally can have a parent-child relationship.

However, this is an invalid Mongoid association because we get this error:

Mongoid::Errors::MixedRelations:

Problem: Referencing a(n) SpecialDoc document from the SpecialDocGrouping document via a relational association is not allowed since the SpecialDoc is embedded.

Summary: In order to properly access a(n) SpecialDoc from SpecialDocGrouping the reference would need to go through the root document of SpecialDoc. In a simple case this would require Mongoid to store an extra foreign key for the root, in more complex cases where SpecialDoc is multiple levels deep a key would need to be stored for each parent up the hierarchy.

Resolution: Consider not embedding SpecialDoc, or do the key storage and access in a custom manner in the application code.

I don't see anything wrong with the type of association that I'm trying to create, besides the fact that it's not supported by Mongoid.

How can I implement this type of association myself?

like image 663
Andrew Avatar asked Apr 24 '13 22:04

Andrew


1 Answers

The association is not valid because when you reference embedded model Mongoid does not store the parent key as foreign key. This means that if you have:

Class Parent        
   embeds_many :children
end 

Class Child
   embedded_in :parent
end

You cannot reference Child document storing only its foreign key, but you need to store all the parents keys until you reach the root. In this case the root is represented by the first parent and you need to store 2 keys.

You can accomplish this manually, and create this type of association without any problem.

Your case is a bit different (and easier) because you want to create the association between two models embedded in the same parent. That means theoretically you don't need to store the parent key because the models share the same root. Mongoid does not handle this scenario, so you need to manually create your association rules, and methods.

Class Bar
  embeds_many :beers
  embeds_many :glasses
end

Class Beer
  embedded_in :bar
  # Manual has_many :glasses association
  def parent
     self.bar
  end

  def glasses
     parent.glasses.where(:beer_id => self.id)
  end

end

Class Glass
   embedded_in :bar
   # Manual belongs_to :beer association
   field :beer_id, type: Moped::BSON::ObjectId
   def parent
      self.bar
   end

   def beer
      parent.beers.find(self.beer_id)
   end
end

(the code is not tested)

like image 191
marquez Avatar answered Nov 04 '22 08:11

marquez