Mongoid: How to implement a relationship between embedded documents? - ruby

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?

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)

Related

Rails Association belongs_to marking as optional dynamically in class and mixin

I have came across a scenario in an existing code base.
Im upgrading Rails to 5.1 from 5.0 and we now need to define relationships as optional where its not required.
I have came across a situation where we have a class and a mixin that are causing conflict.
Is there a way to define the relationship with the :user so that it will meet requirements in both scenarios.
In the Model, a user is not required, but within the mixin, a users presence is validated, causing the conflict.
As the Mixin is included, writing a proc to evaluate whether to mark the relationship as optional by using a function/proc results in the class method being used at runtime. If you prepend the mixin instead, it will default to the method in the mixin, meaning either way they conflict.
class Foo < ActiveRecord::Base
belongs_to :user, optional: true
end
module Mixins
module Foo
extend ActiveSupport::Concern
validates :user, presence: true, if: :user_is_required?
private
def user_is_required?
<!-- LOGIC HERE -->
end
end
end
At an engine level.. the mixin is included as so:
::Foo.include(Mixins::Foo)

Using Mongoid: Can you use embed_in in multiple documents at the same time?

I'm getting use to using Mongoid, however I ran into this problem with a situation where I'm trying to use Mongoid.
I have a game, the game has teams, the teams have players, and the game has the same players.
class Game
include Mongoid::Document
embeds_many :Players
embeds_many :Teams
end
class Team
include Mongoid::Document
embedded_in :Game
embeds_many :Players
end
class Player
include Mongoid::Document
embedded_in :Game
embedded_in :Team
end
Now when I run this code using
game = Game.new( :id => "1" )
game.save
player = Player.new()
game.Players << player
team = Team.new()
game.Teams << team
team.Players << player
I expect to have a Game, that has a team, that team has a player, and that player is ALSO in the game.
Then I run
newgame = Game.find("1")
newteam = newgame.Teams.first
newplayer = newgame.Players.first
newplayer2 = newteam.Players.first
newplayer exists, newplayer2 doesn't exist.
So what's up with that?
Am I only allowed to embedded a document in one object, is there a way around it? I tried making one of the relationship a belong_to and that isn't allowed if the document is embedded according to the output.
I know I can change the models (Game doesn't need a link to the players) I just want to know if this relationship violates some rule, or if there's some trick to make this work as stated.
As a side question can someone go over the rules of "saving" in this case (or assume that the player isn't embedded in the team). Once I set this up I don't appear to have to save the game, the team and the player to record the embedding. If I save any of those are the others then saved automatically. Or do I have to save each individual document when I modify them after the relationship is set (Assuming the modification is done after the relationship is set up as well).
You don't want to use embeds_many. This "embeds" the document into the parent document, and then doesn't make sense to have it embedded in multiple parent documents since then your Player data would be duplicated in multiple locations.
Think of what a nightmare it will be to continuously update and maintain consistency of your data when it's stored in multiple locations.
What you want to use is has_many to model these relationships. This stores only the _id of the document in the parent, whilst the actual document is stored in a separate collection, and allows multiple parents to reference it.
http://mongoid.org/en/mongoid/docs/relations.html#has_many
One to many relationships where the children are stored in a separate collection from the parent document are defined using Mongoid's has_many and belongs_to macros.
class Game
include Mongoid::Document
has_many :Players
has_many :Teams
end
class Team
include Mongoid::Document
belongs_to :Game
has_many :Players
end
class Player
include Mongoid::Document
belongs_to :Game
belongs_to :Team
end

Update or Push Embedded Document to Array

I have two document
class Holder
include Mongoid::Document
embeds_many :things
end
class Thing
include Mongoid::Document
embedded_in :holder
end
Is there a way to find if some element exists in Holder.things and updated it or if not create a new one? Similar to the upsert flag in mongoDB.
Thank you.
What have you tried so far? Look into the embedded 1-N relationship functions that Mongoid provides (here).
#myHolder.things.find_or_create_by(attr: 'val')
# or
if #myHolder.things.where(attr: 'val').empty? ...

How do I query for subclasses in embedded documents with mongoid?

I have a Box, with many toys of various types:
class Box
embeds_many :toys
end
class Toy
field :name
embedded_in :box
end
class Car < Toy
end
class Doll < Toy
end
If I have found the specific box object I want, how do I query it to find just the cars?
my_box.toys.where(??? Car ??? )
Obviously I could iterate over each toy and extract the ones where toy.is_a? Car, but I'm looking for a solution, if one exists, that just uses the built in mongoid criteria/finders.
Thats a good question. luckily Mongoid used to store _type meta attribute in the document to handle these kind of scenarios
so you can find the cars in the box by
Box.where('toys._type'=>'Car')
Thats all

Optional belongs_to relationship in Ruby

I'm migrating an application over to ROR from PHP that defines an object, let's call it a Document that can be created by a User who is either a registered user of the application, or an anonymous user who only gives their email address (in this case without an associated User account). There is no requirement for a visitor to create a User account, but they should still have the ability to create a Document.
In my model, I am first led to create the Document model that references the user with a belongs_to association. This however is not always the case. In pure SQL and PHP this is not hard, although it comes with it's own challenges, but I would like to model this in the "purest" Ruby/Rails way.
If a visitor does not have an associated User, their email address will be stored directly against the Document object.
To start the discussion, here are the snippets for each model.
# user.rb
class User < ActiveRecord::Base
has_many :documents
end
# document.rb
class Document < ActiveRecord::Base
attr_accessible :title, :description, :email_address
belongs_to :user
end
My goal is to be able to retrieve all user documents using the standard dot notation:
user.documents
And to also check for the reverse relationship to see if the document belongs to a User or if it contains an email address.
if document.user.nil? ...
if document.email_address.nil? ...
I have read up on the has_one relationship but am not sure if this is the right path to go down.
Any help or suggestions would be appreciated, thanks!
You might want to consider making the User class more flexible so it supports anonymous users and only stores the email address.
It might make your code a bit cleaner.

Resources