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.
Related
I'm using Joi library to validate input from users and mongoose schema to validate data sent to Mongodb?
I find myself using the spread operator after validating the input to quickly store the data in a mongoose data model, like this:
validate(req.body.inputObject);
//...
const newData = {...req.body.inputObject } /// spread operator here!!!
//...
if /* required additional data */ {
newData.more = {
_id: someId,
value: someValue,
}
}
//...
const data = Data.findByIdAndUpdate(req.params.id, newData, { new: true });
// check and report error, otherwise
res.send(data);
I know that mongoose stores only the properties found in the Schema of an object in the database. Therefore, any additional data passed by the user that is not in the schema is dropped. The alternative, as I see it, is something like this:
validate(req.body.inputObject);
//...
const newData = {
value1: req.body.inputObject.value1,
value2: req.body.inputObject.value2,
...
valueN: req.body.inputObject.valueN,
}
//...
if /* require additional data */ {
newData.more = {
_id: someId,
value: someValue,
}
}
//...
const data = Data.findByIdAndUpdate(req.params.id, newData, { new: true });
// check and report error, otherwise
res.send(data);
The spread operator approach is flexible because I do not need to know/guess what values in the data object the user wants to update. Better, I dont have to assume that the user will provided values for all the properties of the object including the modifications they want to make. This way, users can send just the updated properties. This is not the case for the second approach.
My question is: are there any down sides or security concerns for using the spread operator in situations like this?
Is it bad to rely on mongoose dropping properties not found in the schema?
Is there a better approach that is both flexible and secure?
Or, should I stick with the second approach and expect users to send all the properties along with updates?
Thanks.
I´m using MongoDB and mongoose for a project that needs to track data creation and changes. The requirements are that I need to keep track of records creation and changes including the date/time and the application user (not the OS user) who did it.
I´ve seen the mongoose timestamps option that would solve my date/time issue, but is there a way to extend it to include extra properties, where I´m gonna be adding the application username ?
If not, is there a single place where I can write a function that will be called on every creation/update so that I can include/modify these fields ?
Today I´m insering these properties on every model like below, but I would like to move all of them to a single place.
var companySchema = mongoose.Schema({
name: {
type: String,
index: true
},
phone: {
type: String
},
deleted: {
type: Boolean
},
createdAt: {
type: Date
},
createdBy: {
type: String
},
updatedAt: {
type: Date
},
updatedBy: {
type: String
}
});
How would be the best approach for it ?
I would approach it by creating two models, one for each Data created, one for each Data Changes.
Data created which will have 6 fields one is createdBy, createdAt, and one will be a field with an array of reference id of Data Changes, deletedBy, deletedAt, deletedFlag.
Data Changes will have fields dataID which will have reference id of data created, updatedBy and updatedAt.
This way it will be easy to keep track of when it was created when it was changes and when it was deleted.
PS: You can remove either of Array in Data created model or dataID ref id in Data Change mode, it was just me being extra cautious while binding.
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.
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.
I am trying to attach the full user model to each comment in my comments section. Since mongodb doesn't have joins I have been trying to figure out how to use .populate() to add the user object from the controller. As I understand it, to use the populate function there are only two things you must do.
Define a ref in the model.
Call .populate() on the name of the field which has a ref defined.
I have defined the ref in the model to the 'User' model:
var commentSchema = new mongoose.Schema({
author: { type: String, ref: 'User' },
description: String,
commentID: String,
hasParent: String,
parentComment: String,
timestamp: { type: Number, default: 0 },
});
Then I am trying to add the user model to the author field in the comment object:
Comment
.find()
.populate('author')
.exec(function (err, comments) {
console.log(comments);
});
Is there a step that I am missing? I was hoping to see all the comments with the full user object in the author field of each comment.
The solution was deleting the comments collection from mongodb. Starting fresh it works as expected. If anyone runs into a similar problem try deleting the collection of objects that were created before you had added a ref to the model.
Please consider making following changes.
author: { type: String, ref: 'User' };
authot: { type: Schema.Types.ObjectId, ref:'User'};