updating elements in nested arrays - mongodb

I think I know how to get to my object, I just can't figure out how to update it's values. I am getting this error:
{
"name": "MongoError",
"message": "cannot use the part (tasks of families.25.tasks.name) to traverse the element ({tasks: [ { name: \"Correct Name\", isSelected: false, completedBy: null, submittedBy: \"web-user\", completedOn: null, submittedOn: new Date(1505857352113), message: \"Please correct family name to follow HOK standard.\", assignedTo: \"konrad.sobon\", _id: ObjectId('59c18f8991d1929d43f22f8c') }, { name: \"Some other task\", isSelected: false, completedBy: null, submittedBy: \"web-user\", completedOn: null, submittedOn: new Date(1505917405948), message: \"Yet again, testing this.\", assignedTo: \"konrad.sobon\", _id: ObjectId('59c279fb8388cb58e7454bf6') } ]})",
"driver": true,
"index": 0,
"code": 16837,
"errmsg": "cannot use the part (tasks of families.25.tasks.name) to traverse the element ({tasks: [ { name: \"Correct Name\", isSelected: false, completedBy: null, submittedBy: \"web-user\", completedOn: null, submittedOn: new Date(1505857352113), message: \"Please correct family name to follow HOK standard.\", assignedTo: \"konrad.sobon\", _id: ObjectId('59c18f8991d1929d43f22f8c') }, { name: \"Some other task\", isSelected: false, completedBy: null, submittedBy: \"web-user\", completedOn: null, submittedOn: new Date(1505917405948), message: \"Yet again, testing this.\", assignedTo: \"konrad.sobon\", _id: ObjectId('59c279fb8388cb58e7454bf6') } ]})"
}
My collection looks like this:
My request handler looks like this:
module.exports.updateTask = function (req, res) {
var id = req.params.id;
var taskId = mongoose.Types.ObjectId(req.params.taskid);
Families
.update(
{ _id: id, 'families.tasks._id': taskId},
{ $set: {
'families.$.tasks.name': req.body.name,
'families.$.tasks.message': req.body.message,
'families.$.tasks.assignedTo': req.body.assignedTo}}, function(err, result){
if(err) {
res
.status(400)
.json(err);
} else {
res
.status(202)
.json(result);
}
}
)
};
Any help will be appreciated.

Unfortunately the positional operator $ cannot be used for nested arrays:
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.
https://docs.mongodb.com/manual/reference/operator/update/positional/
You might have to consider restructuring your documents.

Related

Duplicate error even though collection is empty

I try to insert multiple documents into my MongoDB collection, but whatever I do, I get a duplicate error. I made sure that there should be no duplicates possible by dropping the whole collection.
I tried it with .insertMany(), .save(), .create() - none of them do work. Though the docs get inserted, I still get the duplicate error 11000.
My function to insert the docs:
Words.prototype.addManyGeneralWordsEN = async function(words) {
await generalWordEN.create(words).then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err.code)
})
}
// add words to database
await this.addManyGeneralWordsEN(wordsToAdd)
My schema:
const generalWordENSchema = new mongoose.Schema(
{
german: {
type: String,
required: true
},
english: {
type: String,
required: true
},
partOfSpeech: {
required: true,
type: String
},
example: {
default: null,
type: String,
},
defintion: {
default: null,
type: String,
},
image: {
default: null,
type: String,
},
audio: {
default: null,
type: String,
},
level: {
default: null,
type: Number,
},
}
)
generalWordENSchema.index({ german: 1, english: 1, partOfSpeech: 1}, { unique: true })
module.exports = generalWordENSchema
My sample data:
[
{
"english": "clothes",
"german": "Kleidung",
"partOfSpeech": "noun",
"example": "My wife's wardrobe is filled with beautiful clothes.",
"definition": "",
"image": "",
"audio": "",
"level": ""
},
{
"english": "men's clothing",
"german": "Herrenbekleidung",
"partOfSpeech": "noun",
"example": "Men's clothing is on the second floor.",
"definition": "",
"image": "",
"audio": "",
"level": ""
}
]
The problem is probably on this line
generalWordENSchema.index({ german: 1, english: 1, partOfSpeech: 1}, { unique: true })
You created an index for the collection and used partOfSpeech as unique, but you have two documents with the same value noun.
It should work if you change it to:
generalWordENSchema.index({ german: 1, english: 1 }, { unique: true });
You also have a typo on the Schema declaration that might cause you different issues. You typed defintion instead of definition.

GraphQL nested document returns null on mutation

