Updating nested MongoDB document object - javascript

Hello there, a quick MongoDB mixed with some Discord knowledge question:
So currently, I want a formation for my MongoDB Document similar to the following:
channels: {
utility:{
suggestions: String
},
logging: {
main: String,
channel: {
channelCreate: String,
channelDelete: String,
channelUpdate: String,
},
role: {
roleCreate: String,
roleDelete: String,
roleUpdate: String,
}
}
This saves channel IDs so users can decide where each event will be logged. I have this set up in the schema and all good, but when I do findOneAndUpdate I don't know how to edit a single field; for example, let's say I want to edit roleDelete which is inside channels.logging.role how would I do that? because doing
await doc.updateOne({channels:{logging:{role:{roleDelete: IDHERE}}}});
It does not work. In fact, it screws everything up and replaces everything within channels to the value given, so how would I go around actually updating ONE value without messing with everything else? Thank you so much for your attention and participation.
This is using NodeJS Mongoose NPM Package btw.

you need to use $set operator. you can find details on https://docs.mongodb.com/manual/reference/operator/update/set/index.html
doc.updateOne({ _id: ID }, {
$set: {
channels.logging.role.roleDelete: IDHERE
}
}

So I solved this by doing the following:
await doc.updateOne({ 'channels.logging.role.roleDelete': IDHERE}, { new: true, upsert: true, setDefaultsOnInsert: true });
This updated the value if it existed and created it if it didn't exist. Using the above methods uses $set internally. (Read more here)
Feel free to ask if I didn't make myself clear

Related

How to pass unique values to mongodb updateMany $set

I am trying to create unique passwords for every document in a MongoDB collection. Although the function works, it creates the same password for each user.
Here is the code I am currently using:
function createPasswords(){
db.collection('users').updateMany({}, {$set:{password: GeneratePassword(12, false)}});
}
I expected the GeneratePassword function to be run for each document but it obviously only runs once as the result is the same random password for each user in the collection.
My question is, in this case, how might I create unique passwords for each user at once using updateMany.
Incidentally, the GeneratePassword function is not a custom function but instead calls the password-generator package.
Thanks in advance!
UPDATE
I tried the following code based on the answer by turivishal below.
db.collection('users').updateMany({},[{
$set: { updated: 'true',
password: {
$function: {
body: function() {
$function: {
function passGen(param1, param2) {
return GeneratePassword(param1,param2)
}
return passGen(12, false);
}
},
args: [],
lang: "js"
}
}
}
}]);
This produced the following error:
MongoServerError: Invalid $set :: caused by :: The body function must be specified.
If anyone can spot what is going wrong here I'd much appreciate the guidance.
The update() / updateMany() method in MongoDB is a single write operation, modifies multiple documents, the modification of each document is atomic. (we can not say the whole operation is atomic as per docs), but it will not update the different values in each document of collection.
You have to update it one by one or loop through or you can try an update with aggregation pipeline query starting from MongoDB 4.2, it allows for a more expressive update statement and $function starting from MongoDB 4.4, defines a custom aggregation function or expression in JavaScript.
I would suggest this query only if this is a one-time process because it will impact query speed and performance, and as per operator support, this query will support from MongoDB 4.4 or above versions.
db.collection('users').updateMany(
{},
[{
$set: {
password: {
$function: {
body: function() {
// write your generate password method here
function GeneratePassword(param1, param2) {
// ...
}
// this will call internal method and return password
return GeneratePassword(12, false);
},
args: [],
lang: "js"
}
}
}
}]
)
Playground

Mongoose create document in wrong key:value order according to schema

So I have a problem with understanding of how Mongo .create and .findAndUpdate operation works. I have mongoose 5.4.2X and a model with schema which has lot of key:value pairs (without any nested objects) in the exact order (in the code below I use 1. 2. 3. etc to show you the right order) like this:
let schema = new mongoose.Schema({
1.code: {
type: String,
required: true
},
2.realm: {
type: String,
required: true,
},
3.type: {
type: String,
required: true,
enum: ['D', 'M'],
},
4.open: Number,
5.open_size: Number,
6.key: typeof value,..
7...another one key: value like previous one,
8.VaR_size: Number,
9.date: {
type: Date,
default: Date.now,
required: true
}
});
and a class object which have absolutely the same properties in the same order like schema above.
When I form data for Mongo via const contract = new Class_name (data) and using console.log(contract) I have a necessary object with properties in the exact right order like:
Contract {1.code: XXX, 2.realm: YYY, 3.type: D, .... 8.VaR_size: 12, 9.date: 327438}
but when I'm trying to create/update document to the DB via findOneAndUpdate or (findByID) it writes in alphabetical order but not the necessary 1->9, for example:
_id:5ca3ed3f4a547d4e88ee55ec
1.code:"RVBD-02.J"
7.VaR:0.9
(not the 1->9)...:...
8.VaR_size:0.22
__v:0
5.avg:169921
The full code snippet for writing is:
let test = await contracts.findOneAndUpdate(
{
code: `${item_ticker}-${moment().subtract(1, 'day').format('DD.MMM')}` //how to find
},
contract, //document for writinng and options below
{
upsert : true,
new: true,
setDefaultsOnInsert: true,
runValidators: true,
lean: true
}
).exec();
Yes, I have read the mongoose docs and don't find any option param for
solving my problem or probably some optional params are matter but
there are no description for this.
It's not my first project, the funny thing is, that when I'm inserting
tons-of-docs via .insertMany docs are inserted according to schema
order (not alphabetical)
My only question is:
How do I fix it? Is there any option param or it's a part of findAnd....
operations? If there is not solution, what should I do if for me it's
necessary the right ordering and check existence of document before
inserting it?
Update #1 after some time I rephrase my search query for google and find a relevant question on SW here: MongoDB field order and document position change after update
Guess I found the right answer according to the link that I post earlier. So yes, it's part of MongoDB:
MongoDB allocates space for a new document based on a certain padding
factor. If your update increases the size of the document beyond the
size originally allocated the document will be moved to the end of the
collection. The same concept applies to fields in a document.
by #Bernie Hackett
But in this useful comment still no solution, right? Wrong. It seems that the only way to evade this situation is using additions optional params during Model.find stage. The same ordering using during project stage via .aggregate and it looks like this:
Model.find({query},{
"field_one":1,
"field_two":1,
.............
"field_last":1
});

createRecord with custom ID in emberjs 2.x and Firebase 3.x

Until now, I saved all my data with the following line of code:
saveUser(params) {
let newUser = this.store.createRecord('user', params);
newUser.save();
this.transitionTo('index');
This worked fine, but didn't allow for custom ID's in firebase, so I changed it into:
saveUser(params) {
let newUser = this.store.createRecord('user', {
id: params.loginId,
name: params.name,
nickname: params.nickname,
imageUrl: params.imageUrl,
email: params.email
});
newUser.save();
this.transitionTo('index');
Processes them exactly as I want them to be stored on the Firebase database, so no problem there. I'm wondering though, and not finding any solution on the web, how I can combine the two, so that I don't have to bind every param. It's bound to give problems when I add/remove model properties.
Something I'm looking for would look like this (pseudo, yes I tried it, didn't work!):
let newUser = this.store.createRecord('user', {id: params.loginId}, params);
In short, I'm looking for the dynamic properties of ('model', params), but with the option to manually adjust 1 (or more) records without having to type out all of the params.
Thanks in advance !
You will probably want to customize your serializer to accomplish this. The example in the docs is a good one, so it should be pretty straightforward: https://guides.emberjs.com/v2.13.0/models/customizing-serializers/
I am, of course, assuming you are using Ember Data for your models.

What does the context:'query' option do when using mongoose?

In a failed attempt learning exercise to get validators to work with 'document.update', I came across something I don't understand.
I know now that it doesn't work, but one of the things I tried was setting my options to {runValidators:true, context:'query'}. In my validator function, I tried console.logging (this), with and without the context:"query" option.
There was no difference. I received a large object (is this called the 'query object'?) This seems to go against what I read here.
In the color validation function above, this refers to the document being validated when using document validation. However, when running update validators, the document being updated may not be in the server's memory, so by default the value of this is not defined.
It was not undefined , even without the context option.
I even tried making it an arrow function to see if the lexical this was any different. In that case, this was undefined, but again, changing the context option did not make a difference. (I'm still learning, so I don't know if that part is relevant).
in the model:
let Property = mongoose.model('Property', {
name: {type:String, required:true},
occupancy: {type:String},
maxTenants: Number,
tenants: [{ type:mongoose.Schema.Types.ObjectId, ref: 'Tenant', validate: [checkMaxTenants, "Maximum tenants exceeded for this property. Tenant not added."]}]
});
function checkMaxTenants(val){
console.log("this",this);
// return this.tenants.length <= this.maxTenants;
return true;
}
and in the route:
property.update({$set: {tenants:property.tenants}},{new:true,runValidators:true,context:'query'}, function(err,savedProperty){
Anything to help me better understand the discrepancy between what I think I'm reading and what I see would be great!
At the outset, let's be clear that validators are of two types: document validators and update validators (maybe you know this already, but the snippet you posted updates a document, whereas the issue you mention relates to document validation upon save).
There was no difference. I received a large object (is this called the 'query object'?) This seems to go against what I read here.
Document validators are run when you run save on documents as mentioned in the docs.
Validation is middleware. Mongoose registers validation as a pre('save') hook on every schema by default.
Or you can call it manually with .validate()
You can manually run validation using doc.validate(callback) or doc.validateSync()
Update validators are run for update operations
In the above examples, you learned about document validation. Mongoose also supports validation for update() and findOneAndUpdate() operations.
This can be illustrated with the following snippet. For convenience I have changed the type of tenants to a simple integer array, but that shouldn't matter for the purpose of our discussion.
// "use strict";
const mongoose = require('mongoose');
const assert = require('assert');
const Schema = mongoose.Schema;
let Property = mongoose.model('Property', {
name: { type: String, required: true },
occupancy: { type:String },
maxTenants: Number,
tenants: [
{
type: Number,
ref: 'Tenant',
validate: {
validator: checkMaxTenants,
message: "Maximum tenants exceeded for this property. Tenant not added."
}
}
]
});
function checkMaxTenants (val) {
console.log("this", this);
// return this.tenants.length <= this.maxTenants;
return true;
}
mongoose.Promise = global.Promise;
mongoose.createConnection('mongodb://localhost/myapp', {
useMongoClient: true,
}).then(function(db) {
const property = new Property({ name: 'foo', occupancy: 'bar', tenants: [1] });
property.update(
{ $set: { tenants: [2, 3] } },
{
new: true,
runValidators: true,
// context: 'query'
},
function(err, savedProperty) {
}
)
// property.save();
});
Above code with trigger a update validation not document validation
To see document validation in action uncomment property.save() and comment the update operation.
You'll notice that the value of this will be the property document.
this { name: 'foo',
occupancy: 'bar',
_id: 598e9d72992907120a99a367,
tenants: [ 1 ] }
Comment the save, uncomment back the update operation and you'll see the large object you mentioned.
Now the large object you got, you may not have realised, is the global object when you didn't set context: 'query' and the query object when you set the context.
This can be explained at this line in mongoose's source. When no context was set, mongoose sets the scope to null. And then here the .call is called with the scope.
Now, in non strict mode, when .call is called with null, this is replaced with the global object. So check contents of the large object you got. When context is not set, it would be a global object and not the query object. You can add "use strict"; and see that null will be logged. (The snippet posted can verify this for you). You can verify that you got a query object by running instanceof mongoose.Query against this.
Hope this helps you understand things better.

SailsJS MongoDB store and access Array of Object

I'm trying to make a real-time vote application using SailsJS, and am currently having troubles with MongoDB. I am completely new to this, and have been just using SailsJS mimic easy calls to access MongoDB.
module.exports = {
attributes: {
selectOptions:{
type: 'Object'
},
question:{
type:'string',
required: true
},
password:{
type:'string',
required: true
}
}
};
The above code is for the model that I have, and I am the selectOptions should have an array of objects, like [{id: 1,result: 0},{id:2,result 0},...] and would like to know how to do this, as I cannot seem to find any documentation about the array of objects. Only thing I found was something about collection, or making another model and link that to the original model, but when i tried it, sails gave me some foreign key error that I have never faced before. I really really appreciate your time, and look forward for the response.
P.S - I tried making the array into either JSON or Object or nothing(Like not put any type under selectOptions) and made change to the model as well to see if it works, but both JSON and Object didn't work, but selectOptions did. However, I think it was returning a string, as it the length was longer than what the array was supposed to be.

Categories