Why is an array in my mongoose model preventing me from querying - node.js

I have the following mongoose model
CartSchema = new Schema({
company: String,
items: [{
_id: Schema.ObjectId,
price: Number,
gears: [String]
}],
});
I access it via this simpe query
const response = await Cart.findOne( { "items": { _id: "5e4d7a5bcff77131f46d8aa9" } });
And this is my data in the mongo database
So from this information we can see that the only information that I have in my database that corresponds to the model is the items[0]._id which should be found with the query above. The odd thing is it returns null as long as this line gears: [String], is in my model. It is not required (I also tried setting it manually to required : false but I can't seem to get my data if this line is in my model. If I remove the line from my model, I can get the data just fine.
Am I missing something obvious here that would prevent me from getting my data because of the gears: [String] line in my model?

by this way, { "items": { _id: "5e4d7a5bcff77131f46d8aa9" } }, you're searching for an exact match, the items should be an object contains only the specified _id
instead, you should use the dot notation to filter by the _id in the items array
const response = await Cart.findOne( { "items._id": "5e4d7a5bcff77131f46d8aa9" });
hope it helps

Related

Mongoose - How do I find a specific document by matching an element in an array of object Ids in mongoose

I have a Mongoose schema like so:
var accountSchema = new mongoose.Schema({
accountName: String,
accountType: { type: String, default: 'Individual Account' },
accountNumber: { type: String, unique: true },
accountActive: Boolean,
investment: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Investment'
}
]
})
module.exports = mongoose.model('Account', accountSchema);
And I will like to find the Account document that has a specific object id in the investment array.
"investment": [
{
"$oid": "5f353184c2daaf0f5c661ea7"
},
{
"$oid": "5f3531acc2daaf0f5c661ea9"
},
{
"$oid": "5f366e873d566938f81b94a8"
},
]
I have tried the code below but I keep getting an empty array:
Account.find().elemMatch('investment', { id: investment.id }).exec(function(err, account){
console.log(account)
});
What am I doing wrong? I expected elemMatch to work according to the mongoose document. On debug I see that the code is correctly translated to mongodb.
accounts find {"investment":{"$elemMatch":{"id":"5f3531acc2daaf0f5c661ea9"}}} {"projection":{}}
Can anyone help me out here please.
In the docs of $elemMatch, it says:
If you specify only a single condition in the $elemMatch expression, and are not using the $not or $ne operators inside of $elemMatch, $elemMatch can be omitted.
And in Query an Array for an Element:
To query if the array field contains at least one element with the specified value, use the filter { <field>: <value> } where <value> is the element value.
So in your case, you can simply do:
Account.find({investment: investment.id}).exec...
The find method retrieves entire documents. Using elemMatch as a query operator determines which document is selected from the collection. It does not change the contents of the array of sub-documents.
To limit the contents of the investment array element, use elemMatch as a projection operator.

Which type to store an Array of MongoDB ObjectIds?

I want to save an Array of ObjectIds which I get via an REST interface via mongoose to a mongodb. I constantly run into the problem that I get a cast error when I save the objectsIds from the REST interface to the DB. The serverside code is written in typescript.
The schema is:
var WaSchema = new mongoose.Schema({
ownerId: { type: 'String', required: true },
options: { type: 'String', required: false },
launch: [{ type : 'ObjectId', required: true }],
});
From the REST interface I get for "lanch" an Array of strings: launch: Array<string>
Here is how I currently do the save:
WaModel.findOneAndUpdate(query, {
ownerId: userId,
options: wa.options,
launch: wa.launch
},
{ upsert: true },
(err, doc) => {
if (err) throw err
else return 'successfully saved/updated';
})
How must the ObjectId in the REST interface needs to look like to be correctly casted? Is it just a sting like '575e52790c0fc76a11e381d0' or does it need a prefix like ObjectId("575e52790c0fc76a11e381d0")?
How would the Array look like at the end? This depends a liitle on the answer of #1
I saw the populate function, can this be of help here?
1) If you are using mongoose then no need to add prefix ObjectId. You can save as an array of reference Ids.
var insertData = {
ownerId: userId,
options: wa.options,
launch: [ '56cea954d82cd11004ee67b5','56ceaa00d82cd11004ee67bc' ]
}
2) At the end your array will look like this.
"launch" : [
ObjectId("56cea954d82cd11004ee67b5"),
ObjectId("56ceaa00d82cd11004ee67bc")
],
3) And Yes, populate function will be helpful here. It will populate whole array. After populate it will look like
"launch" : [
{ _id: '56cea954d82cd11004ee67b5',
.... other fields
},
{ _id: '56ceaa00d82cd11004ee67bc',
.... other fields
},
]
I would say store the object ids in array with datatype ObjectId. So to do that you should convert the id in string format that is '575e52790c0fc76a11e381d0' into type ObjectId.
Use the following to convert it:
Consider the id is of a user (userId)
var stringUserId = '575e52790c0fc76a11e381d0';
var ObjectId = mongoose.Types.ObjectId;
var userId = ObjectId(stringUserId); //This should assign a value of datatype ObjectId to userId

How to insert data into subdocument using $push in query, instead of retrieving doc and saving it back

