Mongoid embedded document returning empty for queries - mongodb

When I query the embedded model, no records are returned despite there being plenty of parent records containing instances of the embedded model.
There are two models, a Label embedded in a Band:
class Band
include Mongoid::Document
embeds_one :label
end
class Label
include Mongoid::Document
field :name, type: String
embedded_in :band
end
I can query Band (Band.all, Band.find etc) just fine, but when I query Label, it returns nothing. For instance:
I create the band with the embedded label, and save it:
> b = Band.create
=> #<Band _id: 516cff525543d8842e000008, _type: nil>
> b.build_label name: "new label"
=> #<Label _id: 516cff5a5543d8842e000009, _type: nil, name: "new label">
> b.save
=> true
Then I query the Band model, and all is fine:
> Band.all.to_a
=> [#<Band _id: 516cff525543d8842e000008, _type: nil>]
> Band.count
=> 1
> Band.first.label
=> #<Label _id: 516cff5a5543d8842e000009, _type: nil, name: "new label">
> Band.find "516cff525543d8842e000008"
=> #<Band _id: 516cff525543d8842e000008, _type: nil>
But when I query the Label model, nothing shows up!
> Label.all.to_a
=> []
> Label.count
=> 0
> Label.last
=> nil
> Label.first
=> nil
> Label.find "516cff5a5543d8842e000009" # this is the label id from Band
=> nil
I'm almost positive this is not intended behavior. The code is directly from an example on the Mongoid docs here: http://mongoid.org/en/mongoid/docs/relations.html#embeds_one
What am I missing?

In mongo your queries always target collections, i.e. full documents that may embed other documents. For mongo it's just one big JSON/BSON document. Now, Label.all or Label.all are equivalent to querying the Label collection. Since labels are not stored in the Label collection, those queries return nothing. However, you can still query labels on the Band collection by calling
Band.where(:'labels._id' => "516cff5a5543d8842e000009")
or something like
Band.where(:'labels.name' => "rock")
This is fine if you want to get all bands with a certain label. Getting all labels this way, however, is very expensive and not recommended. What's your main use case? If it is showing labels for a band or getting bands with a certain label, then embedding is great. Otherwise, you could use relations (has_many/belongs_to), or denormalize completely, i.e. save labels within bands and in a separate collection at the same time (leading to redundant data).

I think you should use has_many, has_one, belongs_to methods for making possible to run queries as you want like Label.count.
When you embed document into another document it will become as a part(serialized property) of a document. If you want to select labels you should find Band at first and then check the label property. It should definitely work.

Related

Difference between `{id: 1, title: "text"}` and {title: "text", id: 1} MongoDB Indexes

I am designing a schema where a User document stores the Skill ids. A Skill document look like this
Skill = {
id: ObjectId
title: String
description: String
}
User = {
id: ObjectId
skills: [ObjectId]
}
And on the frontend side, a user can add Skills by searching it's title. Therefore, I indexed skills collection by {id: 1, title: "text"}. I want to know whether ordering matters when we combine text indexes with the numeric ones.
Yes, it makes a big difference, the documentation about composite text indexes says
If the compound text index includes keys preceding the text index key, to perform a $text search, the query predicate must include equality match conditions on the preceding keys.
That means if you do {id: 1, title: 'text'} you can only use the text index on title if you also constrain the search to within a single id.
If you do {title: 'text', id: 1} you will be able to text-search title by itself, or further constrained by id, or retrieve the matching id for the text search results.
Are you sure you need that id column in the index? Why not just the text index?
Yes, it does matter. Straight from the docs:
The order of the fields listed in a compound index is important. The index will contain references to documents sorted first by the values of the item field and, within each value of the item field, sorted by values of the stock field. See Sort Order for more information.
In your current structure if you only query the title field without an restrictions (no query) on the id field you will not be able to utilize the index.

How do you filter Algolia Results and omit a specific objectID

I'm trying to filter algolia results and want to use the name of a product to find similar products in our database. I would prefer to ignore the current product so, in the case no results are found, the removeWordsIfNoResults option will be used.
I'm using the Ruby api.
This is basically what I'm trying but it is not working. Is it possible to filter out a specific objectID?
Product.raw_search("Men's Bridge & Burn Camden Steel Blue",
{ :hitsPerPage => 10,
:page => 0,
:attributesToRetrieve => "objectID",
:filters => "objectID != '65411'",
:removeWordsIfNoResults => 'lastWords'
}
)
objectID is a specific attribute in Algolia that is always a string value and cannot be added to the attributesForFaceting .
However, if you're filling it with the ids you have in your database, you can index another attribute (let's say id) with the same value.
Numerical value
As an numerical value, you can directly filter on it using Algolia. However, since you'll only be using the != operator, I recommend you to declare it in the numericAttributesToIndex list with an equalOnly modifier:
index.set_settings({
numericAttributesToIndex: %w(equalOnly(id))
})
At query time, you can pass it as you did in your example if you remove the quotes around the value:
index.raw_search('Product title', {
filters: 'id!=65411'
# Your other parameters
})
String value
With a string value, you want to add it to your attributesForFaceting :
index.set_settings({
attributesForFaceting: %w(id)
})
Then query it like so:
index.raw_search('Product title', {
filters: 'id:-65411'
# Your other parameters
})
(The - for exclusion is described in the facetFilters documentation)

