Meteor.publish: publish collection which depends on other collection
Overview
As of this writing, reactive joins are an unsolved problem. For a complete overview see Reactive Joins In Meteor.
Recommendations
I strongly recommend against using observeChanges directly. It's incredibly hard to get right, and easy to develop a memory leak. If you don't believe me, watch this video on EventedMind. It will make your eyes bleed.
There are several package-based solutions to this problem. The meteor guide recommends publish-composite.
If you find the idea of using a package-based solution to be unacceptable, have a close look at the Joining On The Client section from Reactive Joins In Meteor. It's clean but requires more waiting on the user's part. Also see my post on template joins if you prefer to active your subscriptions at the template level.
Meteor: How to publish cursor that is depending on cursor of other collection
Solution
Lists = new Meteor.Collection('lists');
if (Meteor.isClient) {
Tracker.autorun(function() {
if (Meteor.userId()) {
Meteor.subscribe('lists');
Meteor.subscribe('myLists');
}
});
}
if (Meteor.isServer) {
Meteor.startup(function() {
if (Meteor.users.find().count() === 0) {
var user = {
username: 'test',
email: 'test@test.com',
password: 'test'
};
var userId = Accounts.createUser(user);
var listId = Lists.insert({data: 'content'});
Meteor.users.update(userId, {
$addToSet: {lists: listId}
});
}
});
Meteor.publish('lists', function() {
check(this.userId, String);
var lists = Meteor.users.findOne(this.userId).lists;
return Lists.find({_id: {$in: lists}});
});
Meteor.publish('myLists', function() {
check(this.userId, String);
return Meteor.users.find(this.userId, {fields: {lists: 1}});
});
}
Changes
- Declare the
Lists
collection outside of the client and server (no need to declare it twice). - Ensure the user is logged in when subscribing. (performance enhancement).
- When inserting the test user, use the fact that all insert functions return an id (reduces code).
- Ensure the user is logged in when publishing.
- Simplified
lists
publish function. - Fixed
myLists
publish function. A publish needs to return a cursor, an array of cursors, or a falsy value. You can't return an array of ids (which this code doesn't access anyway because you need to do afetch
or afindOne
). Important note - this publishes another user document which has thelists
field. On the client it will be merged with the existing user document, so only the logged in user will havelists
. If you want all users to have the field on the client then I'd recommend just adding it to the user profiles.
Caution: As this is written, if additional list items are appended they will not be published because the lists
publish function will only be rerun when the user logs in. To make this work properly, you will need a reactive join.
Publish documents in a collection to a meteor client depending on the existence of a specific document in another collection (publish-with-relations)
What you are looking for is a reactive join. You can accomplish this by directly using an observe in the publish function, or by using a library to do it for you. Meteor core is expected to have a join library at some point, but until then I'd recommend using publish-with-relations. Have a look at the docs, but I think the publish function you want looks something like this:
Meteor.publish('offersShared', function() {
return Meteor.publishWithRelations({
handle: this,
collection: ShareRelations,
filter: {receiverId: this.userId},
mappings: [{collection: Offers, key: 'offerId'}]
});
});
This should reactively publish all of the ShareRelations
for the user, and all associated Offers
. Hopefully publishing both won't be a problem.
PWR is a pretty legit package - several of us use it in production, and Tom Coleman contributes to it. The only thing I'll caution you about is that as of this writing, the current version in atmosphere (v0.1.5) has a bug which will result in a fairly serious memory leak. Until it gets bumped, see my blog post about how to run an updated local copy.
update 2/5/14:
The discover meteor blog has an excellent post on reactive joins which I highly recommend reading.
How to publish a view/transform of a collection in Meteor?
UPDATE You can transform a collection on the server like this:
Words = new Mongo.Collection("collection_name");
Meteor.publish("yourRecordSet", function() {
//Transform function
var transform = function(doc) {
doc.date = new Date();
return doc;
}
var self = this;
var observer = Words.find().observe({
added: function (document) {
self.added('collection_name', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('collection_name', oldDocument._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('collection_name', oldDocument._id);
}
});
self.onStop(function () {
observer.stop();
});
self.ready();
});
Publish a virtual collection in meteor
so I used publishVirtual function. thanks to @michel floyd
function publishVirtual(sub, name, cursor) {
var observer = cursor.observeChanges({
added : function(id, fields) { sub.added(name, id, fields) },
changed: function(id, fields) { sub.changed(name, id, fields) },
removed: function(id) { sub.remove(name, id) }
})
sub.onStop(function() {
observer.stop() // important. Otherwise, it keeps running forever
})
}
and added this into publish :
Meteor.publish('freeCourses', function () {
var cursor = Courses.find({}, {fields: {'Seasons.Episodes.paid_url': 0}});
publishVirtual(this, 'freeCourses', cursor);
this.ready();
});
Meteor.publish('premiumCourses', function () {
//userPremiumCourses contains array of course_ids
var userPremiumCourses = userCourses.find({'user_id': this.userId}, {fields: {course_id: 1, _id: 0}}).map(
function (doc) {
return doc.course_id;
}
);
var cursor = Courses.find({_id: {$in: userPremiumCourses}});
publishVirtual(this, 'premiumCourses', cursor);
this.ready();
});
and made two client-side collections for subscribe :
if (Meteor.isClient) {
freeCourses = new Mongo.Collection("freeCourses");
premiumCourses= new Mongo.Collection("premiumCourses");
Meteor.subscribe('freeCourses');
Meteor.subscribe('premiumCourses');
}
How to read a collection that depends on another one in Meteor
Server side code:
Meteor.publish("latestPost", function () {
var post = Posts.find({}, {sort:{created:-1}}).fetch()[0];
console.log("publish : " + post.title);
return [
Posts.find({_id: post._id}),
Comments.find({postId: post._id})
];
});
Client side code:
this.route('home', {
path: '/',
template: 'home',
waitOn: function () {
return [
Meteor.subscribe('latestPost')
];
},
data:function(){
return {
post:Posts.findOne(),
comments:Comments.find()
};
}
});
Check this repository to see whole example.
After user changes to another route, then subcriptions are being automatically stopped.
Related Topics
How to Format a Float in JavaScript
Call Python Function from JavaScript Code
Mongoose - What Does the Exec Function Do
How to Detect Browser's Protocol Handlers
How to Get First and Last Day of the Current Week in JavaScript
Pure JavaScript: a Function Like Jquery's Isnumeric()
JavaScript Callback When Iframe Is Finished Loading
Why am I Seeing an "Origin Is Not Allowed by Access-Control-Allow-Origin" Error Here
How to Determine the Number of Days in a Month with JavaScript
Add Property to an Array of Objects
When a 'Blur' Event Occurs, How to Find Out Which Element Focus Went *To*
JavaScript Es6 Computational/Time Complexity of Collections
Move the Mouse Pointer to a Specific Position
Javascript: Collision Detection
Keydown Simulation in Chrome Fires Normally But Not the Correct Key
Best Way to Get All Selected Checkboxes Values in Jquery
Sending Message from a Background Script to a Content Script, Then to a Injected Script