Reputation: 1043
I have a publication whose scope depends on a element property from another collection. Basically it looks like this on the server:
Meteor.publish('kids', function (parent) {
return Kids.find({ _id: { $in: parent.childrenIds } });
}
In the example above parent.childrenIds
is an array containing the _id's of all the kids that are children of the parent. This works fine until I want to add a new child to the parent:
newKidId = Kids.insert({ name: 'Joe' });
Parents.update({ _id: parentId }, { $push: { childrenIds: newKidId } });
This works on the server for the Kids
collection (i.e., the new kid is added) and it updates the parent's childrenIds
array with the newKidId
. BUT it does not update the above 'kids' publication (the cursor is not updated/modified). As a result, the client's Kids
collection is not updated (and it looks like the change to Kids
is rolled back on the client).
When the client refreshes, all publications are stopped/restarted and the new kid (Joe) is finally published to the client.
Is there a way to avoid refreshing the client and forcing the re-publication of the Kids
collection (ideally only sending the new kid Joe to the client)?
Upvotes: 2
Views: 2527
Reputation: 7070
These days you can simply use reactive-publish package (I am one of authors):
Meteor.publish('kids', function (parentId) {
this.autorun(function (computation) {
var parent = Parents.findOne(parentId, {fields: {childrenIds: 1}});
return Kids.find({_id: {$in: parent.childrenIds}});
});
}
It is important to limit Parents
' query fields only to childrenIds
so that autorun
does not rerun for any other changes to Parents
document.
Upvotes: 0
Reputation: 7070
In meantime there were quite some packages made to deal with reactive publish functions. I am an author of meteor-related, and at the end of the package's README I compare my package with few other packages:
Upvotes: 1
Reputation: 4138
I think you need to use observe in the publish function if a published query relies on a second query. Deps.autorun on the client is not necessary.
See discussions on Meteor server reactivity and Reactive updates when query is filtered by another query.
This is some code based on http://docs.meteor.com 'counts-by-room' example.
Meteor.publish( "kids", function(parent_id){
var self = this;
Parents.find({_id: parent_id}, { childrenIds: 1 }).observe({
added: function (document){
document.childrenIds.forEach( function(kidId){
self.added("kids", kidId, Kids.findOne( { _id: kidId}, {name: 1, _id: 1} ));
});
},
removed: function (oldDocument){
oldDocument.childrenIds.forEach( function(kidId){
self.removed("kids", kidId, Kids.findOne( { _id: kidId}, {name: 1, _id: 1} ));
});
},
changed: function (newDocument, oldDocument){
var oldLength = oldDocument.childrenIds.length;
var newLength = newDocument.childrenIds.length;
if (newLength > oldLength){
self.added("kids",
newDocument.childrenIds[newLength-1],
Kids.findOne( { _id: newDocument.childrenIds[newLength-1] }, {name:1, _id:1}) );
}
else{
self.removed("kids",
oldDocument.childrenIds[oldLength-1],
Kids.findOne( { _id: oldDocument.childrenIds[oldLength-1] }, {name:1, _id:1}) );
}
}
});
self.ready();
});
Upvotes: 0
Reputation: 692
One of the things that is often misunderstood in Meteor is that there is no reactivity on the server. Dynamic descriptions need to be handled by Deps.autorun
blocks on the client. To do this, first make sure you are not including the autopublish package by using this command in project directory:
$ meteor remove autopublish
Second, set up an autorun block on the client like:
Meteor.startup(function(){
Meteor.subscribe('parents');
Deps.autorun(function() {
parent = Parents.findOne();
if (!parent) return;
Meteor.subscribe('kids', parent);
});
});
This will tear down and set up subscriptions as the parent object changes.
You can see a full working example at https://gist.github.com/jagill/5473599 .
Upvotes: 7