Push element into nested array mongoose nodejs - node.js

I am trying to push a new element into an array, I use mongoose on my express/nodejs based api. Here is the code for mongoose:
Serie.updateOne({'seasons.episodes.videos._id': data._id}, {$push: {'seasons.episodes.videos.$.reports': data.details}},
function(err) {
if (err) {
res.status(500).send()
console.log(err)
}
else res.status(200).send()
})
as for my series models, it looks like this:
const serieSchema = new mongoose.Schema({
name: {type: String, unique:true, index: true, text: true},
customID: {type: Number, required: true, unique: true, index: true},
featured: {type: Boolean, default: false, index: true},
seasons: [{
number: Number,
episodes: [{number: Number, videos: [
{
_id: ObjectId,
provider: String,
reports: [{
title: {type: String, required: true},
description: String
}],
quality: {type: String, index: true, lowercase: true},
language: {type: String, index: true, lowercase: true},
}
]}]
}],
});
When I execute my code, I get MongoDB error code 16837, which says "cannot use the part (seasons of seasons.episodes.videos.0.reports) to traverse the element (my element here on json)"
I've tried many other queries to solve this problem but none worked, I hope someone could figure this out.

In your query you're using positional operator ($ sign) to localize one particular video by _id and then you want to push one item to reports.
The problem is that MongoDB doesn't know which video you're trying to update because the path you specified (seasons.episodes.videos.$.reports) contains two other arrays (seasons and episodes).
As documentation states you can't use this operator more than once
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
This limitation complicates your situation. You can still update your reports but you need to know exact indexes of outer arrays. So following update would be working example:
db.movies.update({'seasons.episodes.videos._id': data._id}, {$push: {'seasons.0.episodes.0.videos.$.reports': data.details}})
Alternatively you can update bigger part of this document in node.js or rethink your schema design keeping in mind technology limitations.

Related

MongoDB: How do you create an index on a field which is nested in two arrays?

I have a Mongo schema which essentially has two nested arrays, each of which contain objects. I would like to create an index on a field nested in that second level array. The schema looks as such:
const restaurantsSchema = new Schema({
restaurantName: { type: String, required: true },
dishes: [{
dishName: String,
price: Number,
description: String,
reviews: [{
reviewId: Number, **<<<< INDEX THIS FIELD**
userId: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true,
},
photoUrl: String,
caption: String,
createdAt: Number,
updatedAt: Number,
rating: Number,
reviewText: String,
}],
}],
});
The idea is that I can use this index to find and update a user's reviews. Is this possible with Mongo's multikey indexing or is there another way to do this? Any help would be appreciated.
I believe if you change the schema to
reviewId: {
type: Number,
unique: true
}
mongodb automatically creates an index because each value should be unique. That really the only qualification for creating any index.
write back if it works!

Mongoose validation value from another table

This is my Product table schema
let schema = new mongoose.Schema({
title: {type: String, required: true},
price: {type: Number, required: true},
description: {type: String, required: true},
sizes: {type: Object, required: true},
offer: {type: Number, default: 0},
images: {type: Array, required: true},
deal: {type: Boolean, default: false},
category: {
_id: {type: Schema.Types.ObjectId, required: true},
name: {type: String, required: true}
},
company_name: {type: String, required: true}
});
What I am trying to do
I am trying to validate if category.name value equal exist in my another table called Category.
You could use an async validator and query the categories collection. Something like this (using promise sytax for validator):
let schema = new mongoose.Schema({
title: {type: String, required: true},
price: {type: Number, required: true},
description: {type: String, required: true},
sizes: {type: Object, required: true},
offer: {type: Number, default: 0},
images: {type: Array, required: true},
deal: {type: Boolean, default: false},
category: {
_id: {type: Schema.Types.ObjectId, required: true},
name: {
type: String,
required: true,
validate: function(nameVal) {
return new Promise(function(resolve, reject) {
let Category = mongoose.model('Category'); //you would have to be sure that Category model is loaded before this one. One reason not to do this
Category.findOne({name: nameVal}, (err, cat) => resolve(cat ? true : false)); //any non null value means the category was in the categories collection
});
}
}
},
company_name: {type: String, required: true}
});
Some thoughts about this:
This is documented at http://mongoosejs.com/docs/validation.html#async-custom-validators.
As it states there, the validator is not run by default on updates. There are a lot of caveats to read through there.
In my experience with NoSQL DBs, the code creating a new Product would take care to make sure the category being assigned was valid. The code probably found the category from the DB at some point prior. So having a validator lookup would be redundant. But you may have a situation where you have a lot of code that creates Products and want validation in one place.
When I see that you are storing a document of {_id: ..., name: ...} as the category field in your Product schema, I think you might want this instead:
...
category: {Schema.Types.ObjectId, ref: 'Category'},
This allows you to store a reference to a category you have retrieved from the categories collection. Mongoose will load up the category document inline for you when you retrieve the products, if you use the populate method on your query. See http://mongoosejs.com/docs/populate.html. There are a lot of options with the populate functionality you might find useful. It does not do validation that the category is valid on save, as far as I know. But if you take this approach, you would have already looked up the category previously in the code before your save (see the link to get a better idea what I mean). Essentially this gives you join like behavior with MongoDB, with the storage savings and other benefits one expects from "normalization".