I am using MongoDB with Mongoose and GraphQL for a class project. I am stuck on an issue with GraphQL returning null on fields within a nested document reference (postedBy which references the User schema). I expect the fields to be populated by the referenced object data, but only the ID returns.
Model
const postSchema = new Schema(
{
postText: {
type: String,
required: 'You need add text to your post',
minlength: 1,
maxlength: 10000,
},
createdAt:{
type: Date,
default: Date.now,
get: createdAtVal => dateFormat(createdAtVal)
},
postedBy: {
type: Schema.Types.ObjectId, ref: "User",
required: true
},
comments: [commentSchema]
},
{
toJSON: {
virtuals: true,
getters: true
},
}
)
postSchema.virtual('commentCount').get(function() {
return this.comments.length;
});
const Post = model('Post', postSchema);
module.exports = Post;
TypeDef
type Post {
_id: ID
postText: String
createdAt: String
postedBy: User
comments: [Comment]
commentCount: Int
}
Resolver
addPost: async (parent, args, context) => {
if (context.user) {
const post = await Post.create({ ...args, postedBy: context.user._id });
await User.findByIdAndUpdate(
{ _id: context.user._id },
{ $push: { posts: post._id } },
{ new: true }
);
return post;
}
throw new AuthenticationError('You need to be logged in!');
}
I am able to successfully query the post and have the referenced field populated with the user's _id, username, and image(url). When I run the mutation, the username and image return null.
Here is my mutation:
mutation addPost($PostText: String!) {
addPost(postText: $postText) {
_id
postText
createdAt
postedBy {
_id
username
image
}
commentCount
comments {
_id
}
}
}
And here is the response it gets:
{
"data": {
"addPost": {
"_id": "60612871bd89e52ca08d3ea1",
"postText": "This is an example of a post.",
"createdAt": "Mar 28th, 2021 at 21:08 pm",
"postedBy": {
"_id": "6060a868d856f01738f45185",
"username": null,
"image": null
},
"commentCount": 0,
"comments": []
}
}
}
What am I doing wrong?
Since you're only getting the ref and no additional data, I think you just forgot to populate the user field.
Try:
return await post.populate('postedBy').execPopulate();

Mongoose $push add to document

I'm trying to add an element to an existing array, but it produces an error:
The field 'data' must be an array but is of type object in document
Scheme:
const testScheme = new Schema({
user: {
type: String,
required: true
},
data: [{
platform: {
type: String,
required: true
},
item_name: {
type: String,
required: true
},
price: {
type: Number,
default: 0
},
updatedAt: Date
}]
}, {
versionKey: false,
timestamps: true
});
Document in mongodb:
"data": [{
"price": 50,
"_id": "5a84268d6c78a60c10479437",
"platform": "pl1",
"item_name": "test"
}],
"_id": "5a841bccb44cb8cd5b974d71",
"user": "Ivan",
"updatedAt": "2018-02-14T12:07:41.793Z",
"createdAt": "2018-02-14T11:21:48.104Z"
Query:
var item = {
"platform": "pl700",
"item_name": "someText",
"price": 700,
"updatedAt": new Date()
};
Data.findOneAndUpdate({
'user': 'Ivan'
}, {
$push: {
'data': item
}
}, {
safe: true,
upsert: true
},
function(err, data) {
if (err) return res.status(500).send({
'error': err
});
res.status(200).send({
'data': data
});
}
);
I trying query with $set parametr and it works, but $push, $addToSet didn't work for me. Also i tried to google this problem and can't solve it.
It is not clear what you are intending to do.
To push an item into array you use $addToSet/$push. For updating a array you use $set.
Using $set you can update the whole document or you can update the specific field.
Update whole doc
Data.findOneAndUpdate({
'user': 'Ivan',
'data._id':item._id
}, {
$set: {
'data.$': item
}
}...
)
Update specific field
Data.findOneAndUpdate({
'user': 'Ivan',
'data._id':item._id
}, {
$set: {
'data.$.price': item.price
}
}...
)

getting Readable { .. } instead of specific collection using find() on mongodb

