Mongoid: Retrieves all embedded documents - ruby

Suppose we have these models:
class Person
include Mongoid::Document
embeds_many :albums
end
class Album
include Mongoid::Document
embeds_many :photos
end
class Photo
include Mongoid::Document
end
What I want is to retrieves all Photo of a particular Person. Is there a mongoid/mongodb shortcuts or the only way is to iterate over person.albums and store all album.photos in a new array?
Thanks.

You have 2 ways to do this, one is through Mongoid, which, AFAIK, will inflate all objects.
Something like:
Person.only("albums.photos").where(id: '1').albums.map(&:photos).flatten
Or you can do it in Moped(driver) which will return only an array of photos.
Person.collection.find(id: "1").select("albums.photos" => 1).
first["albums"].map { |a| a["photos"] }.flatten
On the DB load, both dont make any difference, as they will yield the same query, only difference is that the first one will create way more objects than the second one.

Related

Configure Mongoid relation to return objects sorted

I have two classes with a 1-n relationship. Like so:
class Band
include Mongoid::Document
has_many :members
end
class Member
include Mongoid::Document
field :name, type: String
field :joined, type: Date
belongs_to :band
end
Now when I call band.members I get the member objects. What I want is that if I call band.members.last to get the member who joined the last. I achieve this by defining the <=> method for Member and sort based on joined:
band.members.sort.last
How can I make this behavior default? I don't want to avoid the extra call to sort. Is this possible and if yes, how?
class Band
include Mongoid::Document
has_many :members, :order => :joined.asc
end

How to get record by ripple many association?

I want to save data into riak by ripple https://github.com/basho/ripple , and I create one model:
class Person
include Ripple::Document
property :name, String
many :friends, :class_name => "Person"
end
If there are two person model instances, how to know one person is other's friends or not ? like:
person.friends.get(somekey)
As person.friends is an array you can call it like this:
person.friends.include? otherperson

Mongoid: can I embed many and reference one of the embedded?

I have a list of games. Each one has an embedded list of scores. I'd like to keep a reference to the best score outside of the scores list.
class Game
include Mongoid::Document
field :best_score_id, type: Moped::BSON::ObjectId
...
embeds_many :scores
class Score
include Mongoid::Document
field :user, type: String
field :score, type: Int
I tried doing an belongs_to and a has_one but got an error message: "Referencing a Score document from the Game document via a relational association is not allowed since the price history is embedded." I suppose I can store the relevant bits of the Score in a hash called "best_score" but it makes more sense to me to embed many scores and then reference one of them as "Best". Is this possible?
You could do something like this -
Write a method to pick the best score in the game model class -
def best_score
score = scores.order_by(:score, :desc).limit(1)
if score.nil?
nil
else
score.first
end
end
And since scores are embedded in the game, there won't be any +1 query to the database also.

How to manage sequence of steps in Mongoid and Ruby

I'm trying to create a Tour with a sequence of Steps in Ruby and Mongoid:
class Tour
include Mongoid::Document
field :name, type: String
embeds_many :steps
end
class Step
include Mongoid::Document
field :name, type: String
embedded_in :tour
end
What's the best way to manage the ordered sequence of steps? (taking into account: inserting, deleting, moving steps around)
My first reaction is to do it one-way linked list style, storing the next step:
class Step
include Mongoid::Document
field :name, type: String
embeds_one :step, store_as: "next_step"
embedded_in :tour
end
Interested if anyone else has better design advice :)
Thanks!
Using one way relationship would lead to hard maintenance if you want to change steps order.
I recommend using embeds_many + a step order field + custom validator.

How to reference an embedded document in Mongoid?

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?
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...
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.
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
My current workaround is to do the following:
class Map
include Mongoid::Document
embeds_many :locations
references_many :players, :inverse_of => :map
end
class Player
referenced_in :map
field :x_coord
field :y_coord
def location=(loc)
loc.map.users << self
self.x_coord = loc.x_coord
self.y_coord = loc.y_coord
self.save!
end
def location
self.map.locations.where(:x_coord => self.x_coord).and(:y_coord => self.y_coord).first
end
end
This works, but feels like a kluge.
Thinking outside the box, you could make Location its own document and use Mongoid Alize to automatically generate embedded data in your Map document from your Location documents.
https://github.com/dzello/mongoid_alize
The advantage of this method is that you get efficient queries when conditions are suitable, and slower reference based queries on the original document when there's no other way.

Resources