Use of $nin on embedded document

I have a schema like this in MongoDB:
var Customers = new Schema({
name: { type: String, trim: true, index: true, default: null, sparse: true },
facebookId: { type: String, default: null, trim: true, index: true },
friends: [friends]
});
var friends = new Schema({
customer: { type: Schema.ObjectId, ref: 'Customers', required: true },
lastGame: { type: Schema.ObjectId, ref: 'games', required: true, default: null },
lastGameTime: { type: Date, default: null }
});
Now in friends array I have reference of all the customer who are Facebook friend of the particular customer.
Now what I want to do is I have a screen where I want to show all the customers but there I don't want to those customers who are already my Facebook friends i.e for an example lets suppose I have 10 customer in total from 1 to 10, I am customer no 4 who have customer 1,3,5,6 in friends array so my result on the screen should be of user 2,7,8,9,10
I will really be thankful if someone can tell me the way to this stuff by using Query of MongoDb. I have searched and found $nin usage but that works in simple array. I don't get how I can implement this query on an embedded document as in the case of mine.
Look into $elemMatch. This will allow you to identify a specific document in an embedded document array
ElemMatch MongoDB documentation
You can also use $elemMatch in combination with $nin
I solved this issue by using $nin with a simple approach first of all I select all the friends of a particular customer in an array then by using $nin I filtered all the friends from the query and got my result.

Return Mongo document using Mongoose where subdocument does NOT exist?

Help! I'm losing my mind. I need to simply return a Mongo document, using Mongoose, IF a sub document does not exist.
My schemas:
var userSchema = new mongoose.Schema({
email: {type: String, unique: true, lowercase: true},
password: {type: String, select: false},
displayName: String,
picture: String,
facebook: String,
deactivation: deactiveSchema
});
var deactiveSchema = new mongoose.Schema({
when : { type: Date, default: Date.now, required: true },
who : { type: Schema.Types.ObjectId, required: true, ref: 'User' }
});
My goal is to lookup a user by their facebook ID if they have not been deactivated.
If they have been deactivated, then a deactivation subdocument will exist. Of course, to save space, if they are active then a deactivation will not exist.
On a side note, I'm also worried about how to properly construct the index on this logic.
I'd post snippets but every attempt has been wrong. =(
You can use $exists operator:
userSchema.find({deactivation:{$exists:false}}).exec(function(err,document){
});
or $ne:
userSchema.find({deactivation:{$ne:null}}).exec(function(err,document){
});
Since you are retiring data and not deleting, I'd go with one of two approaches:
Flag for retired (Recommended)
add to your schema:
retired: {
type: Boolean,
required: true
}
and add an index for this query:
userSchema.index({facebook: 1, retired: 1})
and query:
User.find({facebook: facebookId, retired: false}, callback)
Query for existence
User.find().exists("deactivation", false).exec(callback)
The latter will be slower, but if you really don't want to change anything, it will work. I'd recommend taking some time to read through the indexing section of the mongo docs.
Mongoose has many options for defining queries with conditions and a couple of styles for writing queries:
Condition object
var id = "somefacebookid";
var condition = {
facebook : id,
deactivation: { $exists : true}
};
user.findOne(condition, function (e, doc) {
// if not e, do something with doc
})
http://mongoosejs.com/docs/queries.html
Query builder
Alternatively, you may want to use the query builder syntax if you are looking for something closer to SQL. e.g.:
var id = "somefacebookid";
users
.find({ facebook : id }).
.where('deactivation').exists(false)
.limit(1)
.exec(callback);

mongodb using update to avoid race condition, update error when field is an empty array

here is my collection :
var schema = new mongoose.Schema({
id: {type: Number, ref: 'id'},
member_id: Number,
title: String,
content: String,
pros: String,
cons: String,
createdAt: {type: Date, default: Date.now},
vote: [{
member_id:{type: Number,unique: true},
support: Boolean,
voted: Boolean,
createdAt: {type: Date, default: Date.now}
}],
comment: [{
member_id: Number,
support: Boolean,
content:String,
createdAt: {type: Date, default: Date.now}
}]
});
This db is for record the votes from users. In server side, receive post to accumulate votes:
Topic.update({"id":id,"vote.member_id":member_id},
{$set:{ 'vote.$.member_id' : member_id, 'vote.$.support' : vote_to,'vote.$.voted' : true}} , { upsert: true }, function(err,test){
if (err) {
console.log(err);
return res.json({error: true});
}
else{
console.log(test);
res.json({msg:"success vote"});
}
But in the initial situation, in the other word, no entry in vote (vote is []), It receive an error message:
{ [MongoError: Cannot apply the positional operator without a corresponding query field containing an array.]
name: 'MongoError',
err: 'Cannot apply the positional operator without a corresponding query field containing an array.',
code: 16650,
n: 0,
connectionId: 133,
ok: 1 }
How can I fix this problem ?
According to the documentation It is not recommended to use the positional operator ($) in mongoDB with upsert...
http://docs.mongodb.org/manual/reference/operator/update/positional/
Do not use the positional operator $ with upsert operations because
inserts will use the $ as a field name in the inserted document.
So remove the upsert: true in your update command and see if it works.
I recommend that you check updated count in the callback of update and if it is zero then you can send another update request with $push in vote array.

Resources