There are a couple of ways you can accomplish this join. A few notes before before we begin:
As I explained in the answer to this question, sorting in the publish function has no affect on the order of documents on the client.
Using the plural form in a collection name is the accepted standard. Course
just looks odd when the collection contains courses.
This question is fundamentally about joins, so I'd recommend reading Reactive Joins In Meteor.
Server Transform
The literal answer to your question is to transform the documents on the server like so:
Meteor.publish 'popularCourses', ->
transform = (fields) ->
if fields.owner
username = Meteor.users.findOne(fields.owner)?.username
fields.owner = username
fields
handle = Course.find().observeChanges
added: (id, fields) =>
@added 'course', id, transform fields
changed: (id, fields) =>
@changed 'course', id, transform fields
removed: (id) =>
@removed 'course', id
@ready()
@onStop ->
handle.stop()
Advantages
- All of the work is done on the server, so the client can just use
owner
as if it was a username.
Disadvantages
Using observeChanges
is probably more computational work than a simple join deserves.
If you publish courses somewhere else, it's entirely likely that owner
will be overwritten when the documents are merged on the client. This can be countered by appending a field like ownerUsername
but that would also require a more expensive observe.
This isn't helpful if you actually need the owner id somewhere on the client.
It isn't reactive if the username changes (probably rare but figured I'd point that out).
Non-Reactive Publish + Client Join
You could implement the publish like this:
CoffeeScript
Meteor.publish 'popularCourses', ->
courseCursor = Course.find()
userIds = courseCursor.map (c) -> c.owner
userCursor = Meteor.users.find {_id: $in: userIds}, {fields: username: 1}
[courseCursor, userCursor]
JavaScript
Meteor.publish('popularCourses', function() {
var courseCursor = Course.find();
var userIds = courseCursor.map(function(c) {return c.owner;});
var userCursor = Meteor.users.find(
{_id: {$in: userIds}},
{fields: {username: 1}
});
return [courseCursor, userCursor];
});
Note that I'm being careful to only publish the username
and _id
from userCursor
(you don't want to publish the hashed password and session data by accident). Then you can join the two collections on the client like this:
Template.myTemplate.helpers
courses: ->
Course.find().map (c) ->
c.owner = Meteor.users.findOne(c.owner)?.username
c
Advantages
Disadvantages
Finally, I'll point out that you can use a package to do a fully reactive join. However, unless the owner (or owner's username) is changing a lot then this is probably overkill.