Do you want to request a feature or report a bug?
bug
What is the current behavior?
When creating new father document with a nested child schema, the child doc is not created with it's defaults.
If the current behavior is a bug, please provide the steps to reproduce.
this is my hierarchy schema:
'use strict';
const mongoose = require('mongoose');
const dbConnection = require('./db-connection');
const ChildSchema = new mongoose.Schema(
{
_id: false,
child_name: {
type: String,
required: true,
default: 'kid 1'
}
}
);
const ParentSchema = new mongoose.Schema(
{
father_name: {
type: String,
required: true
},
child_schema: ChildSchema,
child_obj: {
child_name: {
type: String,
required: true,
default: 'kid 2'
}
}
},
{
minimize: false,
versionKey: false
}
);
module.exports = dbConnection.model('family', ParentSchema, 'hierarchy');
When creating a new father model, in the following way:
router.post(
'/create',
function (req, res, next) {
let newFather = new hierarchyModel({father_name: 'father 1'});
return newFather.save().then(
() => {
return res.end();
}
)
}
);
The father doc is created with the child_obj but but without the child_schema, like this:
{
"_id" : ObjectId("598c0b9c099b494fb568770d"),
"father_name" : "father 1",
"child_obj" : {
"child_name" : "kid 2"
}
}
What is the expected behavior?
I expect that when I create a new father module, its nested child schemas will be created with their defaults. should have been like this:
{
"_id" : ObjectId("598c0b9c099b494fb568770d"),
"father_name" : "father 1",
"child_schema" : {
"child_name" : "kid 1"
},
"child_obj" : {
"child_name" : "kid 2"
}
}
Please mention your node.js, mongoose and MongoDB version.
node.js - 6.11.2
mongoose - 4.11.6
MongoDB - 3.4
im not sure if this is a bug. In this case, you aren't creating the object that would have the default string child_name
=> kid 1
so mongoose doesnt automatically handle it for you. What do you think @vkarpov15 ?
here's a repro script to show what @tomgrossman is talking about:
const mongoose = require('mongoose');
const co = require('co');
mongoose.Promise = global.Promise;
const GITHUB_ISSUE = `gh-5537`;
exec()
.then(() => {
console.log('successfully ran program');
process.exit(0);
})
.catch(error => {
console.error(`Error: ${error}\n${error.stack}`);
});
function exec() {
return co(function* () {
const db = mongoose.createConnection(`mongodb://localhost:27017/${GITHUB_ISSUE}`)
const ChildSchema = new mongoose.Schema(
{
_id: false,
child_name: {
type: String,
required: true,
default: 'kid 1'
}
}
);
const ParentSchema = new mongoose.Schema(
{
father_name: {
type: String,
required: true
},
child_schema: ChildSchema,
child_obj: {
child_name: {
type: String,
required: true,
default: 'kid 2'
}
}
},
{
minimize: false,
versionKey: false
}
);
const Model = db.model('Model', ParentSchema);
const doc = yield Model.create({
father_name: 'father',
});
console.log('doc', doc); // does not have `child_schema: { child_name: 'kid 1' }
});
}
But I'm also not creating the object of kid 2. Why does it acting "better" if it's it just an object than a nested schema?
What should be the use case if I wanted nested single schemas to be created by default?
This is intentional behavior. The intent is to give people an alternative as to whether nested defaults get set or not. If you're using a nested object inline in the schema you'll get defaults, if that behavior is incorrect for your use case you can use a child schema. If you want child schema defaults, you should declare a default value for the child_schema
property:
const ParentSchema = new mongoose.Schema(
{
father_name: {
type: String,
required: true
},
// Mongoose will cast the empty object to match `ChildSchema`, so it will set defaults
child_schema: { type: ChildSchema, default: () => ({}) },
child_obj: {
child_name: {
type: String,
required: true,
default: 'kid 2'
}
}
},
{
minimize: false,
versionKey: false
}
);
But what if you want to have a default sub-document who already exist ?
The best use case for understand the useful request is for a User model who has one Role model document, and by default, the name: "Reader" one.
const RoleSchema = new Schema({
name: {
type: String,
trim: true,
default: 'Reader',
minLength: 4,
maxLength: 16,
unique: 'Role name already exists'
},
color: {
type: String,
enum: ['success', 'light', 'dark', 'primary', 'warning', 'danger', 'info', 'secondary'],
default: 'primary'
},
description: {
type: String,
trim: true,
required: 'Description is requested'
}
})
const UserSchema = new Schema({
email: {
type: String,
trim: true,
unique: 'Email already exists',
lowercase: true,
match: [/.+\@.+\..+/, 'Please, fill a valid email address'],
required: 'Email is required'
},
created: {
type: Date,
default: Date.now
},
validated: {
type: Boolean,
default: false
},
role: {
type: RoleSchema,
default: () => ({name:'Reader})
},
hashed_password: {
type: String,
required: 'Password is required'
},
salt: String
}, { collection: 'users' } )
(from mongo shell call)
> db.roles.find()
{ "_id" : ObjectId("5f43bc5c8e45604b5c04e261"), "color" : "danger", "name" : "Admin", "description" : "Admin has full super power there. He can manage everything in the web site.", "__v" : 0 }
{ "_id" : ObjectId("5f43bc5c8e45604b5c04e262"), "color" : "primary", "name" : "Reader", "description" : "Reader can read articles and subjects for subscribed users", "__v" : 0 }
{ "_id" : ObjectId("5f43be96576b7f4d97d984a1"), "color" : "warning", "name" : "Redactor", "description" : "Redactor role user can write articles on subjects", "__v" : 0 }
User.create time, there is an error. Mongoose doesn't want to let me to create a default existing Role for name: "Reader".
It should not be a bug, but i don't find in the official doc or anywhere how to achieve this useful behavior to just populate by default the sub-document existing Role of a new User to be created.
error Object { driver: true, name: "MongoError", index: 0, … }
driver true
name "MongoError"
index 0
code 11000
keyPattern Object { "role.name": 1 }
role.name 1
keyValue Object { "role.name": "Reader" }
role.name "Reader"
Let me know how to do that the best way...
role: {
type: RoleSchema,
default: (value) => { return (value) ? value : return Role.findOne({name: "Reader"}) }
}
},
First i never understand and rich to use a default role to be mounted at create time and from schema definition
there is sub-documents versus nested model by reference
But considering to achieve to
reduce redundancy,
be able to manage (CRUD) roles to be used by users
I've decided to use schema.object.id and references to a model from users schema:
role_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Role",
required: 'Role is required'
},
Hi @vkarpov15
thank you to reopen, but i also find that i got an error due to a wrong index created and never washed at schema update time.
consider to try it again because maybe it is working fine the way i did.
By the time, i trust about it is better to use object id related key instead of sub-document as if it was a relational database for the reasons i did explained.
The feature idea (if it doesn't exist) should be to get something in mongoose to just follow the object id related and the model used type to provide the sub-document (as if it was writing inside the same document) at find / findOne / findOneAndUpdate (and so on) time.
@jerome-diver I unfortunately do not understand your request at all. The error you mentioned excludes all relevant details. There's also no reason for you to do default: () => ({name:'Reader})
, you can do default: () => ({})
and Mongoose will set the nested defaults.
@vkarpov15, thank you very much to not precipitate yourself to close a thread when you don't understand something and to care about what the users said, it is very nice. I appreciate.
So, because you are interested to understand what i mean, let me try to do my best to explain the idea:
there is two way to create a document with a other nested one:
nested with an id relation (same as relational database does)
nested with sub-document inside (but then, it will have redundant data nested)
const UserSchema = new Schema({
username: {
type: String,
trim: true,
required: 'Username is required',
minLength: 4,
maxLength: 16,
unique: 'Username already exists'
},
role_id: {
type: mongoose.Schema.Types.ObjectId,
required: 'Role is required',
unique: false
},
}, { collection: 'users' } )
export default model('User', UserSchema)
In this situation of schema, if you have many users with the same role, you will have only redundant role_id (that will sure happen).
when you go for find a User model, you will get a username and a role_id
const UserSchema = new Schema({
username: {
type: String,
trim: true,
required: 'Username is required',
minLength: 4,
maxLength: 16,
unique: 'Username already exists'
},
role: {
type: RoleSchema,
default: (value) => {
if(!value) { return Role.findOne({name: "Reader"}) }
return value
}
}, { collection: 'users' } )
export default model('User', UserSchema)
now inside collection you will have redundant full RoleSchema
when you will find a User document, you will have username, role.name, role.id, role.description, role.color
The best should be to nested sub document with id only, but to get back User document with related nested Role inside.
Because you will no more have redundant sub-document (only their id, sure) but will access them easy at find time.
Do you understand ?
I think I understand, I think you're looking for virtual populate.
const UserSchema = new Schema({
username: {
type: String,
trim: true,
required: 'Username is required',
minLength: 4,
maxLength: 16,
unique: 'Username already exists'
},
role_id: {
type: mongoose.Schema.Types.ObjectId,
required: 'Role is required',
unique: false
},
}, { collection: 'users' } )
userSchema.virtual('role', {
ref: 'Role',
localField: 'role_id',
foreignField: '_id',
justOne: true
})
export default model('User', UserSchema)
This way, you can do User.find().populate('role')
when you want to get a user's associated role. If you want to automatically pull the user's role on all queries, you can use mongoose-autopopulate.
Does that help?
Most helpful comment
This is intentional behavior. The intent is to give people an alternative as to whether nested defaults get set or not. If you're using a nested object inline in the schema you'll get defaults, if that behavior is incorrect for your use case you can use a child schema. If you want child schema defaults, you should declare a default value for the
child_schema
property: