4

Below is a Schema for my application. Under "meta" I have to fields that's called "upvotes" and "downvotes" and I want a field for the total amount of points (upvotes - downvotes). As for now I'm calculating this on the client side, but I also want to be able to sort by points (the image with most points first and descending).

Is there some way to auto calculate a field in Mongoose and if so, how is it done?

var ImageSchema = new Schema({
    name : String,
    size : Number,
    title   : String,
    body : String,
    buf : Buffer,
    date: { type: Date, default: Date.now },
    comments : [CommentSchema],
    meta : {
        upvotes : Number,
        downvotes : Number,
        points : ? // <- upvotes - downvotes
        favs : Number,
        uniqueIPs : [String],
        tags : [String]
    }
});
WendiKidd
  • 4,333
  • 4
  • 33
  • 50
holyredbeard
  • 19,619
  • 32
  • 105
  • 171
  • Is there a homework on this? :) I just answered a very similar question http://stackoverflow.com/questions/5035413/mongo-data-modeling-updates-for-voting-up-and-down - if you have a user who may change their vote from up to down, you may need to take care not to undercount the second vote. – Asya Kamsky May 07 '12 at 20:20

2 Answers2

5

You should use Mongoose middleware :

schema.pre('save', function (next) {
    // set points value based on positive and negatives 
})

This way, every time you save data, points value will be refreshed. The downside is that it will be refreshed even if down/upvotes aren't changed.

Warning : be extra carefull if several applications use the same DB, you want the same logic to be applied everywhere.

Arnaud Rinquin
  • 4,296
  • 1
  • 30
  • 51
4

I would make the points field a normal Number field, and increment (or decrement) it appropriately as you increment upvotes and downvotes (shown here using regular JS shell syntax, but the technique will work with mongoose as well):

// when upvoting
db.image.update({/* criteria */}, {$inc: {upvotes: 1, points: 1}})

// when downvoting
db.image.update({/* criteria */}, {$inc: {downvotes: 1, points: -1}})

If you have existing data, you'll need to generate the points field from the existing objects, but once you have it, it will be in sync due to the atomic nature of MongoDB updates and the $inc operator.

dcrosta
  • 26,009
  • 8
  • 71
  • 83
  • 1
    If unique IPs are supposed to track voters then this won't quite work when someone votes multiple times or changes their vote. You can $addToSet and $pull for up/down votes but then you won't know whether to increment the votes (if they were already there) – Asya Kamsky May 07 '12 at 20:22
  • I would include that in the "criteria" -- e.g., `...update({uniqueIps: {$ne: "1.2.3.4"}}, {$inc: {upvotes: 1, points: 1}, $push: {uniqueIps: "1.2.3.4"}})` – dcrosta May 07 '12 at 20:25