bobbyz
bobbyz

Reputation: 5046

How to save a property to a mongoose document post save

I'm using Mongoose's timestamps schema option to make a createdAt and updatedAt property on a user schema (calling them "applicants"). The only reason I need this is to figure out how long it takes a user to get through the signup process. When they click the "Let's get started" button, it creates the user on the backend and sets the createdAt and updatedAt properties (at this point, they are equal). Then when they finish the signup process, I want to send another call to the db and force the document to save again, thus updating the updatedAt property. Once that occurs, I want to find the time difference between the two timestamp properties to see how long it took them to finish the process, then save that value to a timeTaken property.

Problem is, the only way I'm seeing to do this is to trigger the save, then in the promise that's returned, save the document again after a "post save" hook runs to update the applicant's document.

Probably will make more sense with the code:

Applicant's Schema

var applicantSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        lowercase: true
    },
    timeTaken: String
}, {timestamps: true});

applicantSchema.post("save", function () {
    this.setTimeTaken();
});

applicantSchema.methods.setTimeTaken = function () {
    var applicant = this;
    var ms = this.updatedAt - this.createdAt;
    var x = ms / 1000;
    var seconds = Math.floor(x % 60);
    x /= 60;
    var minutes = Math.floor(x % 60);
    x /= 60;
    var hours = Math.floor(x % 24);
    applicant.timeTaken = hours + "h:" + minutes + "m:" + seconds + "s";
};

Applicant Router

applicantRouter.put("/:applicantId", function (req, res) {
    Applicant.findById(req.params.applicantId, function (err, applicant) {
        applicant.save().then(function () {
            applicant.save(function (err, applicant) {
                //console.log(applicant.timeTaken);
                if (err) {
                    res.status(500).send(err)
                } else {
                    res.send({success: true, timeTaken: applicant.timeTaken})
                }
            });
        });
    });
});

How this works:

PUT request comes in, mongoose searches mongodb for the document with the given id, then triggers a "save" event. It's caught by the schema's "post save" hook, which calls the document's setTimeTaken method. This method updates the property on the document, but since this happens post save, I need to save it again to get it to persist.

I can't use a pre-save hook, because it won't have updated the updatedAt property yet.

The code above actually works, but I kinda hate myself for writing it this way. I feel like an infomercial actor: "There's got to be a better way!"

Or maybe there isn't? Someone who knows this stuff better ? Thanks in advance

Upvotes: 4

Views: 9280

Answers (1)

bobbyz
bobbyz

Reputation: 5046

After stepping away from the computer for a few hours I realized I was adding too much complexity and trying too hard to use the built-in timestamps option. I just changed my math to include Date.now() - this.createdAt, changed the post save to a pre save, and got rid of the extra applicant.save():

Applicant's Schema:

var applicantSchema = new Schema({
    name: {
        type: String,
        required: true
    },
    email: {
        type: String,
        required: true,
        lowercase: true
    },
    timeTaken: String
}, {timestamps: true});

applicantSchema.pre("save", function (next) {
    this.setTimeTaken();
    next();
});

applicantSchema.methods.setTimeTaken = function () {
    var applicant = this;
    var ms = Date.now() - this.createdAt;
    var x = ms / 1000;
    var seconds = Math.floor(x % 60);
    x /= 60;
    var minutes = Math.floor(x % 60);
    x /= 60;
    var hours = Math.floor(x % 24);
    applicant.timeTaken = hours + "h:" + minutes + "m:" + seconds + "s";
};

Applicant Router:

applicantRouter.put("/:applicantId", function (req, res) {
    Applicant.findById(req.params.applicantId, function (err, applicant) {
        applicant.save(function (err, applicant) {
            if (err) {
                res.status(500).send(err)
            } else {
                res.send({success: true, timeTaken: applicant.timeTaken})
            }
        });
    });
});

Works like a charm.

Upvotes: 2

Related Questions