I have 3 collections, meteor.user()
, Categories
and Posts
.
A user can choose to follow any category's posts by saving the id of the category in an array in meteor.user()
The end goal of this is that the user has a Personal stream where only the posts in the categories they are are following show up.
Each post inside the Posts collection
has within it an array of the Categories it's featured in.
and each category has within it an array of posts, each post in the array has a field of ID
of the post.
Where id: 67
is the id of the category, and posts.ID: 74
is the id of a post within the category.
below is the id of the post in Posts collection with matches posts.ID in the category collection
The User saves a category id so it appears as the category array in meteor.user()
When I do this
Template.userTimeline.helpers({
category: function() {
var posts = Category.find({
id: {
$in: Meteor.user().category
}
});
return posts
console.log(posts);
}
});
I can do this
{{#each category}}
<ul class="article-timeline">
{{#each posts}} {{ID}} {{/each}}
</ul>
{{/each}}
But this lets me access the posts.ID within the category collection and display it.
PROBLEM
How can I go further and match posts.ID I got with the above code, to the the id of a post in Post
so that I can access the posts from the Posts
collection. Could i do something like this?
Template.userTimeline.helpers({
category: function() {
var postsInCategory = Category.find({
id: {
$in: Meteor.user().category
}
});
var postMatch = Post.find({
ID: {
$in: postsInCategory
}
});
return postMatch
console.log(postMatch);
}
});
FURTHER EDIT AFTER ZIM ANSWER
So the solution you have given me is taking post.title
from the post object contained within my Categories
collection as shown below.
This post
array within the Category collection
is incomplete, its a copy of an original Posts Collection
what I need to do is use post.ID
as shown in the image above. i.e. the post array within the Categories Collection, to create a many to many relationship with the id
of a post within the original Posts
Collection so, instead of having post.title
I need to have title.rendered
as below.
Posts Collection.
If you see that the ID
(not the top id, which is the id of the category), post.ID
in the post array within the Categories collection is IDENTICAL to the id
of the post in Posts
collection. This is the crux of it all, help me create a relationship so that, instead of displaying title
or post.title
from the post array within the Categories
Collection, I need to display title.rendered
within the original Posts
Collection.
it sounds like you don't control the data format, so you would have to use a nested loop to deal with the format you have. i think the following should work.
Template.userTimeline.helpers({
categories: function() {
return Category.find({
id: {
$in: Meteor.user().category
}
});
}
});
note i changed the name of your helper.
{{#each category in categories}}
{{category.description}}
{{#each post in category.posts}}
{{post.title}}
{{/each}}
{{/each}}
c.f. http://blazejs.org/guide/spacebars.html#Each-in
EDIT:
ideally, you would be able to do a server-side join using a package like https://atmospherejs.com/reywood/publish-composite
but since you don't have control over that, you can do a client-side join. you would do this in the onCreated() of your template (caveat: this is untested!)
this.subscribe('posts', function() {
let cursor = Categories.find({id: {$in: Meteor.user().category}});
cursor.observe({
added: function(newDocument) {
let category = newDocument;
// for this category, get all the associated post ids
let postIds = _.map(category.posts, function(p)) {
return p.ID;
};
// get the posts. we can fetch these because we waited for the publish of the posts collection.
let actualPosts = Posts.find({id: {$in: postIds}}).fetch();
// attach the actual posts to the category
category.actualPosts = actualPosts;
},
changed: function(newDocument, oldDocument) {
// if wanting to track changes to the categories, similar to added block
}
});
});
then with a simple change to the template, you can grab those actual posts:
{{#each category in categories}}
{{category.description}}
{{#each post in category.actualPosts}}
{{post.title}}
{{/each}}
{{/each}}
doing the client-side joins is a little more tricky, because as the above is written, you're missing reactivity to the user's tracked categories, and posts w/in the existing categories. so you could further put all that in an autorun. that would track changes to Meteor.user().category, but not sure if that would track post changes w/in the categories. but this should get you a lot of the way there.
EDIT 2:
oh yeah, if you do it that way, that listener will stay active when the template goes away unless you call stop() on it. so...
Template.userTimeline.onDestroyed(function() {
let handle = this.observeHandle.get();
if (handle) {
handle.stop();
}
});
Template.userTimeline.onCreated(function() {
let self = this;
self.observeHandle = new ReactiveVar();
self.subscribe('posts', function() {
let cursor = Categories.find({id: {$in: Meteor.user().category}});
let handle = cursor.observe({
added: function(newDocument) {
let category = newDocument;
// for this category, get all the associated post ids
let postIds = _.map(category.posts, function(p)) {
return p.ID;
};
// get the posts
let actualPosts = Posts.find({id: {$in: postIds}}).fetch();
// attach the actual posts to the category
category.actualPosts = actualPosts;
},
changed: function(newDocument, oldDocument) {
// if wanting to track changes, similar to added block
}
});
self.observeHandle.set(handle);
});
});