Skip to content

Instantly share code, notes, and snippets.

@jnewman12
Last active August 22, 2023 20:45
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jnewman12/ddda4155995ce1cb50eab04663f5c9d1 to your computer and use it in GitHub Desktop.
Save jnewman12/ddda4155995ce1cb50eab04663f5c9d1 to your computer and use it in GitHub Desktop.
Advanced Mongoose

Advanced Mongoose

advanced mongoose


Objectives

  • Define virtual properties for a model
  • Modify how a model is serialized to JSON
  • Define a static method on the Model

Where we're at

  • We have so far seen how we use mongoose to set up our database and to make our models.

  • We have explored the different relationships and set up associations

  • We have queried our objects and seen how we can do that

  • How can we take this to the next level?


Statics and Instance Methods

  • The big thing we want to focus on are concepts in mongoose.js called statics and methods
  • Instances of Models are documents. Documents have many of their own built-in instance methods.
  • We may also define our own custom document instance methods too.
  • The best way to think about it, is methods are instance methods on an object, and statics can be thought of as class methods

Here is an example of an instance method

var animalSchema = new Schema({ name: String, type: String });

// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
  return this.model('Animal').find({ type: this.type }, cb);
};

Now all of our animal instances have a findSimilarTypes method available to it.

var Animal = mongoose.model('Animal', animalSchema);
var dog = new Animal({ type: 'dog' });

dog.findSimilarTypes(function(err, dogs) {
  console.log(dogs); // woof
});

Lets look at static methods

Adding static methods to a Model is simple as well. Continuing with our animalSchema:

// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name, cb) {
  return this.find({ name: new RegExp(name, 'i') }, cb);
};

var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function(err, animals) {
  console.log(animals);
});

Virtuals

  • Virtuals are document properties that you can get and set but that do not get persisted to MongoDB.
  • The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage.
// define a schema
var personSchema = new Schema({
  name: {
    first: String,
    last: String
  }
});

// compile our model
var Person = mongoose.model('Person', personSchema);

// create a document
var axl = new Person({
  name: { first: 'Axl', last: 'Rose' }
});

Suppose you want to print out the person's full name. You could do it yourself:

console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose

But concatenating the first and last name every time can get cumbersome. And what if you want to do some extra processing on the name, like removing diacritics?. A virtual property getter lets you define a fullName property that won't get persisted to MongoDB.

personSchema.virtual('fullName').get(function () {
  return this.name.first + ' ' + this.name.last;
});

Now, mongoose will call your getter function every time you access the fullName property:

console.log(axl.fullName); // Axl Rose

note: If you use toJSON() or toObject() (or use JSON.stringify() on a mongoose document) mongoose will not include virtuals by default. Pass { virtuals: true } to either toObject() or toJSON().


Code

  • lets code along now. I am adding this code in here so you can refer back to this if you choose to implement some of these things inside your own project.
$ mkdir advanced-mongoose
$ cd advanced-mongoose
$ npm init -f
$ npm install mongoose
$ touch food.js
$ subl .
  • now inside food.js, let's add our schema
var mongoose = require('mongoose');

var foodSchema = new mongoose.Schema({
  name: {type: String, required: true},
  group: {type: String, required: true, enum: ['Veg', 'Dairy', 'Meat', 'Bread']},
  shelfLife: {type: Number, required: true}
});

module.exports = mongoose.model('Food', foodSchema);
  • now inside our node REPL, let's play with this
  • start mongo somewhere on your machine with mongod, then in a seperate window type node to get into the repl
$ node
> var mongoose = require('mongoose')
> mongoose.connect('mongodb://localhost/food’)
> var Food = require('./food')
> Food.create({name: 'Tomato', group: 'Veg', shelfLife: 5})
> Food.find({}).exec((err,food) => console.log(food))
  • let's now create a database.js file so we can save time when you have to exit Node and restart it after modifying the schema
var mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/food')
  • now inside our food.js before the module.exports, let's add this code
// add before module.exports
foodSchema.virtual('description').get(function() {
  return `${this.name} (${this.group}) - Good for ${this.shelfLife} days`;
});

module.exports = mongoose.model('Food', foodSchema);
  • now exit node, turn it back on with node, then let's test
$ node
> require('./database')
> var Food = require('./food')
> Food.findOne({}).exec((err, item) => console.log(item.description))
  • now in our food.js before module exports, lets add this
foodSchema.statics.findByGroup = function(group, cb) {
  return this.find({group: group}, cb);
};
  • now let's restart node, and type in this to test the static
$ node
> require('./database')
> var Food = require('./food')
> Food.findByGroup('Veg', (err, food) => console.log(food))
  • lets add one final piece of code.
  • much like rails does for us, we do not want to include our user's password in our JSON output
  • lets see what that could look like
  • at the bottom of food.js before the module exports, let's add this
foodSchema.set('toJSON', {
  virtuals: true, // serialize virtuals
  transform: function(doc, ret, options) {
    // add an id property
    ret.id = ret._id;
    // remove a property like password or whatever
    delete ret._id;
    return ret;
  }
});
  • now let's restart node and test
$ node
> require('./database')
> var Food = require('./food')
> var item
> Food.findOne({}, function(err, food) { item = food })
> item  // outputs actual document
> item.toJSON()  // shows how document will be serialized when sent to client

@abura1han
Copy link

It is very useful. Thank's.

@Jay-davisphem
Copy link

Very useful. Well appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment