Closed Exegetech closed 9 years ago
I don't see anything wrong. Did you register the model for the schema, like mongoose.model('User', UserSchema);
? Does any other plugin work with this model? It would help if you post a full running code example so that I can run/debug.
Hi, Thanks for the prompt response.
So my full code was this
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var crypto = require('crypto');
var _ = require('lodash');
var extend = require('mongoose-schema-extend');
var deepPopulate = require('mongoose-deep-populate');
var authTypes = ['facebook', 'google', 'twitter'];
// Address Schemas
var AddressSchema = new Schema({
street1: String,
street2: String,
city: String,
state: String,
zip: String,
country: { type: String, default: 'US' }
});
// Review Schema
var ReviewSchema = new Schema({
reviewer: { type: Schema.Types.ObjectId, ref: 'User' },
text: String,
rating: { type: Number, min: 0, max: 5 },
status: {
type: String,
enum: ['created at', 'edited at'],
default: 'created at'
},
date: { type: Date, default: Date.now },
transaction: { type: Schema.Types.ObjectId, ref: 'Transaction'}
});
// Notification Schema
var NotificationSchema = new Schema({
message: String,
status: {
type: String,
enum: ['unread', 'read', 'discarded'],
default: 'unread',
required: true
},
date: { type: Date, default: Date.now }
});
// User Schema
var UserSchema = new Schema({
firstName: { type: String, trim: true },
lastName: { type: String, trim: true },
userName: { type: String, trim: true },
email: { type: String, lowercase: true, trim: true },
profilePicture: { type: String, lowercase: true, trim: true },
role: {
type: String,
default: 'user'
},
hashedPassword: String,
provider: String,
salt: String,
facebook: {},
google: {},
twitter: {},
phone: { type: String, trim: true },
shippingAddresses: [AddressSchema],
billingAddresses: [AddressSchema],
description: { type: String, trim: true },
blog: { type: String, trim: true },
followers: [{ type: Schema.Types.ObjectId, ref: 'User' }],
following: [{ type: Schema.Types.ObjectId, ref: 'User' }],
likes: [{ type: Schema.Types.ObjectId, ref: 'Item' }],
items: [{ type: Schema.Types.ObjectId, ref: 'Item' }],
reviews: [ReviewSchema],
billingInfo: {
stripeToken: String,
lastFour: Number,
cardType: String
},
transactions: [{ type: Schema.Types.ObjectId, ref: 'Transaction'}]
});
/**
* Plugins
*/
UserSchema.plugin(deepPopulate);
/**
* Virtuals
*/
UserSchema
.virtual('password')
.set(function(password) {
this._password = password;
this.salt = this.makeSalt();
this.hashedPassword = this.encryptPassword(password);
})
.get(function() {
return this._password;
});
// Public profile information
UserSchema
.virtual('profile')
.get(function() {
return {
'firstName': this.firstName,
'lastName': this.lastName,
'userName': this.userName,
'profilePicture': this.profilePicture,
'description': this.description,
'blog': this.blog
};
});
// Non-sensitive info we'll be putting in the token
UserSchema
.virtual('token')
.get(function() {
return {
'_id': this._id,
'role': this.role
};
});
/**
* Validations
*/
// Validate empty username
UserSchema
.path('userName')
.validate(function(userName) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return userName.length;
}, 'Username cannot be blank');
// Validate empty email
UserSchema
.path('email')
.validate(function(email) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return email.length;
}, 'Email cannot be blank');
// Validate empty password
UserSchema
.path('hashedPassword')
.validate(function(hashedPassword) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return hashedPassword.length;
}, 'Password cannot be blank');
// Validate username is not taken
UserSchema
.path('userName')
.validate(function(value, respond) {
var self = this;
this.constructor.findOne({userName: value}, function(err, user) {
if(err) throw err;
if(user) {
if(self.id === user.id) return respond(true);
return respond(false);
}
respond(true);
});
}, 'The specified username is already in use.');
// Validate email is not taken
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
this.constructor.findOne({email: value}, function(err, user) {
if(err) throw err;
if(user) {
if(self.id === user.id) return respond(true);
return respond(false);
}
respond(true);
});
}, 'The specified email address is already in use.');
var validatePresenceOf = function(value) {
return value && value.length;
};
/**
* Pre-save hook
*/
UserSchema
.pre('save', function(next) {
if (!this.isNew) return next();
if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1)
next(new Error('Invalid password'));
else
next();
});
var objIndexSearch = function(arr, obj, prop) {
return _.findIndex(arr, function(item) {
return item[prop].toString() === obj[prop].toString();
});
};
/**
* Methods
*/
UserSchema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* @param {String} plainText
* @return {Boolean}
* @api public
*/
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashedPassword;
},
/**
* Make salt
*
* @return {String}
* @api public
*/
makeSalt: function() {
return crypto.randomBytes(16).toString('base64');
},
/**
* Encrypt password
*
* @param {String} password
* @return {String}
* @api public
*/
encryptPassword: function(password) {
if (!password || !this.salt) return '';
var salt = new Buffer(this.salt, 'base64');
return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
},
/**
* Addresses
*/
addAddress: function(address, addressType, done) {
this[addressType + "es"].addToSet(address);
return this.save(done);
},
editAddress: function(address, addressType, done) {
var indexToEdit = objIndexSearch(this[addressType + "es"], address, '_id');
this[addressType + "es"].set(indexToEdit, address);
return this.save(done);
},
makeAddressPrimary: function(address, addressType, done) {
var indexToRemove = objIndexSearch(this[addressType + "es"], address, '_id');
var removedAddress = this[addressType + "es"].splice(indexToRemove, 1)[0];
this[addressType + "es"].unshift(removedAddress);
return this.save(done);
},
removeAddress: function(address, addressType, done) {
this[addressType + "es"].id(address._id).remove();
return this.save(done);
},
/**
* Reviews
*/
addReview: function(user, review, done) {
return this.model('User')
.findByIdAndUpdate(user._id, {
$addToSet: {
reviews: _.assign({
reviewer: this._id
}, review)
}
}, done);
},
editReview: function(user, review, done) {
return this.model('User')
.findOneAndUpdate({
_id: user._id,
"reviews.reviewer": this._id,
"reviews._id": review._id
}, {
$set: {
"reviews.$": review
}
}, done);
},
removeReview: function(user, review, done) {
return this.model('User')
.findByIdAndUpdate(user._id, {
$pull: {
reviews: {
reviewer: this._id,
_id: review._id
}
}
}, done);
},
/**
* Followers
*/
follow: function(user, done) {
var self = this;
this.following.addToSet(user._id);
return this.save(function(err, currentUser) {
if (err) return done(err);
self.model('User')
.findByIdAndUpdate(user._id, {
$addToSet: { followers: self._id }
}, function(err, user) {
done(err, currentUser);
});
});
},
unFollow: function(user, done) {
var self = this;
this.following.pull(user._id);
return this.save(function(err, currentUser) {
if (err) return done(err);
self.model('User')
.findByIdAndUpdate(user._id, {
$pull: { followers: self._id }
}, function(err, user) {
done(err, currentUser);
});
});
},
/**
* Like
*/
like: function(item, done) {
this.likes.addToSet(item._id);
return this.save(done);
},
unLike: function(item, done) {
this.likes.pull(item._id);
return this.save(done);
}
};
module.exports = mongoose.model('User', UserSchema);
Strangely, after I moved UserSchema.plugin(deepPopulate);
to the bottom of the file, hence
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var crypto = require('crypto');
var _ = require('lodash');
var extend = require('mongoose-schema-extend');
var deepPopulate = require('mongoose-deep-populate');
var authTypes = ['facebook', 'google', 'twitter'];
// Address Schemas
var AddressSchema = new Schema({
street1: String,
street2: String,
city: String,
state: String,
zip: String,
country: { type: String, default: 'US' }
});
// Review Schema
var ReviewSchema = new Schema({
reviewer: { type: Schema.Types.ObjectId, ref: 'User' },
text: String,
rating: { type: Number, min: 0, max: 5 },
status: {
type: String,
enum: ['created at', 'edited at'],
default: 'created at'
},
date: { type: Date, default: Date.now },
transaction: { type: Schema.Types.ObjectId, ref: 'Transaction'}
});
// Notification Schema
var NotificationSchema = new Schema({
message: String,
status: {
type: String,
enum: ['unread', 'read', 'discarded'],
default: 'unread',
required: true
},
date: { type: Date, default: Date.now }
});
// User Schema
var UserSchema = new Schema({
firstName: { type: String, trim: true },
lastName: { type: String, trim: true },
userName: { type: String, trim: true },
email: { type: String, lowercase: true, trim: true },
profilePicture: { type: String, lowercase: true, trim: true },
role: {
type: String,
default: 'user'
},
hashedPassword: String,
provider: String,
salt: String,
facebook: {},
google: {},
twitter: {},
phone: { type: String, trim: true },
shippingAddresses: [AddressSchema],
billingAddresses: [AddressSchema],
description: { type: String, trim: true },
blog: { type: String, trim: true },
followers: [{ type: Schema.Types.ObjectId, ref: 'User' }],
following: [{ type: Schema.Types.ObjectId, ref: 'User' }],
likes: [{ type: Schema.Types.ObjectId, ref: 'Item' }],
items: [{ type: Schema.Types.ObjectId, ref: 'Item' }],
reviews: [ReviewSchema],
billingInfo: {
stripeToken: String,
lastFour: Number,
cardType: String
},
transactions: [{ type: Schema.Types.ObjectId, ref: 'Transaction'}]
});
/**
* Virtuals
*/
UserSchema
.virtual('password')
.set(function(password) {
this._password = password;
this.salt = this.makeSalt();
this.hashedPassword = this.encryptPassword(password);
})
.get(function() {
return this._password;
});
// Public profile information
UserSchema
.virtual('profile')
.get(function() {
return {
'firstName': this.firstName,
'lastName': this.lastName,
'userName': this.userName,
'profilePicture': this.profilePicture,
'description': this.description,
'blog': this.blog
};
});
// Non-sensitive info we'll be putting in the token
UserSchema
.virtual('token')
.get(function() {
return {
'_id': this._id,
'role': this.role
};
});
/**
* Validations
*/
// Validate empty username
UserSchema
.path('userName')
.validate(function(userName) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return userName.length;
}, 'Username cannot be blank');
// Validate empty email
UserSchema
.path('email')
.validate(function(email) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return email.length;
}, 'Email cannot be blank');
// Validate empty password
UserSchema
.path('hashedPassword')
.validate(function(hashedPassword) {
if (authTypes.indexOf(this.provider) !== -1) return true;
return hashedPassword.length;
}, 'Password cannot be blank');
// Validate username is not taken
UserSchema
.path('userName')
.validate(function(value, respond) {
var self = this;
this.constructor.findOne({userName: value}, function(err, user) {
if(err) throw err;
if(user) {
if(self.id === user.id) return respond(true);
return respond(false);
}
respond(true);
});
}, 'The specified username is already in use.');
// Validate email is not taken
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
this.constructor.findOne({email: value}, function(err, user) {
if(err) throw err;
if(user) {
if(self.id === user.id) return respond(true);
return respond(false);
}
respond(true);
});
}, 'The specified email address is already in use.');
var validatePresenceOf = function(value) {
return value && value.length;
};
/**
* Pre-save hook
*/
UserSchema
.pre('save', function(next) {
if (!this.isNew) return next();
if (!validatePresenceOf(this.hashedPassword) && authTypes.indexOf(this.provider) === -1)
next(new Error('Invalid password'));
else
next();
});
var objIndexSearch = function(arr, obj, prop) {
return _.findIndex(arr, function(item) {
return item[prop].toString() === obj[prop].toString();
});
};
/**
* Methods
*/
UserSchema.methods = {
/**
* Authenticate - check if the passwords are the same
*
* @param {String} plainText
* @return {Boolean}
* @api public
*/
authenticate: function(plainText) {
return this.encryptPassword(plainText) === this.hashedPassword;
},
/**
* Make salt
*
* @return {String}
* @api public
*/
makeSalt: function() {
return crypto.randomBytes(16).toString('base64');
},
/**
* Encrypt password
*
* @param {String} password
* @return {String}
* @api public
*/
encryptPassword: function(password) {
if (!password || !this.salt) return '';
var salt = new Buffer(this.salt, 'base64');
return crypto.pbkdf2Sync(password, salt, 10000, 64).toString('base64');
},
/**
* Addresses
*/
addAddress: function(address, addressType, done) {
this[addressType + "es"].addToSet(address);
return this.save(done);
},
editAddress: function(address, addressType, done) {
var indexToEdit = objIndexSearch(this[addressType + "es"], address, '_id');
this[addressType + "es"].set(indexToEdit, address);
return this.save(done);
},
makeAddressPrimary: function(address, addressType, done) {
var indexToRemove = objIndexSearch(this[addressType + "es"], address, '_id');
var removedAddress = this[addressType + "es"].splice(indexToRemove, 1)[0];
this[addressType + "es"].unshift(removedAddress);
return this.save(done);
},
removeAddress: function(address, addressType, done) {
this[addressType + "es"].id(address._id).remove();
return this.save(done);
},
/**
* Reviews
*/
addReview: function(user, review, done) {
return this.model('User')
.findByIdAndUpdate(user._id, {
$addToSet: {
reviews: _.assign({
reviewer: this._id
}, review)
}
}, done);
},
editReview: function(user, review, done) {
return this.model('User')
.findOneAndUpdate({
_id: user._id,
"reviews.reviewer": this._id,
"reviews._id": review._id
}, {
$set: {
"reviews.$": review
}
}, done);
},
removeReview: function(user, review, done) {
return this.model('User')
.findByIdAndUpdate(user._id, {
$pull: {
reviews: {
reviewer: this._id,
_id: review._id
}
}
}, done);
},
/**
* Followers
*/
follow: function(user, done) {
var self = this;
this.following.addToSet(user._id);
return this.save(function(err, currentUser) {
if (err) return done(err);
self.model('User')
.findByIdAndUpdate(user._id, {
$addToSet: { followers: self._id }
}, function(err, user) {
done(err, currentUser);
});
});
},
unFollow: function(user, done) {
var self = this;
this.following.pull(user._id);
return this.save(function(err, currentUser) {
if (err) return done(err);
self.model('User')
.findByIdAndUpdate(user._id, {
$pull: { followers: self._id }
}, function(err, user) {
done(err, currentUser);
});
});
},
/**
* Like
*/
like: function(item, done) {
this.likes.addToSet(item._id);
return this.save(done);
},
unLike: function(item, done) {
this.likes.pull(item._id);
return this.save(done);
}
};
/**
* Plugins
*/
UserSchema.plugin(deepPopulate);
module.exports = mongoose.model('User', UserSchema);
It didnt throw any error, but the path, which is item.seller becomes null instead of being populated
So basically, UserSchema has property items: [ { type: Schema.Types.ObjectId, ref: 'Item'} ]
and ItemSchema has a property seller: [ { type: Schema.Types.ObjectId, ref: 'User'} ]
, and those two files are in separate files.
I made a repo example, please take a look.
I think I know what the bug is.
It does well with other deepPopulate as long as it does not refer back to the parent object. So in this code
var mongoose = require('mongoose');
var User = require('./user.model.js');
var Item = require('./item.model.js');
var Comment = require('./comment.model.js');
var util = require('util');
mongoose.connect('mongodb://localhost/deep-populate');
var userOne = new User({
userName: 'userOne'
});
var itemOne = new Item({
itemName: 'itemOne',
seller: userOne._id
});
var commentOne = new Comment({
commentName: 'commentOne'
});
Item.find({}).remove(function() {
User.find({}).remove(function() {
Comment.find({}).remove(function() {
commentOne.save(function(err, comment) {
itemOne.comments.addToSet(comment._id);
itemOne.save(function(err, item) {
userOne.items.addToSet(itemOne._id);
userOne.save(function(err, user) {
User
.findById(userOne._id)
.deepPopulate('items.seller items.comments')
.exec(function(err, user) {
console.log(JSON.stringify(user));
});
});
});
});
});
});
});
This is the result
{
"_id": "551156801e0b71782de10180",
"userName": "userOne",
"__v": 0,
"items": [{
"_id": "551156801e0b71782de10181",
"itemName": "itemOne",
"seller": null,
"__v": 0,
"comments": [{
"_id": "551156801e0b71782de10182",
"commentName": "commentOne",
"__v": 0
}]
}]
}
the seller
field is null
while the comments
field is populated
Fixed in 1.0.2
Thanks!
I got this error too.... add the code in schema can solve ur problem: ModelSchema.methods={ deepPopulate:function(){ deepPopulate() } }
@christiansakai
@jimmyyao88 if you're using v2.x, make sure you pass a mongoose
instance, i.e. `require('mongoose-deep-populate')(mongoose). There's no need to add the code you showed.
Thanks ! I did what u said ,but it turns the error: Error: Plugin was not installed , BUT my method can solve it ,I dont no y ,hahahha
Same as @jimmyyao88 , without note in methods throws Error: Plugin was not installed.
@jimmyyao88 @ijames07 please add a failed unit test case or show an executable non-working code.
151 passing (3s)
151 passing (3s)
All passed, what did you try to say?
I don't know unit testing, I just copied out result from it. This is my model causing troubles:
// USER.js - how is defined, if needed I will update it
"use strict";
var mongoose = require("mongoose"),
Schema = mongoose.Schema,
var deepPopulate = require('mongoose-deep-populate')(mongoose);
var userSchema = Schema({
warehouse: { type: Schema.Types.ObjectId, ref: "warehouse" },
...
});
userSchema.plugin(deepPopulate);
userSchema.methods = {
...
};
var User = mongoose.model("user", userSchema);
And there it throws error:
// PASSPORT.js - line where it throws error
var User = require("mongoose").model("user");
passport.use(new LocalStrategy(
function(username, password, done) {
User.findOne({ username: username })
.deepPopulate("warehouse warehouse_level" +
" warehouse_level.carrot warehouse_level.wheat" +
" warehouse_level.corn warehouse_level.hay" +
" warehouse_level.farm warehouse_level.ranch")
.exec(function(err, u) {
if (u && u.authenticate(password)) {
var bu = {
_id: u._id,
username: u.username,
roles: u.roles,
warehouse: u.warehouse,
warehouse_level: u.warehouse_level
};
return done(null, bu);
} else {
return done(null, false);
}
});
}
));
// AUTH.js - line where is error (calling this function causes it)
var auth = passport.authenticate("local", function (err, user) {
if (err) { return next(err); }
if (!user) { res.send({ success: false }); }
req.logIn(user, function (err) {
if (err) { return next(err); }
res.send({ success: true, user: user });
});
});
Full error code:
Error: Plugin was not installed
at Query.deepPopulate (C:\Users\James\Desktop\zoo\node_modules\mongoose-deep-populate\lib\plugin.js:29:13)
at Strategy._verify (C:\Users\James\Desktop\zoo\app\config\passport.js:12:6)
at Strategy.authenticate (C:\Users\James\Desktop\zoo\node_modules\passport-local\lib\strategy.js:90:12)
at attempt (C:\Users\James\Desktop\zoo\node_modules\passport\lib\middleware\authenticate.js:341:16)
at authenticate (C:\Users\James\Desktop\zoo\node_modules\passport\lib\middleware\authenticate.js:342:7)
at exports.authenticate (C:\Users\James\Desktop\zoo\app\config\auth.js:18:2)
at Layer.handle [as handle_request] (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\layer.js:95:5)
at next (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\route.js:131:13)
at Route.dispatch (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\route.js:112:3)
at Layer.handle [as handle_request] (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\layer.js:95:5)
at C:\Users\James\Desktop\zoo\node_modules\express\lib\router\index.js:277:22
at Function.process_params (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\index.js:330:12)
at next (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\index.js:271:10)
at serveStatic (C:\Users\James\Desktop\zoo\node_modules\express\node_modules\serve-static\index.js:74:16)
at Layer.handle [as handle_request] (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\layer.js:95:5)
at trim_prefix (C:\Users\James\Desktop\zoo\node_modules\express\lib\router\index.js:312:13)
Had this issue myself. The deepPopulate plugin integrates itself into the methods of the Model. Hence calling it after
userSchema.methods = {
...
};
will solve your problem. If you call it first, the deepPopulate method will get overwritten..
@ansgarm Thanks, this solved it for me
@ansgarm Thank you so much, you saved me. This should be documented.
Thanks, @ansgarm, you helped me solve the problem.
Hello,
I am trying to use mongoose-deep-populate, but got this error
My code is
Any ideas?