I have a collection named 'EloVars' on my mongodb, with only one document:
{
"_id": {
"$oid": "5800f3bfdcba0f48d2c58161"
},
"nextPID": "0",
"TotalComprasions": "0"
}
I'm trying to get the value of nextPID this way:
var myDoc = db.collection('EloVars').find();
if(myDoc) {
console.log('What exactly am I getting here:')
console.log(myDoc)
req.body.pid = myDoc.nextPID;
}
When I look at the console i noticed that what I'm getting is not 'EloVars' collection... just weired long Readable:
Readable {
pool: null,
server: null,
disconnectHandler:
{ s: { storedOps: [], storeOptions: [Object], topology: [Object] },
length: [Getter] },
bson: {},
ns: 'mydb.EloVars',
cmd:
{ find: 'mydb.EloVars',
limit: 0,
skip: 0,
query: {},
slaveOk: true,
readPreference: { preference: 'primary', tags: undefined, options: [Object] } },
options:
.....
.....
What is this readable and why am I getting it?
find() returns a cursor. You have to iterate the cursor to get the documents.
var cursor = db.collection('EloVars').find();
cursor.each(function(err, doc) {
console.log(doc);
});
Or you can convert it to an array to get the documents.
cursor.toArray(function(err, doc){
console.log(doc);
});
Why are you trying in this way? if there is not any specific reason and you just wanted to get "nextPID" the you can use below query:
db.collection('EloVars').findOne({},{_id:0, nextPID:1}).exec(function(err, doc) {
if(doc) {
console.log('What exactly am I getting here:')
console.log(myDoc)
req.body.pid = doc.nextPID;
}
})
P.S.: it'll get only one nextPID.
to get all:
db.collection('EloVars').find({},{_id:0, nextPID:1}).exec(function(err, docs) {
if(docs && docs.length){
// your code here
}
})

auto increment ids in mongoose

How do I have autoincrement ids in mongoose? I want my ids to start like 1, 2, 3, 4, not the weird id numbers mongodb creates for you?
Here's my schema:
var PortfolioSchema = mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Use mongoose-auto-increment:
https://github.com/codetunnel/mongoose-auto-increment
var mongoose = require('mongoose');
var autoIncrement = require('mongoose-auto-increment');
var connection = ....;
autoIncrement.initialize(connection);
var PortfolioSchema = new mongoose.Schema({
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio' });
module.exports = mongoose.model('Portfolio', PortfolioSchema);
Or if you prefer to use an additional field instead of overriding _id, just add the field and list it in the auto-increment initialization:
var PortfolioSchema = new mongoose.Schema({
portfolioId: {type: Number, required: true},
url: String,
createTime: { type: Date, default: Date.now },
updateTime: { type: Date, default: Date.now },
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
//Auto-increment
PortfolioSchema.plugin(autoIncrement.plugin, { model: 'Portfolio', field: 'portfolioId' });
If you want to have a incrementing numeric value in _id then the basic process is you are going to need something to return that value from a store somewhere. One way to do this is use MongoDB itself to store data that holds the counters for the _id values for each collection, which is described within the manual itself under Create and Auto-Incrementing Sequence Field.
Then as you create each new item, you use the implemented function to get that "counter" value, and use it as the _id in your document.
When overriding the default behavior here, mongoose requires that you both specify the _id and it's type explicitly with something like _id: Number and also that you tell it to no longer automatically try to supply an ObjectId type with { "_id": false } as an option on the schema.
Here's a working example in practice:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var counterSchema = new Schema({
"_id": String,
"counter": { "type": Number, "default": 1 }
},{ "_id": false });
counterSchema.statics.getNewId = function(key,callback) {
return this.findByIdAndUpdate(key,
{ "$inc": { "counter": 1 } },
{ "upsert": true, "new": true },
callback
);
};
var sampleSchema = new Schema({
"_id": Number,
"name": String
},{ "_id": false });
var Counter = mongoose.model( 'Counter', counterSchema ),
ModelA = mongoose.model( 'ModelA', sampleSchema ),
ModelB = mongoose.model( 'ModelB', sampleSchema );
async.series(
[
function(callback) {
async.each([Counter,ModelA,ModelB],function(model,callback) {
model.remove({},callback);
},callback);
},
function(callback) {
async.eachSeries(
[
{ "model": "ModelA", "name": "bill" },
{ "model": "ModelB", "name": "apple" },
{ "model": "ModelA", "name": "ted" },
{ "model": "ModelB", "name": "oranage" }
],
function(item,callback) {
async.waterfall(
[
function(callback) {
Counter.getNewId(item.model,callback);
},
function(counter,callback) {
mongoose.model(item.model).findByIdAndUpdate(
counter.counter,
{ "$set": { "name": item.name } },
{ "upsert": true, "new": true },
function(err,doc) {
console.log(doc);
callback(err);
}
);
}
],
callback
);
},
callback
);
},
function(callback) {
Counter.find().exec(function(err,result) {
console.log(result);
callback(err);
});
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
For convience this implements a static method on the model as .getNewId() which just descriptively wraps the main function used in .findByIdAndUpdate(). This is a form of .findAndModify() as mentioned in the manual page section.
The purpose of this is that it is going to look up a specific "key" ( actually again the _id ) in the Counter model collection and perform an operation to both "increment" the counter value for that key and return the modified document. This is also aided with the "upsert" option, since if no document yet exists for the requested "key", then it will be created, otherwise the value will be incremented via $inc, and it always is so the default will be 1.
The example here shows that two counters are being maintained independently:
{ _id: 1, name: 'bill', __v: 0 }
{ _id: 1, name: 'apple', __v: 0 }
{ _id: 2, name: 'ted', __v: 0 }
{ _id: 2, name: 'oranage', __v: 0 }
[ { _id: 'ModelA', __v: 0, counter: 2 },
{ _id: 'ModelB', __v: 0, counter: 2 } ]
First listing out each document as it is created and then displaying the end state of the "counters" collection which holds the last used values for each key that was requested.
Also note those "weird numbers" serves a specific purpose of always being guranteed to be unique and also always increasing in order. And note that they do so without requiring another trip to the database in order to safely store and use an incremented number. So that should be well worth considering.