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

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

Related

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

Multiple controllers, two "depths" of relationships

I'm writing my first app in Ruby on Rails (I've only went through railstutorial.org before ) which is simple Electronic Prescription Service and I run on one thing I can't cope with.
I want to create form for adding new prescription as well as adding some medicines which belong to this newly created prescription.
First problem is - how can I actually add medicines to the prescription when during filling the form prescription doesn't exist yet? What I did before was I first create prescription with #user.prescription.create(presc_attributes) and later #prescription.relations.create(medicine_id).
Now I need to do this probably on one form sending the whole thing using one button ( one HTML request, am I right? ) unless you guys have got better idea. I'm also struggling with an idea where to actually put method creating this prescription. Should it be in PrescriptionController#new or maybe connected to RelationsController#new as well?
I've read couple of articles about nested fields_for but they don't seem to match exactly what I need.
Im really new to RoR so sorry if I missed something important.
Here is my EER as well
http://i.stack.imgur.com/sa9CB.png
UPDATE---
ahhh i see, I think what you want is a relationship with an inverse_of.
If you are using a belongs_to on the join model, it is a good idea to set the :inverse_of option on the belongs_to, which will mean that the following example works correctly (where tags is a has_many :through association):
#post = Post.first
#tag = #post.tags.build name: "ruby"
#tag.save
The last line ought to save the through record (a Taggable). This will only work if the :inverse_of is set:
class Taggable < ActiveRecord::Base
belongs_to :post
belongs_to :tag, inverse_of: :taggings
end
If you do not set the :inverse_of record, the association will do its best to match itself up with the correct inverse. Automatic inverse detection only works on has_many, has_one, and belongs_to associations.
FROM http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

How to get aggregate totalling with data from two Models in DataMapper?

I am new to DataMapper and Ruby, and have been struggling to work out a way to format the expression which will give me what I am after. Let me explain. I have two Models in my project:
Class Manufacturer
include DataMapper::Resource
property :id, Serial
property :name, String
...<snip>...
has n, :items
end
Class Item
include DataMapper::Resource
property :id, Serial
property :name, String
...<snip>...
belongs_to :manufacturers
end
What I am trying to get is a collection which will give me the list of all manufacturers, plus a count of all items that they have produced. e.g.:
"Acme Industries", 32
"Bart Enterprises", 12
"Coco Mondo", 0
"XYZ Corp.", 55
That is, the :name from the Manufacturer model, and the count(:id) from the Item model. I've got as far as:
Manufacturer.all.items.aggregate(:manufacturer_id, :all.count)
which gives me the :manufacturer_id property and the correct count of items. Close, but no banana.
How can I get the manufacturer name rather that the id property in this case?
I tried to learn how to use DataMapper a month ago and I could find very limited support in stackoverflow and in the internet. It turns out that the project is not supported any more and the team behind is now developing another orm called ROM.
Since you are in the beginning I would recommend you to use another orm with better support(sequel,ActiveRecords etc)

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? ...

Mongoid: How to implement a relationship between embedded documents?

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)

Resources