I would like to implement some validation logic within a child document, that validation is related to the state of a field of its parent. My code looks like:
const childSchema = function (parent) {
return new Schema({
name: {
type: String,
set: function (v) {
const prefix = (parent.isFlashed) ? 'with' : 'without'
return `${prefix} ${v}`
}
},
version: String
})
}
const parentSchema = new Schema({
isFlashed: Boolean,
firmware: childSchema(this)
})
I'm wondering why my code doesn't work and how can I check the value of a property of the parent schema inside my child schema.
Thanks in advance
You don't need to define your child schema as a function that returns a new Schema. You just reference the child schema in the parent.
const ChildSchema = new Schema({
name: {
type: String,
set: function(v) {
const prefix = this.parent().isFlashed ? 'with' : 'without';
return `${prefix} ${v}`;
}
}
});
const ParentSchema = new Schema({
isFlashed: Boolean,
firmware: ChildSchema
});
You'll notice that I reference the parent object as a function of this: this.parent().
When you're creating a new parent object with a child, you just use a nested object that matches the format of the child schema.
const Parent = mongoose.model('Parent', ParentSchema);
const parent = new Parent({isFlashed: false, firmware: {name: "ChildName"}});
Related
I have a model in expressjs that has a property which is an instance of another model, basically a nested model, like this:
// nested.js
var NestedPropDef = {
someProp: { type: String, default: '' },
};
var schema = new Schema(NestedPropDef, { minimize: false });
schema.statics = {
getInstance() {
var nestedObject = new NestedProp();
nestedObject.someProp = 'something';
console.log('[first log] value of nestedObject:', nestedObject);
return nestedObject;
},
});
var NestedProp = mongoose.model('NestedProp', schema);
exports.NestedPropDef = NestedPropDef;
exports.NestedProp = NestedProp;
// parent-file.js
var NestedPropDef = require('./nested').NestedPropDef;
var NestedProp = require('./nested').NestedProp;
var schema = new Schema({
otherProp: { type: String, default: '' },
nestedProp: NestedPropDef,
});
schema.methods.updateNestedProp = function (data, callback) {
this.nestedProp = NestedProp.getInstance();
console.log('[second log] value of nestedProp:', this.nestedProp);
this.otherProp: data.otherProp;
console.log('[third log] value of this:', this);
this.save(callback);
});
var Parent = mongoose.model('Parent', schema);
module.exports = Parent;
Each of those console.log statements yield the following:
[first log] value of nestedObject: {
someProp: 'something'
}
So I know the instance of the nested property is being created correctly.
[second log] value of nestedProp: {}
I don't understand why this is an empty object. For some reason, the value returned by .getInstance is not saved to this.nestedProp.
[third log] value of this: {
otherProp: 'some value'
}
I don't understand why nestedProp is completely missing from this object.
So basically I can't figure out why the return value from the static method of the nested object is not getting used by the parent model. Any ideas will be very welcome, thanks!
Update: it appears that the bug is linked to using this JWT library, though I don't know why. I don't use the library in any routes/code related to this problem. I think they're linked because the bug goes away when I revert to the commit before I installed the JWT library.
Update 2: the JWT lib was actually unrelated to the issue, but the problem was introduced with a recent change to mongoose. As of mongoose#5.9.24, the log statements would be as follows (and this is what I want):
[first log] value of nestedObject: {
someProp: 'something'
}
[second log] value of nestedProp: {
someProp: 'something'
}
[third log] value of this: {
otherProp: 'some value',
nestedProp: {
someProp: 'something'
}
}
I know that I can export the schema of NestedProp instead of its definition, and that would mostly fix the issue, but it adds the _id field to nestedProp and that wasn't happening before. In other words, doing this:
// nested.js
...
exports.NestedPropSchema = schema;
exports.NestedProp = NestedProp;
// parent-file.js
var NestedPropSchema = require('./nested').NestedPropSchema;
var NestedProp = require('./nested').NestedProp;
var schema = new Schema({
otherProp: { type: String, default: '' },
nestedProp: NestedPropSchema,
});
...
causes this:
[third log] value of this: {
otherProp: 'some value',
nestedProp: {
someProp: 'something',
_id: ...
}
}
I know there's a way to disable the automatic addition of the _id, but my objective is to restore the previous functionality so that other subtle bugs don't creep up on me. How can I restore the results I was seeing in 5.9.24? Or was I always nesting incorrectly (and Mongoose was just working with my incorrect nesting)?
I am trying to populate subfields of a document, which are not defined as refs. The problem is that mongoose keeps returning null, whenever I try to fetch the document and populate the fields.
I will try to make this a generic question. I haven't found an answer anywhere online.
schemaA:
const schemaA = new Schema({
before: {
type: Object,
default: {}
},
after: {
type: Object,
default: {}
}
});
module.exports = SchemaA = mongoose.model("schemaA", schemaA);
schemaB:
const schemaB = new Schema({
someField: {
subFieldA: {
type: String
},
subFieldB: {
type: String
}
}
});
module.exports = SchemaB = mongoose.model("schemaB", schemaB);
And an example document that would exist in schemaA is:
_id: ObjectId('5e4ab79d9d3ce8633aedf524')
before: {
someField: {
subFieldA: ObjectId('5e4ab74f9d3ce8633aedf2eb'),
subFieldB: ObjectId('5e4ab74f9d3ce8633aedf2ep')
},
}
after: {
someField: {
subFieldA: ObjectId('5e4ab74f9d4ce8633aedf2eb'),
subFieldB: ObjectId('5e4ab74f9d3ce8639aedf2ep')
},
}
date: 2020-02-17T15:56:13.340+00:00
My query:
const schemaAs = await SchemaA.find()
.populate(
"before.someField.subFieldA, before.someField.subFieldB, after.someField.subFieldA, after.someField.subFieldB"
)
But this query returns null. What am I doing wrong?
You are looking for Dynamic References.
This lets you set what collection you are referencing as a property to each individual document, instead of hard coding it to one specific collection.
As far as I know, it is not possible to populate a property without any reference.
Is it possible to have a Mongoose Schema that resembles the following:
var categorySchema = new Schema({
name : String
});
var childSchema = new Schema({
name : String,
category : {
type : Schema.Types.ObjectId,
ref : 'parent.categories'
}
});
var parentSchema = new Schema({
categories : [categorySchema],
children : [childSchema]
});
Basically a child can only have a category that is contained by its parent. Is what I am trying to do possible? If not what is the cleanest way to do this?
If there is only one field name in categorySchema, maybe you could just put it into parentSchema without population as below,
var childSchema = new Schema({
name : String,
category : {
name: String
}
});
var parentSchema = new Schema({
categories : [{name: String}],
children : [childSchema]
});
When try to insert new child into parent, you could query the parent firstly, then iterate categories to get existing one and add it to children, save the parent as last, sample codes as below
Parent.find({_id: parent._id})
.exec(function(err, p) {
if (err) throw err;
var p = new Child({name: 'tt'});
p.categories.forEach(function(c) {
if (c /*find the match one*/) {
p.category = c; // assign the existing category to children
}
});
// save this parent
p.save(function(err) {...});
});
If there are many fields in categorySchema, maybe define it as individual schema could be one option, in case of there are many categories in Parent to make parent collection too large.
var categorySchema = new Schema({
name : String,
// other fields....
});
var Category = mongoose.model('Category', categorySchema);
var childSchema = new Schema({
name : String,
category : {type : Schema.Types.ObjectId, ref : 'Category'}
});
var parentSchema = new Schema({
categories : [{type : Schema.Types.ObjectId, ref : 'Category'}],
children : [childSchema]
});
The same logic when try to add new children to parent document as above.
Say that my Schema is:
var fooSchema = new Schema({
foo: String
});
and I want to add select: false to foo. How would I do that? Without doing the following:
var fooSchema = new Schema({
foo: { type: String, select: false }
});
Can I do fooSchema.<somethingToAddSelectFalseToFoo>?
I assume that this would work: Mongoose schema.set() function: http://mongoosejs.com/docs/api.html#schema_Schema-set.
Schema#set(key, [value])
Sets/gets a schema option.
Parameters:
key <String> option name
[value] <Object> if not passed, the current option value is returned
fooSchema.set('foo', { select: false });
In a select query you could exclude it, i.e. fooSchema.find().select("-foo"). Some examples are at https://stackoverflow.com/a/12097874/442472.
Here's my code:
var userSchema = new mongoose.Schema({
email: String,
password: String,
role: Something
});
My goal is to define the role property to have specific values ('admin', 'member', 'guest' and so on..), what's the better way to achieve this? Thanks in advance!
You can do enum.
var userSchema = new mongoose.Schema({
// ...
, role: { type: String, enum: ['admin', 'guest'] }
}
var user = new User({
// ...
, role: 'admin'
});
There isn't really a way that I know of to have specific values possible for role, but maybe you'd like to create multiple object types based off of a master object type, each with their own roles (and anything else you want to distinguish). For example...
var userSchema = function userSchema() {};
userSchema.prototype = {
email: String,
password: String,
role: undefined
}
var member = function member() {};
member.prototype = new userSchema();
member.prototype.role = 'member';
var notSupposedToBeUsed = new userSchema();
var billTheMember = new member();
console.log(notSupposedToBeUsed.role); // undefined
console.log(billTheMember.role); // member
Another possibility is have userSchema with a constructor that easily allows you to select one of the built in values. An example...
var userSchema = function userSchema(role) {
this.role = this.role[role];
// Gets the value in userSchema.role based off of the parameter
};
userSchema.prototype = {
email: String,
password: String,
role: { admin: 'admin', member: 'member', guest: 'guest' }
}
var a = new userSchema('admin');
var b = new userSchema('blah');
console.log(a.role); // 'admin'
console.log(b.role); // undefined
More: http://pivotallabs.com/users/pjaros/blog/articles/1368-javascript-constructors-prototypes-and-the-new-keyword