Edit: this was actually working
As the Mongoose - Subdocs: "Adding subdocs" documentation says, we can add a subdoc using the push method (i.e. parent.children.push({ name: 'Liesl' });)
But I want to go further, and would like to use the $push operator to insert subdocuments.
I have two Schemas: the ThingSchema:
var ThingSchema = mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String
}
});
and the BoxSchema, the main document that has an array of subdocuments (things) of ThingSchema:
var BoxSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
description: {
type: String
},
things: {
type: [ThingSchema]
}
});
var BoxModel = mongoose.model('Box', BoxSchema);
I need every subdocument in things to have unique names - that is, that it would be impossible to insert a new document into this array that has a name value that already exists in the subdocs.
I'm trying to do something like:
var thingObj = ... // the 'thing' object to be inserted
BoxModel.update({
_id: some_box_id, // a valid 'box' ObjectId
"things.name": { "$ne": thingObj.name }
},
{
$push: { things: thingObj}
},
function(err) {
if (err) // handle err
...
});
but not getting any desired results.
What would be the correct way to add a ThingSchema subdocument into BoxSchema's thing array using the $push operator to do so in the query (must not add the subdoc if there's another subdoc named the same), instead of the Mongoose Docs way?
Edit: this is actually the issue
I made a mistake, the code above works as expected but now the problem I have is that when thingObj does not match the ThingSchema, an empty object is inserted into the things array:
// now thingObj is trash
var thingObj = { some: "trash", more: "trash" };
When executing the query given the above trash object, the following empty object is inserted into the subdocs array:
{ _id: ObjectId("an_obj_id") }
What I want this case, when the thingObj doesn't match the ThingSchema, is nothing to be added.
$addToSet adds something unique to the array (as in it checks for duplicates). But it only works for primitives.
What you should do is put things into their own collection and make a unique index on name. Then, make this change
things: {
type: [{type: ObjectId, ref: 'thingscollection'}]
}
this way you can do
BoxModel.update({
_id: some_box_id, // a valid 'box' ObjectId
"things": { "$ne": thingObj._id }
},
{
$addToSet: { things: thingObj._id}
},
function(err) {
if (err) // handle err
...
});
And when you fetch use .populate on things to get the full documents in there.
It's not exactly how you want it, but that's a design that might achieve what you're aiming for.

How to find documents by some conditions for its linked documents

I am a newby in MongoDB and I have a problem when querying a linked documents of the some documents collection.
Here is my database scheme:
var tagScheme = Schema({
name: { type: String, required: true }
});
tagScheme.index({ name: 1 }, { unique: true });
var linkScheme = Schema({
name: { type: String },
tags: [{ type: Schema.Types.ObjectId, ref: 'Tag' }]
});
linkScheme.index({ name: 1 }, { unique: true });
I need to get a count of the appropriate links for the specified tag. I try to execute the following query:
dbschemes.Link.find({ 'tags.name': specifiedTagName }, function (err, links) {
return res.send(500, err);
alert(links.length);
});
This query works not properly: it always returns an empty links list. Could someone exlain me what the problem is?
As JohnnyHK commented, the type of query you want to do is a relational type query and document database such as mongodb simply do not support them. Fix your schema to put that tag data directly in the link schema (nesting or "denormalizing" from a relational standpoint, which is OK in this case), then you can query it:
var LinkSchema = new Schema({
name: String,
tags: [String]
});
With that Schema, your query will work as you expect.
To address the comments below. This is a document database. It's not relational. There are trade-offs. Your data is de-normalized and it gives you some scalability and performance and you trade off flexibility of queries and data consistency to get them. If you wanted to rename a tag, a relatively rare occurrence, you'd have to do a whopping 2 database commands (a $push of the new name then a $pull of the old name) as opposed to relation where a single update command would do it.

How to sort array of embedded documents via Mongoose query?

I'm building a node.js application with Mongoose and have a problem related to sorting embedded documents. Here's the schema I use:
var locationSchema = new Schema({
lat: { type: String, required: true },
lon: { type: String, required: true },
time: { type: Date, required: true },
acc: { type: String }
})
var locationsSchema = new Schema({
userId: { type: ObjectId },
source: { type: ObjectId, required: true },
locations: [ locationSchema ]
});
I'd like to output the locations embedded in the userLocations documented sorted by their time attribute. I currently do the sorting in JavaScript after I retrieved the data from MongoDb like so:
function locationsDescendingTimeOrder(loc1, loc2) {
return loc2.time.getTime() - loc1.time.getTime()
}
LocationsModel.findOne({ userId: theUserId }, function(err, userLocations) {
userLocations.locations.sort(locationsDescendingTimeOrder).forEach(function(location) {
console.log('location: ' + location.time);
}
});
I did read about the sorting API provided by Mongoose but I couldn't figure out if it can be used for sorting arrays of embedded documents and if yes, if it is a sensible approach and how to apply it to this problem. Can anyone help me out here, please?
Thanks in advance and cheers,
Georg
You're doing it the right way, Georg. Your other options are either to sort locations by time upon embedding in the first place, or going the more traditional non-embedded route (or minimally embedded route so that you may be embedding an array of ids or something but you're actually querying the locations separately).
This also can be done using mongoose sort API as well.
LocationsModel.findOne({ userId: theUserId })
// .sort({ "locations.time": "desc" }) // option 1
.sort("-locations.time") // option 2
.exec((err, result) => {
// compute fetched data
})
Sort by field in nested array with Mongoose.js
More methods are mentioned in this answer as well
Sorting Options in mogoose
Mongoose Sort API

Resources