Closed skotchio closed 9 years ago
by design. there are docs involved to call hooks on.
correction, there are no docs to call hooks on.
So? If I want to call pre, post middleware I need to use the first approach?
yes that is correct. Model.update,findByIdAndUpdate,findOneAndUpdate,findOneAndRemove,findByIdAndRemove are all commands executed directly in the database.
This should definitely be clarified in the guide, especially if you're talking about validation in that same page and describing findByIdAndUpdate
as "better"
if you click on the link "better" it takes you to the full documentation of the method where it explains validation etc.
feel free to send a pull request to add something you feel is better. its pretty easy to do so: https://github.com/LearnBoost/mongoose/blob/master/CONTRIBUTING.md
On Wed, Oct 31, 2012 at 6:03 PM, Jesse Fulton notifications@github.comwrote:
This should definitely be clarified in the guidehttp://mongoosejs.com/docs/documents.html, especially if you're talking about validation in that same page and describing findByIdAndUpdate as "better"
— Reply to this email directly or view it on GitHubhttps://github.com/LearnBoost/mongoose/issues/964#issuecomment-9967865.
Aaron @aaronheckmann https://twitter.com/#!/aaronheckmann
Added notes to the middleware doc
Pull request: https://github.com/LearnBoost/mongoose/pull/1750
Hello,
I know the issue is closed, but I am not entirely satisfied with the answer. Shouldn't there be at least a post-save or post-update middleware for findOneAndUpdate and other similar operations ? Is seems ok since the document is returned. Not feasible for pre middlewares or for Model.update, i agree.
It would greatly improve the capabilities of plugins such as mongoosastic that is currently blind to some operations it should be able to support.
If no middleware, does someone have an idea on how to manage some post update operations in a plugin ?
Thanks
@albanm certain methods bypass mongoose completely, so you don't get the middleware hooks. AFAIK, the only way to get the hooks to execute is to use separate find()
and save()
calls as mentioned above.
I get that and it makes sense. Pre middlewares are out of question as there is no fetching prior to certain methods. But still, mongoose should be able to wrap the update operations that also return the updated documents and trigger some post hooks.
I think @albanm is right, People may want the same functionality when they use the same function. How about wrap those 'directly update' methods with some interception of check that if there's any hooks exist? If hook exist, use it, or call original update methods otherwise.
+1
+1
+1
+1
:+1:
+1
:-1:
I get the feature request, but the longer I work with middleware, I find myself needing to bypass middleware often when calling db update scripts and other items that are not routine to my normal operations. By using static methods it becomes very easy to create a custom wrapper that implements the above feature requests already. Since mongoose is now opening to ideas for version 4, it is unlikely that any API change of this magnitude will happen in v3. I request that this be moved to v4 discussion.
I would however, :+1: if I was able to disable middleware on a save call. I often find myself with a mongoose object that was passed from another function and simply want to perform a .save - in this situation, doing a .save is preferable to writing a new query. If this is possible, please point it out
Either way, amazing library. Kudos to awesome maintainers. Not sure how I would operate without it.
+1
A simple way to add hooks for these method is overwrite the embed functions:
_update = UserModel.update
UserModel.update = (conditions, update) ->
update.updatedAt or= new Date
_update.apply this, arguments
Then every update call of mongoose will fix the updatedAt key of data.
You may give a try of this model limbo. It is a simple wrapper of mongoose model, supporting bind static/method/overwrite to all the schemas, and call rpc methods to query the mongodb.
+1, need this feature...
While I sort of understand the reasoning, the lack of hooks on atomic updates IMHO makes Mongoose somewhat pointless in practice. When I use atomic updates any validations, defaults, etc. are not executed, so the entire purpose of using an ODM is defeated. Using find/save will do the job, but is there any guarantee this is always used?
Moreover, usually I would try to avoid find/save since it's not an atomic operation. MongoDB compensates it's lack of transaction support by providing powerful atomic query & update features. So I would use these atomic operations but w/o middleware support Mongoose won't provide much value over the native MongoClient
.
Even the examples in http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning would use update
, hence bypass middleware. I can use versioning properly or middleware but not combine both? Really, where's the point in having it?
I don't even fully understand the technical reasons: If update
& co. wrap around the database operations, why can't we intercept the call and pass the query objects so we can do some validation/customization before we actually do the update?
@joerx +1 would suffice.. :) but your reasoning is flawless.
The 3.9.x branch has support for pre and post hooks for find
and findOne
- should be easy to add support for findOneAndUpdate
and update
.
Is this feature merged ?
So the pre('findOneAndUpdate')
and post('findOneAndUpdate')
hooks are in master, there isn't an update hook just yet. There's no release that contains either of those yet.
So does it triggers pre-save after .update() now?
No. There is a separate update()
hook for Query.update()
. Save hooks are distinct from query hooks.
@vkarpov15 Can you please link to the supporting documentation for the update hook? Any word on when pre('findOneAndUpdate')
and post('findOneAndUpdate')
will hit a release?
@karlstanton use 4.0.0-rc2, npm install mongoose@unstable
:)
'use strict';
var Promise = require('bluebird');
var mongoose = Promise.promisifyAll(require('mongoose'));
var counterSchema = new mongoose.Schema({
total: {
type: Number,
default: 0
}
});
counterSchema.post('findOneAndUpdate', function (doc) {
console.log(doc.total);
});
var Counter = mongoose.model('Counter', counterSchema);
Promise.coroutine(function *() {
yield mongoose.connectAsync(process.env.MONGODB_URI);
console.log('Connected');
let counter = yield Counter.createAsync({});
console.log(`${counter.total}`);
for (let i = 0; i < 10; i++) {
yield Counter.findOneAndUpdateAsync({ _id: counter.id }, { $inc: { total: 1} });
}
})();
Connected
0
0
1
2
3
4
5
6
7
8
9
Seems like it's a step behind. What am I missing?
AH, the new
flag default breaking change. While there's something to say about consistency with the underlying driver, I have to say it's really counter-intuitive, especially when considering this new hook.
@neverfox :)
Yeah @neverfox good catch. This change is documented in the release notes and you can see more discussion why this was changed in #2262.
The reason is that new
is false by default in the mongodb node driver, the mongodb shell, the actual mongodb server, etc. and wrapper layers setting non-standard defaults makes life difficult for devs. My canonical example of a module that gets this horribly wrong is gulp-uglify, which overrides a bunch of uglify-js's defaults.
I see that the issue is closed, but is the functionality in place in version 4.0.2 out is it just still in the unstable version? It doesn't seem to execute on findOneAndUpdate with Scema.pre('update') or Schema.pre('findOneAndUpdate') with the 4.0.2 version. Am I missing something that I have to pass to the function?
How are you declaring the pre hook @CaptainStaplerz ?
Is findOneAndUpdate middleware hook available in 4.0.2? I upgraded from 3.8 to latest mongoose 4.0.2 to use this and the middlware Document.schema.post('findOneAndUpdate', function (doc) is not getting triggered like save() or remove() does
@honitus show me your code
@vkarpov15 - Thanks for quick response, here you go
blog.controller,js
// Updates an existing Blog in the DB, adds comment
exports.update = function(req, res) {
Blog.findOneAndUpdate(
{"_id": req.body._id,},
{"$push": {"comments": buildComment}},
{safe: true, upsert: true}, function (err, workspace) {
if (err) {
return handleError(res, err);
}
return res.send(200);
}
);
}
blog.socket.js
/**
* Broadcast updates to client when the model changes
*/
'use strict';
var Blog= require('./blog.model');
exports.register = function(socket) {
//SAVE WORKS
Blog.schema.post('save', function (doc) {
onSave(socket, doc);
});
// IS NOT TRIGGERED :(
Blog.schema.post('findOneAndUpdate', function (doc) {
onComment(socket, doc);
});
Blog.schema.post('remove', function (doc) {
onRemove(socket, doc);
});
}
//SAVE WORKS when a new blog is created
function onSave(socket, doc, cb) {
socket.emit('blog:save', doc);
}
// IS NOT TRIGGERED :(
function onComment(socket, doc, cb) {
socket.emit('blog:findOneAndUpdate', doc);
}
function onRemove(socket, doc, cb) {
socket.emit('blog:remove', doc);
}
@vkarpov15 Thanks for reopening. Does this mean hook for findOneAndUpdate isn't there in 4.0.2 and you plan to include in 4.0.3.
@vkarpov15 Here's the code where I declare the hooks:
...
var TodoSchema = new mongoose.Schema({
name: {type: String, required: true},
note: String,
completed: {type: Boolean, default: false},
updatedAt: {type: Date, default: Date.now},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users'
}
});
...
// Not executed
TodoSchema.pre('update', function() {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
});
// Not executed
TodoSchema.pre('findOneAndUpdate', function() {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
});
And here's where I call the update:
...
router.route('/:id')
.put(function(req, res, next) {
TodoModel.findOneAndUpdate({_id: req.params.id, user: req.user.id}, req.body, {new: true}, function(err, post) {
if(err) return next(err);
if(post) {
res.status(200).json(post);
}
else {
next(newSystemError(errorCodes.TODO_NOT_FOUND, undefined, req.params.id));
}
});
});
As far as I know the findOneAndUpdate()
hook should be in there, if it's not working that's a bug
@CaptainStaplerz try:
TodoSchema.pre('update', function(next) {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
next();
});
Also, is the console.log
statement executing, or is it just the Date.now() part that's giving unexpected results?
@vkarpov15 I changed my source code, adding the changes you made in Add implementation #964 https://github.com/Automattic/mongoose/commit/e98ef98e857965c4b2ae3339fdd7eefd2a5a9913
It works like charm now. So I think the fix is not checked into main
@honitus are you sure you're using mongoose 4.0.2? That change is in fact committed to 4.0.0 and up.
honitus$ npm view mongoose version 4.0.2
@honitus what you're doing incorrectly is that you're adding hooks to the schema after compiling your model. Model.schema.pre('remove');
is not expected to work, see "Compiling your first model" on the model docs. Attach the hooks to the schema first and then things should work - that's the only difference I see between your code and our tests.
@CaptainStaplerz the only way I can find to repro your code is with an empty update. The below code works
var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');
mongoose.connect('mongodb://localhost:27017/gh964');
var TodoSchema = new mongoose.Schema({
name: {type: String, required: true},
note: String,
completed: {type: Boolean, default: false},
updatedAt: {type: Date, default: Date.now},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users'
}
});
TodoSchema.pre('update', function() {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
});
TodoSchema.pre('findOneAndUpdate', function() {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
});
var Todo = mongoose.model('Todo', TodoSchema);
Todo.update({}, { note: "1" }, function(err) {
if (err) {
console.log(err);
}
console.log('Done');
process.exit(0);
});
But once you make the update empty:
var mongoose = require('mongoose');
mongoose.set('debug', true);
var util = require('util');
mongoose.connect('mongodb://localhost:27017/gh964');
var TodoSchema = new mongoose.Schema({
name: {type: String, required: true},
note: String,
completed: {type: Boolean, default: false},
updatedAt: {type: Date, default: Date.now},
user: {
type: mongoose.Schema.ObjectId,
ref: 'Users'
}
});
TodoSchema.pre('update', function() {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
});
TodoSchema.pre('findOneAndUpdate', function() {
console.log('------------->>>>>> update updatedAt')
this.updatedAt = Date.now();
});
var Todo = mongoose.model('Todo', TodoSchema);
Todo.update({}, { }, function(err) {
if (err) {
console.log(err);
}
console.log('Done');
process.exit(0);
});
The pre update hook no longer executes. Is this consistent with what you're seeing?
@vkarpov15 Model.schema.pre('remove'); is one the stubs automatically created by angular full stack I added implementation #964 e98ef98 All I did is add one line below this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate, See Line 1526 in 4.0.2 - this.model.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate,
Replace Line 1526 with- this.schema.s.hooks.wrap('findOneAndUpdate', Query.base.findOneAndUpdate, and it works.
That's by design. Mongoose is not supposed to allow modifying the schema hooks after the model has been compiled (that is, the mongoose.model()
call), which is why that no longer works.
@vkarpov15 I managed to get the hook working now with the following, it must have been a stupid typo or something else:
TodoSchema.pre('findOneAndUpdate', function(next) {
console.log('------------->>>>>> update updatedAt: ', this.updatedAt);
this.updatedAt = Date.now();
next();
});
However 'this' doesn't seem to refer to the updated model (but it does in the 'save' hook?), thereby this.updatedAt refers to undefined.
How do I update the 'updatedAt' -field in the 'findOneAndUpdate' -hook?
Because a findAndUpdate method is presented to cut down the following code:
to this:
We need to use pre, post middleware exactly the same. At now pre, post middleware are not executed when I make findByIdAndUpdate.