I have "Video" mongoose schema that references an "owner" which is a reference to a "User" schema. I am using the Angular-Fullstack Yeoman Generator
video.model.js:
'use strict';
// load the things we need
var mongoose = require('mongoose'), Schema = mongoose.Schema;
var videoSchema = mongoose.Schema({
type : { type: String, enum: ['youtube', 'vimeo', 'local'], required: true },
owner : { type: String, ref: 'User', required : true },
date : { type: Date, default: Date.now },
name : { type: String, required: true },
sourcemedia : { type: String, required: true, unique: true}
});
// keep our virtual properties when objects are queried
videoSchema.set('toJSON', { virtuals: true });
videoSchema.set('toObject', { virtuals: true });
User Schema (boilerplate Yeoman angular-fullstack code):
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var crypto = require('crypto');
var authTypes = ['github', 'twitter', 'facebook', 'google'];
var UserSchema = new Schema({
name: String,
email: { type: String, lowercase: true },
role: {
type: String,
default: 'user'
},
hashedPassword: String,
provider: String,
salt: String,
facebook: {},
google: {},
github: {}
});
/**
* 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 {
'name': this.name,
'role': this.role
};
});
// 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 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 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();
});
/**
* 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');
}
};
module.exports = mongoose.model('User', UserSchema);
I have a controller that has functions which get one video (exports.show) and a function that gets more than one video (exports.index), video.controller.js:
'use strict';
var _ = require('lodash');
var Video = require('./video.model');
// Get list of videos
exports.index = function(req, res) {
Video.find()
.populate('owner')
.exec(function (err, videos) {
console.log("Pulled videos:", videos);
if(err) { return handleError(res, err); }
return res.json(200, videos);
});
};
// Get a single video
exports.show = function(req, res) {
Video.findById(req.params.id)
.populate('owner')
.exec(function (err, video) {
if(err) { return handleError(res, err); }
if(!video) { return res.send(404); }
return res.json(video);
});
};
If a Video is added to the database and is queried before restarting the server, everything behaves as expected. The populate function works and an "owner" is populated for each video. Once the server is restarted, all of the owners for any queries are null
and I don't know why. If I look at the documents in the Mongo CLI, the Videos have "owner" which is an String "_id" for a User (this is as expected).
> mongo
MongoDB shell version: 2.4.12
> db.videos.find()
{ "owner" : "550a11cb5b3bf655884fad40", "type" : "youtube", "name" : "test", "sourcemedia" : "xxxxxxx", "_id" : ObjectId("550a11d45b3bf655884fad41"), "date" : ISODate("2015-03-18T04:00:00Z"), "__v" : 0 }
I have tried using a Schema.Type.ObjectId
as the type for a Video's "owner", but I get the same result:
owner : { type: Schema.Types.ObjectId, ref: 'User', required : true },
Why are my video's "owner" properties returning null?
Mongo version: 2.4.12 Mongoose version: 3.8.24