How do I implement this mongodb query & update operation (CSharp driver)?

I have this collection:
Books
[
{
title: Book1,
References: [ObjectId(1), ObjectId(3), ObjectId(5)] <- These are object ids of another collection
Main-Reference: ObjectId(5)
},
{
title: Book2,
References: [ObjectId(2), ObjectId(5), ObjectId(7)]
Main-Reference: ObjectId(5)
}
{
title: Book3,
References: [ObjectId(5), ObjectId(7), ObjectId(9)]
Main-Reference: ObjectId(7)
},
]
I have an operation where I delete a Reference from book collection
Example: Assume I have to delete Reference ObjectId(5) from my collection
So my new collection become this:
Books
[
{
title: Book1,
References: [ObjectId(1), ObjectId(3)] <- ObjectId(5) is pulled
Main-Reference: ObjectId(1) <- ObjectId(1) is new value as ObjectId(5) is deleted
},
{
title: Book2,
References: [ObjectId(2), ObjectId(7)] <- ObjectId(5) is pulled
Main-Reference: ObjectId(2) <- ObjectId(2) is now main reference
}
{
title: Book3,
References: [ObjectId(7), ObjectId(9)] <- ObjectId(5) is pulled
Main-Reference: ObjectId(7) <- no changes here as ObjectId(7) still exists in References
},
]
Currently this is how I am doing:
Step 1: Pull ObjectId(5) from all Books where References[] has ObjectId(5)
Step 2: Query Books collection where Main-Reference=ObjectId(5) & use References: {$slice:1} slice to get the first array element from References array
Step 3: Update all of the books found in Step 2 & replace Main-Reference with the first array element I get from slice
This seems clumsy to me and trying to see if there is a better way to do this.
If I essentially get your gist you basically want to
Pull the item that is not required from your references array
Set the value of your main-reference field to the first element of the altered array
And get that done all in one update without moving documents across the wire.
But this sadly cannot be done. The main problem with this is that there is no way to refer to the value of another field within the document being updated. Even so, to do this without iterating you would also need to access the changed array in order to get the new first element.
Perhaps one approach is to re-think your schema in order to accomplish what you want. My option here would expanding on your references documents a little and removing the need for the main-reference field.
It seems that the assumption you are willing to live with on the updates is that if the removed reference was the main-reference then you can just set the new main-reference to the first element in the array. With that in mind consider the following structure:
refs: [ { oid: "object1" }, { oid: "object2" }, { oid: "object5", main: true } ]
By changing these to documents with an oid property that would be set to the ObjectId it gives the option to have an additional property on the document that specifies which is the default. This can easily be queried determine which Id is the main reference.
Now also consider what would happen if the document matching "object5" in the oid field was pulled from the array:
refs: [ { oid: "object1" }, { oid: "object2" } ]
So when you query for which is the main-reference as per the earlier logic you accept the first document in the array. Now of course, to your application requirements, if you want to set a different main-reference you just alter the document
refs: [ { oid: "object1" }, { oid: "object2", main: true } ]
And now the logic remains to choose the array element that has the main property as true would occur in preference, and as shown above that if that property does not exist on any elements document then fall back to the first element.
With all of that digested, your operation to pull all references to an object out of that array in all documents becomes quite simple, as done in the shell ( same format should basically apply to whatever driver ):
db.books.update(
{ "refs.oid": "object5" },
{ $pull: { refs: {oid: "object5"} } }, false, true )
The two extra arguments to the query and update operation being upsert and multi respectively. In this case, upsert does not make much sense as we only want to modify documents that exist, and multi means that we want to update everything that matched. The default is to change just the first document.
Naturally I shortened all the notation but of course the values can be actual ObjectId's as per your intent. It seemed also reasonable to presume that your main usage of the main-reference is once you have retrieved the document. Defining a query that returns the main-reference by following the logic that was outlined should be possible, but as it stands I have typed a lot out here and need to break for dinner :)
I think this presents a worthwhile case for re-thinking your schema to avoid over the wire iterations for what you want to achieve.

