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

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.

Related

How to get document values while validating during a mongoose document update?

I'm having a problem while trying to run validations with mongoose.
I need to validate a value during an update to be sure that the new value is valid. In my case, I have two fields, a start and an end field, which means that while updating I need to check if the new end value is lower than the current start value.
My validation is
period: {
start: { type: Date, required: true, default: new Date() },
end: {
type: Date,
required: true,
validate: {
validator: function (v) {
return this.period.start < v;
},
message: props =>
`finish date ${props.value} is previous than start date!`,
},
},
},
This works fine during creation, but when you try to update it does not work at all. it says that period is null or undefined, if I add on findOneAndUpdate the options { runValidators: true, context: 'query' }, then this is a mongoose object, but I actually need to access data from the document that I'm trying to validate to check if the original value of start is not higher than the new value of end.
So I'm wondering how to get the current document during validation?
If for some reason this is impossible, how do I validate during an update? because I need to validate everything whenever it is creating and updating.
I thought about some "workaround" but I'm sure that mongoose has a correct way to do it.
You just can't.
After bumping into the same problem - mongodb doesn't support accessing the updated document during the update. Which means: if you want to use the document validation method during update, you can only do it without the other fields.
Which means, you can either validate independently without "this" keyword,
or validate in the update function, or use this walkaround I found:
Send the field/variable you need to verify inside your query(let say in the options object), which would look like:{ runValidators: true, context: 'query', start: whatYouNeed }. see docs for reference.
To sum it up:
validator function, inside your model definition:
validator:function(value){
if(value<this.options.period.start)do something
}
and the update call:
findByIdAndUpdate(id,updateObject,{{ runValidators: true, context: 'query', start:
whatYouNeed }})
edit: be careful not to use an arrow function inside the validator, because it will turn "this" into an empty object.

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

Mongo/MongooseJS Object model doesn't save correctly [duplicate]

I'm using Mongoose ODM to partially validate models before they are stored to MongoDB.
Is it possible to relax Mongoose schemata so that a given part of the document is not validated? I have tried to the following:
var MySchema = new Schema({
user_id: { type: Schema.ObjectId, ref: 'User' },
freeform_data: {},
});
For instance if I set the contents to:
{
user_id: '123456',
freeform_data: {
dataitem1: 'a',
dataitem2: 'b',
items: [ 1, 2, 3, 4 ]
}
}
Then only user_id is stored, which makes perfectly sense security-wise.
How can I disable mongoose's validation for this field?
I am using this application only for prototyping purposes so I don't care about security right now (I just want to prototype).
When you modify the contents of a Mixed field like freeform_data, you need to notify Mongoose that you've changed its value by calling markModified(path) on the modified document or a subsequent save() call won't save it.
For example:
user.freeform_data = { foo: 'bar' };
user.markModified('freeform_data');
user.save();
Mongeese
: a mongoose multi-database helper/hack module
https://github.com/donpark/mongeese
Disclaimer: I was looking to connect to two MongoDB instances in the same app and gave up. So I haven't tried it.

How to add a collection2 schema to users collection using accounts-password package in meteorjs?

So, I have just started a meteor project and have included the accounts-password package. The package only supports few keys. I want to add a new SimpleSchema to the users collection with some more fields.
I am not given to create another instance of users collection with
#users = Mongo.Collection('users');
//Error: A method named '/users/insert' is already defined
I can attach a schema but will be forced to keep alot of fields optional, and may not be able to register with the default package otherwise.
Can I add simpleSchema without making other fields optional and still be able to login properly?
Or is there any other work around for this case?
Thank you for help in advance
You can get users collection with:
#users = Meteor.users;
You can find nice example of defining user collection in the docs of collection2 package: https://atmospherejs.com/aldeed/collection2
Schema = {};
Schema.User = new SimpleSchema({
username: {
type: String,
regEx: /^[a-z0-9A-Z_]{3,15}$/
},
emails: {
type: [Object],
// this must be optional if you also use other login services like facebook,
// but if you use only accounts-password, then it can be required
optional: true
},
"emails.$.address": {
type: String,
regEx: SimpleSchema.RegEx.Email
},
"emails.$.verified": {
type: Boolean
},
createdAt: {
type: Date
},
profile: {
type: Schema.UserProfile,
optional: true
},
services: {
type: Object,
optional: true,
blackbox: true
},
// Add `roles` to your schema if you use the meteor-roles package.
// Option 1: Object type
// If you specify that type as Object, you must also specify the
// `Roles.GLOBAL_GROUP` group whenever you add a user to a role.
// Example:
// Roles.addUsersToRoles(userId, ["admin"], Roles.GLOBAL_GROUP);
// You can't mix and match adding with and without a group since
// you will fail validation in some cases.
roles: {
type: Object,
optional: true,
blackbox: true
},
// Option 2: [String] type
// If you are sure you will never need to use role groups, then
// you can specify [String] as the type
roles: {
type: [String],
optional: true
}
});
You have three ways to adapt for the attach of a schema to such a collection:
Make every new field optional.
Have default values (friends defaults to [] for example).
Update the UI to include new mandatory elements (a radio for "P = NP" or "P != NP").
Each option is somewhat valid in itself. Chose what seems the most logical in the current context and what will give you the least headaches.
Do you absolutely need a user-given value for someField when he registers? Then you have to update the UI to fetch this value.
Is the presence of someField important, and it can be initialized to a default object (an empty array, null, 0...)? Then a default value will fit, and it will be added when Collection2 cleans the document.
None of the above? Optional.
As a somewhat personal note, I prefer this kind of code:
someUser.friends.forEach(sendGifts);
To this kind:
if(someUser.hasOwnProperty('friends')) {//Or _.has(someUser, 'friends') but it sounds sad
someUser.friends.forEach(sendGifts);
}
In the second code friends is an optional field, so we're not sure if it's present or undefined. Calling forEach on undefined results in a nice big error, so we have to check for the field existence first... Thus, I would advise slightly avoiding optional fields for consistency and simplicity.

Mongoose findOneAndUpdate and upsert returns no errs, no documents affected

I have a very minimal model:
var CompanySchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
});
var Company = mongoose.model('Company', CompanySchema)
I am attempting to add a single document if it doesn't exist. Currently, there are no documents while I test:
models.Company.findOneAndUpdate({
name: 'companyName'
}, {upsert: true}, function(err, numberAffected, raw){
console.log(err, numberAffected, raw)
})
This is using the upsert options from the Mongoose docs
However err is null, numberAffected is null. Why isn't my document updated?
As of Mongoose 4+, do not forget to set new: true along with upsert or you will get the old document as a return value, not the updated one.
This is quite tricky especially when the request creates a document, as if you do not specify new: true, you receive a null document (there was no existing doc), but no error.
var myObj = ...;
collection.findOneAndUpdate(
{uniqueAttr: myObj.uniqueAttr},
myObj,
{upsert: true, new: true},
function(...) {...}
in your code you are using the 3 parameter version of the method findOneAndUpdate so, according to the documentation you posted, the parameters are: A.findOneAndUpdate(conditions, update, callback).
You should use the 4th parameters version of the method to specify the upsert option.
I would like to point out that I never used the framework mongoose. Hope this helps.
Edit:
Yes, in your case, conditions and update are the same. If your object is more complex then the one showed in the example, you might want to check for the _id or a "unique" (not guaranteed by MongoDB) attribute (better if it has an index on it). For example:
var myObj = ...;
collection.findOneAndUpdate({uniqueAttr: myObj.uniqueAttr}, myObj, {upsert: true}, function(){
...
});

Categories