Reputation: 973
I'm having the following data structure in my Meteor project:
- Users with a set of list-ids that belong to the user (author)
- Lists that actually contain all the data of the list
Now I'm trying to publish all Lists of a user to the client. Here is a simple example:
if (Meteor.isClient) {
Lists = new Meteor.Collection("lists");
Deps.autorun(function() {
Meteor.subscribe("lists");
});
Template.hello.greeting = function () {
return "Test";
};
Template.hello.events({
'click input' : function () {
if (typeof console !== 'undefined')
console.log(Lists.find());
}
});
}
if (Meteor.isServer) {
Lists = new Meteor.Collection("lists");
Meteor.startup(function () {
if ( Meteor.users.find().count() === 0 ) {
Accounts.createUser({ //create new user
username: 'test',
email: '[email protected]',
password: 'test'
});
//add list to Lists and id of the list to user
var user = Meteor.users.findOne({'emails.address' : '[email protected]', username : 'test'});
var listid = new Meteor.Collection.ObjectID().valueOf();
Meteor.users.update(user._id, {$addToSet : {lists : listid}});
Lists.insert({_id : listid, data : 'content'});
}
});
Meteor.publish("lists", function(){
var UserListIdsCursor = Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
if(UserListIdsCursor!=undefined){
var UserListIds = UserListIdsCursor.fetch();
return Lists.find({_id : { $in : UserListIds}});
}
});
Meteor.publish("mylists", function(){
return Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
});
//at the moment everything is allowed
Lists.allow({
insert : function(userID)
{
return true;
},
update : function(userID)
{
return true;
},
remove : function(userID)
{
return true;
}
});
}
But publishing the Lists doesn't work properly. Any ideas how to fix this? I'm also publishing "mylists" to guarantee that the user has access to the field "lists".
Upvotes: 1
Views: 3249
Reputation: 64312
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: '[email protected]',
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}});
});
}
Lists
collection outside of the client and server (no need to declare it twice).lists
publish function.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 a fetch
or a findOne
). Important note - this publishes another user document which has the lists
field. On the client it will be merged with the existing user document, so only the logged in user will have lists
. 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.
Upvotes: 3
Reputation: 8865
The real problem here is the schema.
Don't store "this user owns these lists" eg, against the users collection. Store "this list is owned by this user"
By changing your example to include an ownerId
field on each List
then publishing becomes easy - and reactive.
It also removes the need for the myLists
publication, as you can just query client side for your lists.
Edit: If your schema also includes a userIds
field on each List
then publishing is also trivial for non-owners.
Lists = new Meteor.Collection('lists');
if (Meteor.isClient) {
Deps.autorun(function() {
if (Meteor.userId()) {
Meteor.subscribe('lists.owner');
Meteor.subscribe('lists.user');
}
});
}
if (Meteor.isServer) {
Lists._ensureIndex('userIds');
Lists._ensureIndex('ownerId');
Meteor.startup(function() {
if (Meteor.users.find().count() === 0) {
var user = {
username: 'test',
email: '[email protected]',
password: 'test'
};
var userId = Accounts.createUser(user);
var listId = Lists.insert({data: 'content', ownerId: userId});
}
});
//XX- Oplog tailing in 0.7 doesn't support $ operators - split into two publications -
// or change the schema to include the ownerId in the userIds list
Meteor.publish('lists.user', function() {
check(this.userId, String);
return Lists.find({userIds: this.userId});
});
Meteor.publish('lists.owner', function() {
check(this.userId, String);
return Lists.find({ownerId: this.userId});
});
}
Upvotes: 2
Reputation: 75945
Meteor.users.find() returns a cursor of many items but you're trying to access .lists
with
Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
You need to use findOne instead.
Meteor.users.findOne({_id: this.userId}).lists;
Additionally you're running .fetch()
on an array which is stored in the user collection. If this is an array of ._id fields you don't need fetch.
You can't also do .lists
in your second publish because its a cursor you have to check lists client side so just use Meteor.users.find(..) on its own since you can only publish cursors.
Upvotes: 0