How to apply function for all Collection - node.js

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 }
);
},

Related

Using updateOne method to update an object field inside array - throws error "Cannot create field 'url' in element"

I have MongoDB database (with Mongoose) containing a collection of Products (among others), which looks like this:
[
{
name: 'Product A',
url: 'product-a',
category: 'accesory',
price: 12,
shortDescription: ['example description'],
technicalSpecs: [{ speed: 10, weight: 20 }],
images: [],
reviews: [],
relatedProducts: [
{
url: 'product-b',
name: 'Product B',
// to be added in Update query
//id: id_of_related_product
}
]
} /* other Product objects */
]
As every MongoDB document is provided with _id property by default, but within the relatedProducts array i only have url and name properties, i want to add the id property (associated with corresponding Product) for each object in the relatedProducts array, so i will be able to conveniently query and process those related products.
I came up with an idea to query all Products to get only those, which have non-empty relatedProducts array. Then i loop them and i search for Product model, which has specific url and name properties - this let's me get it's true (added by MongoDB) _id. At the end i want to add this _id to matching object inside relatedProducts array.
My code:
async function assignIDsToRelatedProducts(/* Model constructor */ Product) {
const productsWithRelatedOnes = await Product.find(
{ relatedProducts: { $ne: [] }}, ['relatedProducts', 'name', 'url']
);
for (const productItem of productsWithRelatedOnes) {
for (const relatedProduct of productItem.relatedProducts) {
const product = await Product.findOne(
{ url: relatedProduct.url, name: relatedProduct.name },
'_id'
);
// throws error
await productItem.updateOne(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': product._id } }
);
}
}
}
However it throws the following error:
MongoError: Cannot create field 'url' in element {relatedProducts: [ /* array's objects here */ ]}
I don't know why MongoDB tries to create field 'url', as i use it to project/query url field (not create it) in updateOne method. How to fix this?
And - as i am newbie to MongoDB - is there a simpler way of achieving my goal? I feel that those two nested for..of loops are unnecessary, or even preceding creation of productsWithRelatedOnes variable is.
Is it possible to do with Mongoose Virtuals? I have tried it, but i couldn't match virtual property within the same Product Model - attach it to each object in relatedProducts array - after calling .execPopulate i received either an empty array or undefined (i am aware i should post at-the-time code of using Virtual, but for now i switched to above solution).
Although i didn't find solution or even reason of my problem, i solved it with a slightly other approach:
async function assignIDsToRelatedProducts(Product) {
const productsHavingRelatedProducts = Product.find({ relatedProducts: { $ne: [] }});
for await (const withRelated of productsHavingRelatedProducts) {
for (const relatedProductToUpdate of withRelated.relatedProducts) {
const relatedProduct = await Product
.findOne(
{ url: relatedProductToUpdate.url, name: relatedProductToUpdate.name },
['url', '_id']
);
await Product.updateMany(
{ 'relatedProducts.url': relatedProduct.url },
{ $set: { 'relatedProducts.$.id': relatedProduct._id } }
);
}
}
const amountOfAllProducts = await Product.find({}).countDocuments();
const amountOfRelatedProductsWithID = await Product
.find({ 'relatedProducts.id': { $exists: true } }).countDocuments();
console.log('All done?', amountOfAllProducts === amountOfRelatedProductsWithID);
}
Yet, i still suppose it can be done more concisely, without the initial looping. Hopefully somebody will suggest better solution. :)

mongoose filter by multiple conditions and execute to update data

I am wondering what would be the best approach to make schema functions using mongoose. I have never used this so the way I think is somewhat limited, same goes for looking for docs, without knowing what's available, is not very efficient.
Through docs I found that either using findOneAndUpdate might solve the problem; but there are some constraints.
Here is the code I am planning to run:
models/Bookmark.js
const mongoose = require('mongoose')
const bookmarkItemSchema = new mongoose.Schema({
restaurantId: String,
cachedAttr: {
name: String,
latitude: Number,
longitude: Number,
},
})
const bookmarkListSchema = new mongoose.Schema({
listName: String,
items: [bookmarkItemSchema],
})
const bookmarkSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
lists: [bookmarkListSchema],
})
// const add = (lists, userId) => {
// let bookmark = Bookmark.findOne({userId})
// bookmark.lists.listName === lists.listName //current, new
// ? bookmark.lists.items.push(lists.items)
// : bookmark.lists.push(lists)
// return bookmark
// }
mongoose.model('Bookmark', bookmarkSchema)
Routes/bookmark.js
router.post('/bookmarks', async (req, res) => {
const {lists} = req.body
console.log(lists)
if (!lists) {
return res.status(422).send({error: 'You must provide lists'})
}
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
try {
// const bookmark = Bookmark.add(lists, req.user._id, obj)
// await bookmark.save()
// res.send(bookmark)
let bookmark = Bookmark.findOne({"userId": req.user._id})
if (bookmark.lists.listName === lists.listName){ // THIS IS UNDEFINED. How to get this object?
let item = lists.items
bookmark.lists.items.push(item)
await bookmark.save()
res.send(bookmark)
}
} catch (e) {
res.status(422).send({error: e.message})
}
})
The req.body looks like this:
{
"lists": {
"listName": "My Saved List",
"items": {
"restaurantId": "abcdefg",
"cachedAttr": {
"name": "abcdefg",
"latitude": 200,
"longitude": 200
}
}
}
}
Basically what I commented out in the models/Bookmark.js file is what I would really like to do.
If the userId's list name already exists, then I would like to just add an item to the list.
Otherwise, I would like to add a new list to the object.
What is the best approach for doing this? Is there a straight forward mongoose api that I could use for this problem? or do I need to make two separated function that would handle each case and make that as schema methods and handle it in the routes file?

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 change key Name in mongoDB [duplicate]

