Setting a virtual field in a Model based on an async query from another model

Problem

I want to have a user setting (in a user model) that is derived from the sum of values in another model.

What I have tried to do is create a virtual value using a query like this:

var schemaOptions = {
    toObject: {
        virtuals: true
    }
    ,toJSON: {
        virtuals: true
    }
};
/**
 * User Schema
 */

var UserSchema = new Schema({
  firstname: String,
  lastname: String,
  email: String,
  username: String,
  provider: String,
  phonenumber: Number,
  country: String,
  emailverificationcode: {type:String, default:'verifyme'},
  phoneverificationcode: {type:Number, default:4321 },
  emailverified: {type:Boolean, default:false},
  phoneverified: {type:Boolean,default:false},
}, schemaOptions)

UserSchema
    .virtual('credits')
    .get(function(){
        //Load Credits model
        var Credit = mongoose.model('Credit');
        Credit.aggregate([
            { $group: {
                _id: '5274d0e5a84be03f42000002',
                currentCredits: { $sum: '$amount'}
            }}
        ], function (err, results) {
                if (err) {
                    return 'N/A'
                } else {
                    return results[0].currentCredits.toString();
                    //return '40';
                }
            }
        );
    })

Now, this gets the value but it fails to work correctly (I cannot retrieve the virtual 'value' credits). I think this is because of the async nature of the call.

Can someone suggest the correct way to achieve this?

Once again many thanks for any input you can provide.

Edit:

So I am trying to follow the suggested way but no luck so far. I cannot get my 'getCredits' method to call.

Here is what I have so far:

UserSchema.method.getCredits = function(cb) {
        //Load Credits model
        var Credit = mongoose.model('Credit');
        Credit.aggregate([
            { $group: {
                _id: '5274d0e5a84be03f42000002',
                currentCredits: { $sum: '$amount'}
            }}
        ], function (err, results) {
                cb(results);
            }
        );
};


var User = mongoose.model('User');

User.findOne({ _id : req.user._id })
.exec(function (err, tempuser) {
    tempuser.getCredits(function(result){

    });
})

Any ideas? Thanks again

Problem courtesy of: kSeudo

Solution

There are a few issues with your implementation:

UserSchema.method.getCredits
           ^^^^^^ should be 'methods'

Also, you have to make sure that you add methods (and virtuals/statics) to your schema before you create the model, otherwise they won't be attached to the model.

So this isn't going to work:

var MySchema = new mongoose.Schema(...);
var MyModel  = mongoose.model('MyModel', MySchema);

MySchema.methods.myMethod = ... // too late, model already exists

Instead, use this:

var MySchema = new mongoose.Schema(...);

MySchema.methods.myMethod = ... 

var MyModel  = mongoose.model('MyModel', MySchema);

I would also advise you to always check/propagate errors.

Solution courtesy of: robertklep

Discussion

There is currently no discussion for this recipe.

This recipe can be found in it's original form on Stack Over Flow.