Fetch Record from mongo db based on type and ancestry field

in mongodb records are store like this
{_id:100,type:"section",ancestry:nil,.....}
{_id:300,type:"section",ancestry:100,.....}
{_id:400,type:"problem",ancestry:100,.....}
{_id:500,type:"section",ancestry:100,.....}
{_id:600,type:"problem",ancestry:500,.....}
{_id:700,type:"section",ancestry:500,.....}
{_id:800,type:"problem",ancestry:100,.....}
i want to fetch records in order like this
first record whose ancestry is nil
then all record whose parent is first record we search and whose type is 'problem'
then all record whose parent is first record we search and whose type is 'section'
Expected output is
{_id:100,type:"section",ancestry:nil,.....}
{_id:400,type:"problem",ancestry:100,.....}
{_id:800,type:"problem",ancestry:100,.....}
{_id:300,type:"section",ancestry:100,.....}
{_id:500,type:"section",ancestry:100,.....}
{_id:600,type:"problem",ancestry:500,.....}
{_id:700,type:"section",ancestry:500,.....}
Try this MongoDB shell command:
db.collection.find().sort({ancestry:1, type: 1})
Different languages, where ordered dictionaries aren't available, may use a list of 2-tuples to the sort argument. Something like this (Python):
collection.find({}).sort([('ancestry', pymongo.ASCENDING), ('type', pymongo.ASCENDING)])
#vinipsmaker 's answer is good. However, it doesn't work properly if _ids are random numbers or there exist documents that aren't part of the tree structure. In that case, the following code would work rightly:
function getSortedItems() {
var sorted = [];
var ids = [ null ];
while (ids.length > 0) {
var cursor = db.Items.find({ ancestry: ids.shift() }).sort({ type: 1 });
while (cursor.hasNext()) {
var item = cursor.next();
ids.push(item._id);
sorted.push(item);
}
}
return sorted;
}
Note that this code is not fast because db.Items.find() will be executed n times, where n is the number of documents in the tree structure.
If the tree structure is huge or you will do the sort many times, you can optimize this by using $in operator in the query and sort the result on the client side.
In addition, creating index on the ancestry field will make the code quicker in either case.

Mongodb querying document with linked id

I have a document that has an id of another document from a different collection embedded in it.
My desired result is to return (I'm using python and pymongo) the all the fields of the first collection, and all of the friends from the document that was embedded.
I understand mongo doesn't do joins and I understand I'll need to make two queries. I also don't want to duplicate my data.
My question is how to piece the two queries together in python/pymongo so I have one results with all the fields from both documents in it.
Here is what my data looks like:
db.employees
{_id: ObjectId("4d85c7039ab0fd70a117d733"), name: 'Joe Smith', title: 'junior',
manager: ObjectId("4d85c7039ab0fd70a117d730") }
db.managers
{_id: ObjectId("ObjectId("4d85c7039ab0fd70a117d730"), name: 'Jane Doe', title: 'senior manager'}
desired result
x = {_id: ObjectId("4d85c7039ab0fd70a117d733"), name: 'Joe Smith', title: 'junior',
manager: 'Jane Doe' }
Your basically doing something that Mongo does not support out of the box and would actually be more painful than using the two records separately.
Basically (in pseudo/JS code since I am not a Python programmer):
var emp = db.employees.find({ name: 'Joe Smith' });
var mang = db.managers.find({ _id: emp._id });
And there you have it you have the two records separately. You cannot chain as #slownage shows and get a merged result, or any result infact since MongoDB, from one qauery, will actually return a dictionary not the value of the field even when you specify only one field to return.
So this is really the only solution, to get the the two separately and then work on them.
Edit
You could use a DBRef here but it is pretty much the same as doing it this way, only difference is that it is a helper to put it into your own model instead of doing it youself.
If it works it should be something like:
db.managers.find({
'_id' => db->employees->find({ ('_id' : 1),
('_id': ObjectId("4d85c7039ab0fd70a117d733") }))
})
updated