Assuming I have a collection in MongoDB with 5000 records, each containing something similar to:
{
"occupation":"Doctor",
"name": {
"first":"Jimmy",
"additional":"Smith"
}
Is there an easy way to rename the field "additional" to "last" in all documents? I saw the $rename operator in the documentation but I'm not really clear on how to specify a subfield.
You can use:
db.foo.update({}, {$rename:{"name.additional":"name.last"}}, false, true);
Or to just update the docs which contain the property:
db.foo.update({"name.additional": {$exists: true}}, {$rename:{"name.additional":"name.last"}}, false, true);
The false, true in the method above are: { upsert:false, multi:true }. You need the multi:true to update all your records.
Or you can use the former way:
remap = function (x) {
if (x.additional){
db.foo.update({_id:x._id}, {$set:{"name.last":x.name.additional}, $unset:{"name.additional":1}});
}
}
db.foo.find().forEach(remap);
In MongoDB 3.2 you can also use
db.students.updateMany( {}, { $rename: { "oldname": "newname" } } )
The general syntax of this is
db.collection.updateMany(filter, update, options)
https://docs.mongodb.com/manual/reference/method/db.collection.updateMany/
You can use the $rename field update operator:
db.collection.update(
{},
{ $rename: { 'name.additional': 'name.last' } },
{ multi: true }
)
If ever you need to do the same thing with mongoid:
Model.all.rename(:old_field, :new_field)
UPDATE
There is change in the syntax in monogoid 4.0.0:
Model.all.rename(old_field: :new_field)
Anyone could potentially use this command to rename a field from the collection (By not using any _id):
dbName.collectionName.update({}, {$rename:{"oldFieldName":"newFieldName"}}, false, true);
see FYI
I am using ,Mongo 3.4.0
The $rename operator updates the name of a field and has the following form:
{$rename: { <field1>: <newName1>, <field2>: <newName2>, ... } }
for e.g
db.getCollection('user').update( { _id: 1 }, { $rename: { 'fname': 'FirstName', 'lname': 'LastName' } } )
The new field name must differ from the existing field name. To specify a in an embedded document, use dot notation.
This operation renames the field nmae to name for all documents in the collection:
db.getCollection('user').updateMany( {}, { $rename: { "add": "Address" } } )
db.getCollection('user').update({}, {$rename:{"name.first":"name.FirstName"}}, false, true);
In the method above false, true are: { upsert:false, multi:true }.To update all your records, You need the multi:true.
Rename a Field in an Embedded Document
db.getCollection('user').update( { _id: 1 }, { $rename: { "name.first": "name.fname" } } )
use link : https://docs.mongodb.com/manual/reference/operator/update/rename/
This nodejs code just do that , as #Felix Yan mentioned former way seems to work just fine , i had some issues with other snipets hope this helps.
This will rename column "oldColumnName" to be "newColumnName" of table "documents"
var MongoClient = require('mongodb').MongoClient
, assert = require('assert');
// Connection URL
//var url = 'mongodb://localhost:27017/myproject';
var url = 'mongodb://myuser:mypwd#myserver.cloud.com:portNumber/databasename';
// Use connect method to connect to the server
MongoClient.connect(url, function(err, db) {
assert.equal(null, err);
console.log("Connected successfully to server");
renameDBColumn(db, function() {
db.close();
});
});
//
// This function should be used for renaming a field for all documents
//
var renameDBColumn = function(db, callback) {
// Get the documents collection
console.log("renaming database column of table documents");
//use the former way:
remap = function (x) {
if (x.oldColumnName){
db.collection('documents').update({_id:x._id}, {$set:{"newColumnName":x.oldColumnName}, $unset:{"oldColumnName":1}});
}
}
db.collection('documents').find().forEach(remap);
console.log("db table documents remap successfully!");
}
If you are using MongoMapper, this works:
Access.collection.update( {}, { '$rename' => { 'location' => 'location_info' } }, :multi => true )

MongoDB and dynamic query building with $push statement with 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
}
},

Resources