MongooseJS预钩子中修改文档

3
我在使用mongoose时遇到了一些问题。我的目标是在预保存期间修改对象,例如必要时拆分标签或计算子文档持续时间的总和并在主文档中更新它。
我发现如果我加载一个模型,然后调用doc.update传递新数据,只有 `schema.pre('update', ...)` 能触发,我的中间件内对 `this` 进行的任何更改都不会被更新。我还尝试在我的更新中间件中使用 `this.set('...', ....);`,但无济于事。
似乎如果我用 `doc.save(...)` 代替,那么在 `schema.pre('save', ...)` 中对 `this` 的更改会按预期附加。除了将发布的变量扩展到我的模型属性并保存之外,我没有看到任何利用doc.update达成此目的的方法。
我的目标: - 通过 `doc.update(properties, ....)` 更新现有文档 - 使用保存期间的中间件修改文档,进行高级验证,并更新相关文档 - 在更新期间使用中间件修改文档,进行高级验证,并更新相关文档 - 可互换地使用 model.findByIdAndUpdate、model.save、model.findById->doc.update、model.findById->doc.save,并全部进入我的保存/更新中间件。
以下是一些任意示例代码:
function loadLocation(c) {
    var self = this;
    c.Location.findById(c.params.id, function(err, location) {
        c.respondTo(function(format) {
            if (err | !location) {
                format.json(function() {
                    c.send(err ? {
                        code: 500,
                        error: err
                    } : {
                        code: 404,
                        error: 'Location Not Found!'
                    });
                });
                format.html(function() {
                    c.redirect(c.path_to.admin_locations);
                });
            } else {
                self.location = location;
                c.next();
            }
        });
    });
}

LocationController.prototype.update = function update(c) {
    var location = this.location;
    this.title = 'Edit Location Details';

    location.update(c.body.Location, function(err) {
        c.respondTo(function(format) {
            format.json(function() {
                c.send(err ? {
                    code: 500,
                    error: location && location.errors || err
                } : {
                    code: 200,
                    location: location.toObject()
                });
            });
            format.html(function() {
                if (err) {
                    c.flash('error', JSON.stringify(err));
                } else {
                    c.flash('info', 'Location updated');
                }
                c.redirect(c.path_to.admin_location(location.id));
            });
        });
    });
};

module.exports = function(compound) {
    var schema = mongoose.Schema({
        name: String,
        address: String,
        tags: [{ type: String, index: true }],
        geo: {
            type: {
                type: String,
            default:
                "Point"
            },
            coordinates: [Number] // Longitude, Latitude
        }
    });
    schema.index({
        geo: '2dsphere'
    });
    var Location = mongoose.model('Location', schema);
    Location.modelName = 'Location';
    compound.models.Location = Location;

    schema.pre('save', function(next) {
        if(typeof this.tags === 'string') {
            this.tags = this.tags.split(',');
        }
    });
};

==== * 修订样例 * ====

module.exports = function(compound) {
    var schema = mongoose.Schema({
        name: String,
        bio: String
    });

    schema.pre('save', function(next) {
        console.log('Saving...');
        this.bio = "Tristique sed magna tortor?"; 
        next();
    });

    schema.pre('update', function(next) {
        console.log('Updating...');
        this.bio = "Quis ac, aenean egestas?"; 
        next();
    });

    var Author = mongoose.model('Author', schema);
    Author.modelName = 'Author';
    compound.models.Location = Author;
};
2个回答

9
pre
钩子对于doc.save()doc.update()都有效。在这两种情况下,this指的是文档本身。
请注意,在编译模型之前,需要将钩子添加到模式中。
schema.pre('save', function(next) {
    if(typeof this.tags === 'string') {
        this.tags = this.tags.split(',');
    }
});
var Location = mongoose.model('Location', schema);

嗯,我创建了糟糕的示例代码并没有帮助。我尝试了您的建议,使用一个更简单的案例,但仍然得到相同的结果:在使用Author.findById查找文档后,当我调用author.update(c.req.body, ...)时,schema.pre('save', ...)不会触发,但是schema.pre('update', ...)会触发,而对this.name的更改不会被应用。schema.pre('save', ...)应该会触发,对吗?看起来author.save会触发schema.pre('save', ...),并且随后进行的更改会被应用。那么解决方案是完全放弃author.update吗? - user1363145
doc.update() 只会触发被调用的方法的钩子,不会触发其他任何方法的钩子。 - aaronheckmann
好的,但这仍然没有回答原始问题。在 schema.pre('update', ...) 过程中进行的更改仍然没有被追加。这是一个错误、故意的还是我应该专门使用 doc.save 并使用某种方法来迭代我的 req.body 并合并属性? - user1363145
不是一个错误。document.update(doc, cb) 发送的是传递的文档的更新,而不是当前更改的值。这与 document.save() 不同。 - aaronheckmann
@aaronheckmann 你确定这是指当前文档吗? - Ludo
显示剩余2条评论

0

Mongoose不支持Model Update API的钩子。但是,可以通过Monkey-patch实现更新钩子。Hooker NPM包是一个干净的实现方式。

RESTeasy项目是Node REST API的样板,其中包含演示如何实现的代码:

var hooker = require('hooker');

var BaseSchema = new mongoose.Schema({
  sampleString: {
    type: String
  }
});

var BaseModel = mongoose.model('Base', BaseSchema);

// Utilize hooks for update operations. We do it in this way because MongooseJS
// does not natively support update hooks at the Schema level. This is a way
// to support it.
hooker.hook (BaseModel, 'update', {
  pre: function () {
    // Insert any logic you want before updating to occur here
    console.log('BaseModel pre update');
  },
  post: function () {
    // Insert any logic you want after updating to occur here
    console.log('BaseModel post update');
  }
});

// Export the Mongoose model
module.exports = BaseModel;

现在update钩子在Mongoose 4.0中可以原生地工作,但默认情况下它们是关闭的。这篇博客文章解释了原因:https://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm - steampowered

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接