So I have a collection file, located in /lib/collections/products.js
in which I define my collection and in using Meteor Collection Hooks I was hoping to implement some cascading deletes and updates.
This would save on general āmaintenanceā code included in all my meteor methods that might delete a product, for example.
Products = new Mongo.Collection('products');
///////////////////////////////////////////////////////////////////
// HOOKS //
///////////////////////////////////////////////////////////////////
Products.after.insert(function (userId, doc) {
// Increment the product category's product count
ProductCategories.update(doc.category, { $inc: { productCount: 1 } });
});
Products.before.remove(function (userId, doc) {
// Decrement the product category's product count
ProductCategories.update(doc.category, { $inc: { productCount: -1 } });
// Ensure all print list entries that reference this product are removed
PrintLists.update({ 'items.product': doc._id }, { $pull: { items: { product: doc._id } } }, { multi: true });
});
Now perhaps I completely misunderstood the idea of the hooks, but if I delete a product, it seems as though the Products.before.remove()
function fires in both the client and the server. Rather than operating like a Meteor method, (operating in āsimulation modeā on the client) it seems as though the decrement to productCount
is actually called twice, one on the client, one on the server and both take effect!?
Weirdly, the same doesnāt seem to happen to the increment in the Products.after.insert()
functionā¦ No idea why!?
Can someone explain what is going on, as Iām obviously a bit lost!
4 Likes
Youāre right! Iāve experienced the same behavior in a similar use case. Iām essentially creating activities in an afterInsert
hook.
// common.js
Products = new Meteor.Collection('products');
Activities = new Meteor.Collection('activities');
Products.after.insert(function (userId, doc) {
Activities.insert({productId: doc._id, title: doc.name + " inserted."});
});
It ends up creating 2 activities. My workaround is to only have the hook defined in the server code. But you loose the latency compensation then. Not sure if this behavior is intended or a bug.
Actually, when you wrap your insert code into a Meteor method
// common.js
Meteor.methods({
insertProduct: function (name) {
Products.insert({name: name});
},
});
and call it with
Meteor.call('insertProduct', "A Product")
then it works. This creates only one activity and makes use of latency compensation.
2 Likes
I guess that makes sense really, itās just that I had wrongly assumed that the client side versions of these function calls were effectively the same as the latency-compensated methodsā¦ Perhaps the documentation for the package needs to give an example or at least a warning that this isnāt the case!?
Iād still go with defining hooks on the server. The problem is, collection-hooks does not track the server outcome of the hook trigger.
So letās say you make an insert on the client and it triggers an increment update on the client. But the same insert fails on the server (eg due to a server checked constraint). What happens is, the insert ends up not happening, but the increment does go through since there is nothing against an increment.
This is the case wether or not a method or direct updates are used.
Therefore, the safest bet is, triggering your hooks on the server side where you know youāve completed your sanity checks and you are good to go.
1 Like
Knowing what I know now, I agree and will be keeping my hooks server-side only. However, if the hooks just did simulated effects on the client, much like methods, then that issue wouldnāt arise.
The Meteor.method
technique actually does work! The client will run the hook instantly (latency compensation) but if the server says something different, it will revert the changes. Take my example from above. If you define the method like this:
// common.js
Meteor.methods({
insertProduct: function (name) {
if (Meteor.isServer)
return false
else
Products.insert({name: name});
},
});
you will see a short flickering of the inserted product (AND activity) but that will be reverted as if nothing ever happened. Same thing should be happening with incrementing/decrementing.
3 Likes
Hmm, I wonder if this is the collection-hooks package nature or meteor methods nature and also I do wonder if this is by design or coinsidence, or perhaps even a bug
So the takeaway seems to be that if we wrap methods within other methods, latencey compensation works in such a way that unless all components of the method from the outermost parrent to innermost child run without error on the server, anything done on the client will have just been canceled away.
And Iām also guessing what constitutes an error is a proper Meteor.error object that is properly thrown from within a callback.
Can anyone confirm/argue this?
1 Like
Manā¦I was diggin this for few hours today First I thought the problem was with my for loop but then I understood that was a bug or smth. It should be really added to the docs. Will wrap it into a server method thenā¦
I am trying to do the same, I am doing an insert of a default row with this relation key value, when I observe what happens during my insert in Assesments collection, I just see a flicker of ChairAssesments insert and later it disappears. Not sure what is happening here I tried it with both with Meteor.isServer and without it, both gives same effect.
if(Meteor.isServer){
Assesments.after.insert(function (userId, doc) {
ChairAssesments.insert({ assesmentId: doc._id });
});
}