Creating a many-to-many relationship between 2 mongodb collections by ids - javascript

I have two collections. One is called Posts and the other is called Categories. In the posts collection, are individual posts that each contain an array of id's called categories which are id's of the categories the individual post belongs to stored in the posts.
The second collection is the Categories collection which contains the categories each of the posts belong to
Goal
In my template, I am displaying each post as its title, content, image and author as well as the Category names which come about by linking the Category Ids in the Posts collection to the individual categories in the Category collection
<template name="latest">
{{#each posts}}
<div>{{> category}}</div>
<h5 class="latest-title">{{title.rendered}}</h5>
<img class="latest-img" src="{{featured_image_thumbnail_url}}" alt="" />
{{/each}}
</template>
In my category template
<template name="category">
{{#each categories}}
{{name}}
{{/each}}
</template>
In my category.js
Template.category.helpers({
categories(){
return CategoryCollection.find({ id: parseInt(this.categories) });
}
});
As you can see, I want to display the names of the categories belonging to a post, they are in an array because a post might have 3 categories it belongs too. but I can't seem to get it to work.
Edit
This is my edit to include the $in
Template.category.helpers({
categories(){
return CategoryCollection.find({ id: {$in: this.categories }});
}
});
And this is my template
<template name="category">
{{#each categories}}
{{name}}
{{/each}}
</template>
It doesn't seem to be working.
Progress
It wasn't working because I hadn't assigned categories to my example post, the above edited code is the answer

Another solution I'm using for this kind of stuff is just showing the categories in the array of that post, without even a helper. Here is what I do...
Inside the method:
//the categories is what you pass from the client as an array of strings
//IE: ['cat1', 'cat2']
categories.forEach(function(cc){
const cat = Categories.findOne({name: cc});
if(!cat){
Categories.insert({
name: cc,
count: 1
});
} else {
//increment count if it exists
Categories.update({name: cc}, {
$inc:{
count: 1
}
});
}
});
The reason I insert with count 1 and increment the count if category exists is for multiple different use cases such as:
Showing existing categories on search so that it always returns existing documents
In case of edit/delete post, if count == 1, delete the category if count > 1, decrement count of that category.
Showing a reference of existing categories when a user is adding/editing posts. Depending on what they write on the input I return Category recommendations with a regex.
In the post, just show the category names from the post. No need to find category.
<!--
see that its {{this}} not {{name}} because
we saved the categories to the post as an array of strings
we'd do {{name}} if we were looping through the categories collection
-->
{{#each categories}}
{{this}}
{{/each}}
If you need to access the category from the DB in the post like click events, you can do a quick Categories.findOne({name: this.name})
I see that you save other stuff to the Category collection which I'd probably save to the post itself and some, I wouldn't even save and generate the necessary stuff client side if they're like links etc.

You want to use the $in operator:
return CategoryCollection.find({ id: {$in: this.categories }});

Related

Ember JS and Iterating without effecting the count of the results

I'm using Ember JS and i don't know if my approach to achieve the thing i want is true or maybe there is another way. If there is a better way please tell me.
Now I have a model named "visit" among the data it stores is the "time" of the visit
Now the time is set through a hard-coded service loop in select tag.
I want the user to select the date of the visit "a query is sent to the database with the selected date to get the visits in that date" then I want to compare the time in the results of the query with the array provided in my service and remove the similar one's. Ergo the pre-selected time isn't available.
I tried to loop in the template for the service data then another loop for the "visit" query and added an "if" statement in the class of the time display to add "disabled" if the two values equal each other.
The problem here is that the loop shows me the data twice now or maybe thrice affected by the results found in the query.
I think there is another approach by simply handling the data in the "visit" query to simply remove the matched data from the service and the results, but I'm not sure how to do so.
Here is my template
{{#each formData.time as |time|}}
{{#each disabledTime as |disabled|}}
{{#if (eq disabled.value time.value)}}
<button class="disabled">{{time.time}}</button>
{{else}}
<button {{action 'addTime' time}} class="btn-success">{{time.time}}</button>
{{/if}}
{{/each}}
{{/each}}
And Here is my controller
formData : Ember.inject.service(),
store : Ember.inject.service(),
disabledTime : Ember.computed("model.issueDate", function(){
return this.get('store').query('visit', { where : { issueDate : this.get('model.issueDate') } } );
}),
Is there a better way to handle the data in the "disabledTime" so I take the data from the service "formData.time" and remove the similar data then return the data that doesn't match. Because this way it looks simpler and I can make the loop in the template through a "select" tag instead of the buttons.
If your query return an array, and your FormatDate.time too, what about setDiff ?
you would have something like :
formData: Ember.inject.service(),
store: Ember.inject.service(),
disabledTime: Ember.computed("model.issueDate", function(){
return this.get('store').query('visit', { where : { issueDate : this.get('model.issueDate') } } );
}),
availableTime: Ember.setDiff('formData.times', 'disabledTime')
and use it in your template
<select>
{{#each availableTimes as |time|}}
<option value=time.time>{{time.time}}</option>
{{/each}}
</select>
As i understand, you have two arrays of objects, which share a property value. You now want to filter the formData.time list to objects with a value not present in the disabledTime list.
Instead of looping always completely over the second list, for each object in the first list, you could filter formData.time beforehand:
// computed based on contents in formData.time and disabledTime
filteredTimes: Ember.computed('{disabledTime.[],formData.time.[]}', function() {
let disabled = Ember.get(this, 'disabledTimes');
// return a filtered formData.time
return Ember.get(this, 'formData.time').filter(time => {
let value = Ember.get(time, 'value');
// inclued only if value not present in disabledTime list
return !disabled.isAny('value', value));
});
})
This assumes that if an object exists only in disabledItem it can be ignored. If not so, you'd have to merge, not filter the lists (i.e. return a new list with disabled flag).

Meteor: How can I create a many-to-many relationship between two collections linked by ids?

I have two collections. One is called Posts and the other is called Categories. In the Posts collection, are individual posts which have an id. Shown in the picture as id: 1291, for this particular post, which is an integer, not a string.
The second collection is the Categories collection which contains the categories each of the posts belong to, most importantly, within each category in the Categories collection is an array of the posts that belong to that category. Pay attention to posts.ID which in this case is ID: 1291, This ID is the post id in the Posts collection
Summery.
There is a Posts collection which has individual posts. Those
individual posts belong to different categories.
There is also the Categories collection which has the individual categories.
In each category is an array of the posts that belong to
that category but with few post objects I can utilise.
THE PROBLEM
I have a flow that goes like this.
The user scrolls through a list of categories and clicks on a category from the Categories collection with the goal of viewing the posts within that category.
<template name="category">
{{#each categories}}
<span class="title">{{name}}</span>
{{/each}}
</template>
As it stands, When the user performs this action, they view the posts that are already in category that belongs to the Categories collection (remember the posts array within the category collection) like this
Template.singleCategory.helpers({
categories() {
var id = FlowRouter.getParam('_id');
return Categories.find({
_id: id
}, {
sort: {
timestamp: 1
},
limit: 100
});
}
});
But these posts in the Categories collection do not have certain key objects found in the Posts collection, plus I want to get rid on the posts in the categories.
Goal
Instead of using these posts found in the Categories collection, I would like to use the posts found in the Posts collection and I would like to create the relationship using the similar Ids they share. So when a user clicks on a category, the posts they should see should come from the Posts collection, linked by the post ID in the Categories collection which is identical to the post id in the Posts collection.
Code
Here is the code. I'm trying to say the following:
In this single category being viewed by the user after they clicked it from the list of other categories, do not show the posts embedded in the Categories collection, Instead, return the Posts collection. In this Posts collection, find each post id, then match it to ID from category.posts within the Categories collection, then Show the
objects from Posts collection, not category.posts.
Template.singleCategory.helpers({
posts(){
return Posts.find({ ID: {$in: this.posts }});
}
});
However, I cannot seem to get it to work. I'm getting the error
Exception in template helper: Error: $in needs an array
the posts in the category collection are an array.
how can I solve this?
Edited
Building on the code in the answer section I had to treat category.posts so its an array. I'm getting the results I want, well, to an extent, some posts are not coming through. I wonder, could this code be better?
Template.Category.helpers({
categories(){
var id = FlowRouter.getParam('_id');
//create a variable with the correct category by id
var category = Category.findOne(id, {
sort: {
timestamp: 1
},
limit: 100
});
console.log(category);
//target ID within posts
var postArray = category.posts;
console.log(postArray);
//treat the object, clean it up to prepare it for becoming an array
var postArrayStr = JSON.stringify(postArray).replace(/ID/g, '').replace(/\[|\]/g, '').replace(/""/g, '').replace(/:/g, '').replace(/\{|\}/gi, '');
//create an array
var idsArray = postArrayStr.split(',').map(function(item) {
return parseInt(item, 10);
});
//match it to posts id
var matchi = Posts.find({
id: {
$in: idsArray
}
}).fetch();
//
console.log(matchi);
//return it
return matchi;
}
});
What I would do is having in the category collection an array with just the post mongo's _id field (which is unique).
Then in the helper you can query the post collection to return all the posts with those ids.
So it would be something like this:
Template.singleCategory.helpers({
posts(){
var id = FlowRouter.getParam('_id');
var category = Category.findOne(id, {sort: {timestamp: 1},limit: 100 })
var postArray = category.posts
// example --> ["rCBWsKLCAWghBRJg4", "yMDtnDSo43ZDEcqpu", "cQeJntQtvwiHpgdZ9"]
return Posts.find( {_id: {$in: postArray}});
// it will return an array oj objects where each object is a post from the posts collection : [Object, Object, Object]
}
});
There are better way to do it but I think this solution may fix your problem without change too many things

How to pass data between Meteor Template Helpers?

Meteor Newbie here!
I have a page where all the open orders are displayed. The order details are stored in a collection. A Template helper will return the order details.
Template.delivery.helpers({
openOrders: function(){
return Orders.find({status: "open"});
}
});
The template look some what like this.
{{#if openOrders}}
{{#each openOrders}}
Date: {{createdAt}}
Total items: {{items.length}}
Location: {{location}} //It prints the location _id
{{/each}}
{{/if}}
The Order Collection only have the _id of the location. The Location details are stored in a Collection named Locations.
I want to display the location name (which is stored in the Location collection) instead of the _id.
I created a Template helper which returns the Location details, but how can I link these to helpers so that the Location name is displayed instead of Location _id?
As you're using mongodb in a relational database fashion, you need to install publish-composite package to make sure all the necessary data are subscribed.
When you use each, it will set the this to the current thing that is being iterated over. This will allow you to use this in your helper to perform lookups. So in this case, if you're using a helper to get the orders:
orders: function () {
return Orders.find({ status: "orders" });
}
Then when you iterate over it with {{#each}}, this is set to the current order, meaning your location helper with look like this:
location: function () {
return Locations.findOne(this.locationId);
}
Putting it all together in the template it would be like:
{{#if Template.subscriptionsReady}}
{{#each orders}}
<h1 class="title">Order #{{_id}}</h1>
{{#with location}}
<div class="location">
<span>Latitude: {{ latitude }}</span>
</div>
{{/with}}
{{/each}}
{{/if}}
Keep in mind: this will only work if you also publish your locations.

How to get a single record to the template from model in emberjs

I'm new to ember js. I was trying to get a single record from my mongo db database but the query to server returns the full list view instead of a record.
templates/profile.hbs
{{#each item in model}}
{{item.userName}}
{{/each}}
routes/profile.hbs
export default Ember.Route.extend({
model: function() {
return this.store.find('user',{userName:'sanka'});
}
});
New syntax for printing arrays is:
{{#each model as |item|}}
{{item.userName}}
{{/each}}
Does your backend have a support for this kind of filter? Go check your network tab. Otherwise we won't be able to tell what your issue is. But it really looks as if you dont have support in your mongoDB. Because you're actually retrievering records, so it looks like the filter is "broken" or "non-existing"
To query for a single record that is not in an array you should be using
findRecord(type,id)
peekRecord(type, id)
queryRecord(type, {query})
http://emberjs.com/blog/2015/06/18/ember-data-1-13-released.html

Ember.js: Passing arguments to render helper

Say I have two objects in my application: 1) Topic, 2) Categories.
Topic has a route /topic/:topic_id
Categories doesn't have a URL route. It contains the list of all available categories that topics can be in.
On the page for Topic, I would like to show a list of all categories, while highlighting the category that the topic is a part of (the 'active' category)
Original Solution
I had originally solved this with a component as follows (a lot of code removed for brevity):
Component Template:
(Goes trough each category and lists them. If the current one is active, it says so.)
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{#if this.active}} Active -> {{/if}}
{{this.name}}
<br />
{{/each}}
</script>
Component Object:
(Goes through the models via the CategoriesController, marks one category as the current active category for that topic via a "current" parameter passed in from the {{category-list}} component helper)
App.CategoryListComponent = Ember.Component.extend({
tagName: '',
didInsertElement: function () {
var categories = App.CategoriesController;
var current = this.get('current').get('id');
categories.get('content').forEach(function (c) {
if (c.id === current) {
c.active = true;
}
});
this.set('categories', categories);
}.observes('current')
});
Displaying the Category List:
In the Topic view ('category' is the property of Topic that says what category the topic is in):
{{category-list current=category}}
This all works, but I have a feeling that it is not the correct way to go about solving the problem. Components strike me as something that should be very reusable, and this really only is just an encapsulated part of one view.
Second Attempt
Now I am trying to use the category list as a view. So now instead of just a Component, I have a CategoriesRoute (not hooked up in the router though), a CategoriesController, and a CagetoriesView.
When the Topic resource loads up, it sets up the categories controller with the loaded model data:
App.TopicRoute = Ember.Route.extend({
model: function (params) {
return Em.RSVP.hash({
topic: this.store.find('topic', params.topic_id),
categories: this.store.find('category')
});
},
setupController: function (controller, context) {
this._super(controller, context.topic);
this.controllerFor('categories').set('model', context.categories);
}
});
The CategoriesController is just a standard array controller:
App.CategoriesController = Ember.ArrayController.extend({});
The CategoriesView is also very simple:
App.CategoriesView = Ember.View.extend({
templateName: 'categories',
tagName: ''
});
And finally the categories template:
<script type="text/x-handlebars" id="components/category-list">
{{#each categories}}
{{this.name}}
<br />
{{/each}}
</script>
I am rendering the list with the {{render}} helper in the Topic template:
{{render "categories" }}
The Problem:
The above seems to work fine in terms of showing the list of categories. However, I can't figure out how to tell the view which category is active (the category in which the topic is in).
I looked around on the internet, and it looks like the {{render}} helper used to allow for an optional options hash (which is how I solved the problem with a Component in my first attempt), but it seems to have been removed in some recent version of Ember.
I found a StackOverflow entry that mentioned making my own {{render}} helper, but it looks like that is to dynamically change models, or something of that nature. I'd like to keep the models backing for the categories view, I just need to be able to tell it which category is active via the Topic.category property.
Or, was my first attempt the better solution? I am very new to Ember, so I'm not sure what would be the best strategy here. My instinct tells me I should use my second attempt rather than the first, but I am not positive.
Any help is very appreciated.
You are right in saying that components must be re-usable everywhere and should not be tied to any particular controller. I would use a view for this. The way I would do is, I would have a
App.CategoriesController = Em.ArrayController.extend({
itemController: 'category',
//some other stuff here
});
for categories and then have an itemController called
App.CategoryController = Em.ObjectController.extend({
setActiveState: function() {
if (this.get('parentController.current.id') === this.get('content.id')) {
this.set('active', true);
}
}
});
and in the template, you could say
{{#each category in categories}}
{{category.name}}
{{/each}}

Categories