How to reference an embedded document in Mongoid?

10,433

Solution 1

Because Maps are their own collection, you would need to iterate over every Map collection searching within for the Location your Player is referenced.

You can't access embedded documents directly. You have to enter through the collection and work your way down.

To avoid iterating all of the Maps you can store both the Location reference AND the Map reference in your Player document. This allows you to chain criteria that selects your Map and then the Location within it. You have to code a method on your Player class to handle this.

def location
  self.map.locations.find(self.location_id)
end

So, similar to how you answered yourself except you could still store the location_id in your player document instead of using the coord attribs.

Another way would be to put Maps, Locations, and Players in their own collections instead of embedding the Location in your Map collection. Then you could use reference relationships without doing anything fancy... however your really just using a hierarchical database likes it's a relational database at this point...

Solution 2

PLEASE go and VOTE for the "virtual collections" feature on MongoDB's issue tracker:

http://jira.mongodb.org/browse/SERVER-142

It's the 2nd most requested feature, but it still hasn't been scheduled for release. Maybe if enough people vote for it and move it to number one, the MongoDB team will finally implement it.

Solution 3

In my use case, there is no need for the outside object to reference the embedded document. From the mongoid user group, I found the solution: Use referenced_in on the embedded document and NO reference on the outside document.

class Page
  include Mongoid::Document
  field :title

  embeds_many :page_objects
end

class PageObject
  include Mongoid::Document
  field :type

  embedded_in       :page,    :inverse_of => :page_objects
  referenced_in     :sprite
end

class Sprite
  include Mongoid::Document
  field :path, :default => "/images/something.png"
end

header_sprite = Sprite.create(:path => "/images/header.png")
picture_sprte = Sprite.create(:path => "/images/picture.png")

p = Page.create(:title => "Home")
p.page_objects.create(:type => "header", :sprite => header_sprite)
p.page_objects.first.sprite == header_sprite
Share:
10,433

Related videos on Youtube

Scott Brown
Author by

Scott Brown

Updated on April 17, 2020

Comments

  • Scott Brown
    Scott Brown about 4 years

    Using Mongoid, let's say I have the following classes:

    class Map
      include Mongoid::Document
    
      embeds_many :locations
    end
    
    class Location
      include Mongoid::Document
    
      field :x_coord, :type => Integer
      field :y_coord, :type => Integer
    
      embedded_in      :map, :inverse_of => :locations
    end
    
    
    class Player
      include Mongoid::Document
    
      references_one   :location
    end
    

    As you can see, I'm trying to model a simple game world environment where a map embeds locations, and a player references a single location as their current spot.

    Using this approach, I'm getting the following error when I try to reference the "location" attribute of the Player class:

    Mongoid::Errors::DocumentNotFound: Document not found for class Location with id(s) xxxxxxxxxxxxxxxxxxx.
    

    My understanding is that this is because the Location document is embedded making it difficult to reference outside the scope of its embedding document (the Map). This makes sense, but how do I model a direct reference to an embedded document?

  • Dave Rapin
    Dave Rapin about 13 years
    This is perfectly doable because you're referencing a top level collection from within an embedded collection. What the OP wants to do is "reference an embedded collection from outside of it's parent collection", which is an entirely different thing. For example if you needed to access an embedded PageObject from your Sprite... you'd have to go through the parent Page collection to find it. Or like in my response you can store both a Page and a PageObject reference in your Sprite so you don't have to iterate over the entire Page collection.
  • colllin
    colllin over 11 years
    I believe you don't need to store the map ID separately: Maps.where('locations._id' => player.location_id) -- just make sure you set up the proper indexes (index maps by 'locations._id')
  • colllin
    colllin over 11 years
    Oh - you would still need to manually find the embedded location object, but at least you're no longer dependent on that location belonging to a particular map. Not sure if that's an issue.