0

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

dcoffey3296
  • 2,504
  • 3
  • 24
  • 34

1 Answers1

2

Doh! With the angular-fullstack generator, the user's table is auto re-seeded every time you restart the server. My populates weren't working because the unique user _id's were being wiped every time I re-started.

Found this comment in server/config/seed.js which was what told me the solution...

/**
 * Populate DB with sample data on server start
 * to disable, edit config/environment/index.js, and set `seedDB: false`
 */

To solve I changed server/config/environment/development.js, the object (for me on line 8)

seedDB: true

to

seedDB: false
dcoffey3296
  • 2,504
  • 3
  • 24
  • 34
  • It seems like a bug in yeoman angular-fullstack scaffolding, why would one develop and debug volatile users (it should be set to false by default). – moshe beeri Dec 13 '15 at 21:29