How to have an auto calculated attribute in mongoose schema

Viewed 11841

suppose I have following mongoose schema in Node.js

var schema = new mongoose.Schema({
    views:Number,
    clicks:Number,
    ctr:Number
})

In which I want that CTR attribute should be automatically calculated by a formula based on values of other attributes, i.e lets say

ctr = clicks / views

I don't want to have CTR as a virtual field because based on the value of CTR I will sort all documents of my collection and apparently sorting is not possible based on virtual fields. So I was wondering if there is any way to have calculated attributes in mongoose schema which gets automatically updated on update of any of the dependent attribute/field

4 Answers

I want that CTR attribute should be automatically calculated by a formula based on values of other attributes

From the Mongoose docs [emphasis added]:

You can also set the default schema option to a function. Mongoose will execute that function and use the return value as the default.

Therefore, your example could be rewritten as:

const schema = new mongoose.Schema({
  views: {
    type: Number
  },
  clicks: {
    type: Number
  },
  ctr: {
    type: Number,
    default: function() {
      return this.clicks / this.views
    }
  }
})

So I was wondering if there is any way to have calculated attributes in mongoose schema which gets automatically updated on update of any of the dependent attribute/field

If I understand correctly, note the section in the docs on the setDefaultsOnInsert option. This may be of relevance when recalculating the default upon update.

You can do anything, but don't forget about that that difficult thing slower your server.

In my case for calculating age user I do that.

const mongoose = require( 'mongoose' );
const Schema = mongoose.Schema;

    const userSchema = new Schema( {

      born: {
        type: String,
        get: a => {
          let date = new Date(a);
          let today = new Date();
          let timeDiff = today.getTime() - date.getTime();
          let age = Math.floor( timeDiff / ( 1000 * 3600 * 24 ) / 365 );
          return age;
        },
        set: age => age,
        alias: 'age',
      },
    } 

);

module.exports = mongoose.model( 'User', userSchema );

And Mongoose Documantation

Another approach is to use the Pre-save middleware. I am implementing that in my Schema and it works good so far. The caveat is that it will be not triggered for updates, though. The format is the following:

schema
  .pre('save', function(next){
   this.ctr = this.clicks/this.views
   next();   
});
Related