MongoDB and dynamic query building with $push statement with node.js - node.js

I'm creating an application where I need to keep version of each field in database when update is performed. So I figured that I would use statement like this to accomplish it.
collection.update({ _id: id },
{
$push: { placeholder : { $each: [{ data: keyValue, timeStamp: date, editor: editorID}] } }
},
The placeholder is a field which might be different each time depending which field I'm updating in the table.
var key = req.body.key;
var keyValue = req.body.keyValue;
var editorID = req.body.editor;
var date = new Date();
The above variables are initialized based on the form submitted and the key is the name of the database filed which I would like to use instead of placeholder.
How can I make the placeholder to represent database field name passed from the form?

I found the solution.
var query = {};
query[key] = { data: keyValue, timeStamp: date, editor: editorID };
and then
collection.update({ _id: id },
{
$push:query
}
},

Related

Only update specific values using updateOne $set in mongoose

I want to only update two values inside todo in the Mongoose model below
const userSchema = new mongoose.Schema({
name: String,
email: String,
phone: Number,
points: Number,
todo: {
}
})
The todo actually contains 11 values and I only want to update 2 values at a time which I am getting from the frontend
let {
t,
i
} = req.body.delitem;
await User.updateOne({
username: req.body.user
}, {
$set: {
todo: {
[t]: "",
[i]: ""
}
}
})
but what is happening is these two fields are getting set to blank strings but the remaining fields are also changing to NULL whereas I want the other fields to stay the same.
Use dot notation like this:
await User.updateOne({username:req.body.user},
{
"$set": {
"todo.t": "",
"todo.i": ""
}
})
Example here
If you use an object into $set you are telling mongo to replace the object by the new one.
I believe your issue is that you're using $set and passing a whole object which overwrites the whole todo with your t and i fields and you've enforced required on those other fields.
If you see the docs in mongo for $set you can instead update specific fields in subobjects. The following snippet should be more appropriate.
let {t,i} = req.body.delitem;
await User.updateOne({username:req.body.user}, {
$set: {
"todo.t": "",
"todo.i": ""
}
})

Mongoose overwriting data in MongoDB with default values in Subdocuments

I currently have a problem with updating data in MongoDB via mongoose. I have a nested Document of the following structure
const someSchema:Schema = new mongoose.Schema({
Title: String,
Subdocuments: [{
SomeValue: String
Position: {
X: {type: Number, default: 0},
Y: {type: Number, default: 0},
Z: {type: Number, default: 0}
}
}]
});
Now my problem is that I am updating this with findOneAndUpdateById. I have previously set the position to values other than the default. I want to update leaving the position as is by making my request without the Position as my frontend should never update it (another application does).
However the following call
const updateById = async (Id: string, NewDoc: DocClass) => {
let doc: DocClass | null = await DocumentModel.findOneAndUpdate(
{ _id: Id },
{ $set: NewDoc },
{ new: true, runValidators: true });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
return doc;
}
Now this works fine if I don't send a Title for the value in the root of the schema (also if i turn on default values for that Title) but if I leave out the Position in the Subdocument it gets reset to the default values X:0, Y:0, Z:0.
Any ideas how I could fix this and don't set the default values on update?
Why don't you find the document by id, update the new values, then save it?
const updateById = async (Id: string, NewDoc: Training) => {
const doc: Training | null = await TrainingModel.findById({ _id: Id });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
doc.title = NewDoc.title;
doc.subdocument.someValue = NewDoc.subdocument.someValue
await doc.save();
return doc;
}
check out the link on how to update a document with Mongoose
https://mongoosejs.com/docs/documents.html#updating
Ok after I gave this some thought over the weekend I got to the conclusion that the behaviour of mongodb was correct.
Why?
I am passing a document and a query to the database. MongoDb then searches Documents with that query. It will update all Fields for which a value was supplied. If for Title I set a new string, the Title will get replaced with that one, a number with that one and so on. Now for my Subdocument I am passing an array. And as there is no query, the correct behavioud is that that field will get set to the array. So the subdocuments are not updated but indeed initialized. Which will correctly cause the default values to be set. If I just want to update the subdocuments this is not the correct way
How to do it right
For me the ideal way is to seperate the logic and create a seperate endpoint to update the subdocuments with their own query. So to update all given subdocuments the function would look something like this
const updateSubdocumentsById= async ({ Id, Subdocuments}: { Id: string; Subdocuments: Subdocument[]; }): Promise<Subdocument[]> => {
let updatedSubdocuments:Subdocument[] = [];
for (let doc of Subdocuments){
// Create the setter
let set = {};
for (let key of Object.keys(doc)){
set[`Subdocument.$.${key}`] = doc[key];
}
// Update the subdocument
let updatedDocument: Document| null = await DocumentModel.findOneAndUpdate(
{"_id": Id, "Subdocuments._id": doc._id},
{
"$set" : set
},
{ new : true}
);
// Aggregate and return the updated Subdocuments
if(updatedDocument){
let updatedSubdocument:Subdocument = updatedTraining.Subdocuments.filter((a: Subdocument) => a._id.toString() === doc._id)[0];
if(updatedSubdocument) updatedSubdocuments.push(updatedSubdocument);
}
}
return updatedSubdocuments;
}
Been struggling with this myself all evening. Just worked out a really simple solution that as far as I can see works perfectly.
const venue = await Venue.findById(_id)
venue.name = name
venue.venueContact = venueContact
venue.address.line1 = line1 || venue.address.line1
venue.address.line2 = line2 || venue.address.line2
venue.address.city = city || venue.address.city
venue.address.county = county || venue.address.county
venue.address.postCode = postCode || venue.address.postCode
venue.address.country = country || venue.address.country
venue.save()
res.send(venue)
The result of this is any keys that don't receive a new value will just be replaced by the original values.

How to apply function for all Collection

I want to update my collection in server.js by using a function.
When I change one field I need to change multiple collections.
My question is how can I use a parameter as a Collection name. Is there any way for it or I must write a function for each Collection?
update: function(personID,option) {
return Personel.update(
{ id: personID },
{ $set: option },
{ multi: true }
);
},
I want to apply this logic for separate collections.
There is a trickier workaround for this problem. you need to actually bind all of your collection in a single object.
CollectionList = {};
CollectionList.Personel = new Mongo.Collection('personel');
CollectionList.secondCollection = new Mongo.Collection('second');
after that you pass as your collection name as a string into the method.
update: function(collectionName,personID,option){
return CollectionList[collectionName].update(
//..rest of your code
);
You can try this approach:
var Personel = new Mongo.Collection('personel');
var Items = new Mongo.Collection('items');
var SomeOtherCollection = new Mongo.Collection('someOtherCollection');
....
update: function(personID, option, collectionName) {
// Choose collection by given name
var Collection = {
Personel: Personel,
Items: Items,
SomeOtherCollection: SomeOtherCollection
}[collectionName];
return Collection.update(
{ id: personID },
{ $set: option },
{ multi: true }
);
},

How can i use an if statement in a mongoose query?

i have a mongodb collection named Feed and it has an attribute named "type". according to that string, i want to send a changeable fields with json. For example if type is "photo" i want to do somethig like that
schema.find({number: "123456"},"body number",
function(err, data) {
but if the string is story, instead of photo; İn the same 'schema.find' query,it should create a json with "body url" instead of "body number". and they all should be passed with the same json.
res.json(data);
For a clear example, i want my json to be like this. as you se the fields change according to "type". but they are all actually in the same collection.
[
{
type: 'photo',
number: 123456,
url: 'asd.jpg',
},
{
type: 'story',
body: 'hello',
number: 123456,
}
]
So basically you want to return certain documents fields from the Feed collection, which are specified in a variable like e.g. "firstName pic points photos".
Are there Feed documents with the story field?
The Model.find() does not create any schema.
Maybe edit with further code so we can understand the command.
For document-specific JSON formatting like this, you can override the default toJSON method of your Feed model as shown in this gist.
UPDATE
If you want this sort of flexibility in your documents then it's even easier. Just define your schema to include all possible fields and then only set the fields that apply to given document for its type. The fields that you don't use won't appear in the document (or in the JSON response). So your schema would look like:
var feedSchema = new Schema({
type: { type: 'String' },
venue: Number,
url: String,
body: String
});
Take a look to mongoose-schema-extend. Using the 'Discriminator Key' feature, you can instruct .find() to create the proper model in each individual case.
Your code should look like this (not tested):
var feedSchema = new Schema({
venue: Number,
}, {discriminatorKey : 'type' }});
var photoSchema = feedSchema.extend({
url: String
});
var storySchema = feedSchema.extend({
body: String
});
var Feed= mongoose.model('feed', feedSchema );
var Photo= mongoose.model('photo', photoSchema );
var Story= mongoose.model('story', storySchema );
//'photo' and 'story' will be the values for 'type' key
Feed.find({venue: "123456"}, function(err, models) {
console.log(models[0] instanceof Photo); // true
console.log(models[0] instanceof Story); // false
console.log(models[1] instanceof Photo); // false
console.log(models[1] instanceof Story); // true
});

Add document to an embedded document array

I'm trying to add an embedded document to an existing document field. I found one fitting answer with the search but I'm running into errors. I'm using node.js, Express and Mongoose.
My database schemas:
var entry = new Schema({
name : { type : String, required : true},
description : { type : String, default: ""},
});
var compo = new Schema({
name : String,
description : String,
entries : [entry]
});
And I'm trying to update the entries array with the following code
var entry = new entryModel();
entry.name = "new name";
entry.description= "new description";
compoModel.findOne(query, function (err, item) {
if (item) {
item.entries.push(entry);
item.save(function (err) {
if (!err) {
log.debug('Entry added successfully.');
} else {
log.error("Mongoose couldn't save entry: " + err);
}
});
}
});
It yields an error: TypeError: Object.keys called on non-object
What have I missed?
So I managed to get it working via the Model.update method by simply adding a new object to the compo.entries list and calling compoModel.update.
My similar issue (same error) was solved by clearing the sub-document array. It was populated prior to the definition of the sub-document scheme. At least this is what i think happened.
E.g.:
var token = new Schema( { value: String, expires: Date } )
var user = new Schema( { username: String, tokens: [token] } )
.. and, prior to defining the 'token' scheme i had entries such as:
{ username: 'foo', tokens: ['123456'] }
.. so, clearing tokens did it for me.
user.tokens = []
user.save()

Resources