Export result of a forEach function - meteor

I've got a forEach function on a collection
const docs = Docs.find({"owner": userId}, {fields: {"recipients": 1}});
docs.forEach(({ recipients }) => {
var docsRecipients = recipients;
//log #1
console.log(docsRecipients);
});
var docsRecipientsFinal = docsRecipients;
//log #2
console.log(docsRecipientsFinal);
The #1 console.log returns correct results, not the #2 (of course, this is just to be understood).
How can I export the result of the forEach function, for using it outside of the loop ? Thanks.

Assuming you want an array
const docs = Docs.find({"owner": userId}, {fields: {"recipients": 1}});
var docsRecipients = []; //define it outside the forEach scope
docs.forEach(({ recipients }) => {
docsRecipients.push(recipients);
console.log(docsRecipients);
});
var docsRecipientsFinal = docsRecipients;
//log #2
console.log(docsRecipientsFinal);
Update :
Just use
var docRecipients = Docs.find({"owner": userId}, {fields: {"recipients": 1}}).fetch();

In Meteor, there is no need to even iterate over the collection, just use the cursor's .fetch() method. It returns an array of documents.
const docs = Docs.find({
"owner": userId
}, {
fields: {
"recipients": 1
}
}).fetch();

the scope of docsRecipients is within the foreach block, to access the docsRecipients value declare it as a global variable above foreach,also this will store the last index value of foreach.
sample foreach
var docsRecipients="";
docs.forEach(({ recipients }) => {
docsRecipients = recipients;
//log #1
console.log(docsRecipients);
});
var docsRecipientsFinal = docsRecipients;
//log #2
console.log(docsRecipientsFinal);

Related

Populate the cloud firestore document containing an array of document references

I have a collection named campgrounds in which every document contains an array of document reference to the documents in the comments collections.
It looks like this Campground
I'm trying to figure out a way to populate this comments array before sending it to my ejs template.
My code looks like this
app.get("/campgrounds/:docId", function(req, res) {
var docRef = firestore.collection("campgrounds").doc(req.params.docId);
try {
docRef.get().then(doc => {
if (!doc.exists) {
res.send("no such document");
} else {
// res.send(doc.data());
res.render("campground", {
doc: doc.data(),
title: doc.data().title,
id: req.params.docId
});
}
});
} catch (error) {
res.send(error);
}
});
In your array you store DocumentReferences. If you want to get the data of the corresponding documents in order to include this data in your object you should use Promise.all() to execute the variable number (1 or more) of get() asynchronous operations.
The following should work (not tested at all however):
app.get("/campgrounds/:docId", function(req, res) {
var docRef = firestore.collection("campgrounds").doc(req.params.docId);
try {
var campground = {};
docRef.get()
.then(doc => {
if (!doc.exists) {
res.send("no such document");
} else {
campground = {
doc: doc.data(),
title: doc.data().title,
id: req.params.docId
};
var promises = [];
doc.data().comments.forEach((element, index) => {
promises.push(firestore.doc(element).get());
});
return Promise.all(promises);
}
})
.then(results => {
var comments = {};
results.forEach((element, index) => {
comments[index] = element.data().title //Let's imagine a comment has a title property
});
campground.comments = comments;
res.render("campground", campground);
})
} catch (error) {
res.send(error);
}
});
Note that with this code you are doing 1 + N queries (N being the length of the comments array). You could denormalize your data and directly store in the campground doc the data of the comments: you would then need only one query.

Publish users using id from a different collection

I'm trying to access the userIds stored in a collection and then use them to publish the details of all of the meteor.users. My publish function doesn't isn't return anything?
Meteor.publish('allUsersWithOffers', function () {
var user = Offers.find({}, {fields: {"UserId": 1}});
return Meteor.users.find({_id: user});
});
Give this a try:
Meteor.publish('allUsersWithOffers', function () {
var offers = Offers.find({}, { fields: { UserId: 1 } }).fetch();
var ids = _.pluck(offers, 'UserId');
// This is critical - you must limit the fields returned from
// the users collection! Update this as needed.
options = { fields: { username: 1, emails: 1 } };
return Meteor.users.find({ _id: { $in: ids } }, options);
});
find returns a cursor - you need to call fetch to actually get the documents.

Publish a virtual collection in meteor

I'm trying to publish a collection with 2 different names.
freeCourses contains courses without paid_url field.
premiumCourses contains all courses which id exist in userCourses collection.
userCourses collection :
{ user_id: "1", course_id: "1" }
Meteor.publish('freeCourses', function () {
this.added('freeCourses', Courses.find({}, {fields: {'Seasons.Episodes.paid_url': 0}}));
this.ready();
});
Meteor.publish('premiumCourses', function () {
//userPremiumCourses is 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;
}
);
this.added('premiumCourses', Courses.find({_id: {$in: userPremiumCourses}}));
this.ready();
});
if(Meteor.isClient){
Meteor.subscribe('freeCourses');
Meteor.subscribe('premiumCourses');
}
I want to get freeCourses and premiumCourses as two different collections on the client.
I've never seen this done before but if it was possible I believe you would need to define two collections that referred to the same underlying mongo collection:
freeCourses = new Mongo.collection('userCourses');
premiumCourses = new Mongo.collection('userCourses');
I just tested that and that fails.
A collection can have multiple publications each with its own query parameters and fields but it appears you want something more like a SQL view. That doesn't exist in Meteor afaik.
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 show documents from multiple remote publication in the template?

I wish to use Meteor to subscribe a few remote publication via DDP. Then show the documents in one template. Here is what I did:
Posts = {};
var lists = [
{server: "localhost:4000"},
{server: "localhost:5000"}
];
var startup = function () {
_.each(lists, function (list) {
var connection = DDP.connect(`http://${list.server}`);
Posts[`${list.server}`] = new Mongo.Collection('posts', {connection: connection});
connection.subscribe("allPosts");
});
}
startup();
This file is at client folder. Every startup, in this example, at browser I have two client collections Posts["localhost:4000"] and Posts["localhost:5000"], both are same schema. I know this format (Collection[server]) is ugly, please tell me if there is a better way.
Is there a way to show these client collections in the same template with reactive. Like this:
Template.registerHelper("posts", function () {
return Posts.find({}, {sort: {createdAt: -1}});
});
I think Connected Client is a big part of the Meteor. There should be a best practice to solve this problem, right?
Solved.
Connect to multiple servers via DDP, then observe their collections reactive via cursor.observeChanges.
Posts = {};
PostsHandle = {};
// LocalPosts is a local collection lived at browser.
LocalPosts = new Mongo.Collection(null); // null means local
// userId is generated by another Meteor app.
var lists = [
{server: "localhost:4000", userId: [
"hocm8Cd3SjztwtiBr",
"492WZqeqCxrDqfG5u"
]},
{server: "localhost:5000", userId: [
"X3oicwXho45xzmyc6",
"iZY4CdELFN9eQv5sa"
]}
];
var connect = function () {
_.each(lists, function (list) {
console.log("connect:", list.server, list.userId);
var connection = DDP.connect(`http://${list.server}`);
Posts[`${list.server}`] = new Mongo.Collection('posts', {connection: connection}); // 'posts' should be same with remote collection name.
PostsHandle[`${list.server}`] = connection.subscribe("posts", list.userId);
});
};
var observe = function () {
_.each(PostsHandle, function (handle, server) {
Tracker.autorun(function () {
if (handle.ready()) {
console.log(server, handle.ready());
// learn from http://docs.meteor.com/#/full/observe_changes
// thank you cursor.observeChanges
var cursor = Posts[server].find();
var cursorHandle = cursor.observeChanges({
added: function (id, post) {
console.log("added:", id, post);
piece._id = id; // sync post's _id
LocalPosts.insert(post);
},
removed: function (id) {
console.log("removed:", id);
LocalPosts.remove(id);
}
});
}
})
});
}
Template.posts.onCreated(function () {
connect(); // template level subscriptions
});
Template.posts.helpers({
posts: function () {
observe();
return LocalPosts.find({}, {sort: {createdAt: -1}}); // sort reactive
}
});

How to DRY up Template Helpers in meteor?

My Template helper has duplicated code:
Template.foodMenu.helpers({
breakfast: function() {
var breakfastItems = EatingTimes.find(// query for breakfast items);
// function to sort breakfastItems in here (code duplication)
},
lunch: function() {
var lunchItems = EatingTimes.find(// query for lunch items);
// function to sort lunchItems in here (code duplication)
},
dinner: function() {
var dinnerItems = EatingTimes.find(// query for dinner items);
// function to sort breakfastItems in here (code duplication)
}
);
I would like to DRY it up:
Template.foodMenu.helpers({
breakfast: function() {
var breakfastItems = EatingTimes.find(// query for breakfast items);
sortFoodItems(breakfastItems);
},
lunch: function() {
var lunchItems = EatingTimes.find(// query for lunch items);
sortFoodItems(lunchItems);
},
dinner: function() {
var dinnerItems = EatingTimes.find(// query for dinner items);
sortFoodItems(dinnerItems);
}
);
Where do I place this function so I can DRY up? How do I name space it so I can call it properly? I am using Iron Router if that makes a difference.
var sortFoodItems = function (foodItems) {
// code to sort out and return foodItems to particular method that calls it
};
Just define you function before helpers in the same file
var sortFoodItems = function (foodItems) {
// code to sort out and return foodItems to particular method that calls it
};
Template.foodMenu.helpers({
breakfast: function() {
var breakfastItems = EatingTimes.find(/* query for breakfast items */);
sortFoodItems(breakfastItems);
},
lunch: function() {
var lunchItems = EatingTimes.find(/* query for lunch items */);
sortFoodItems(lunchItems);
},
dinner: function() {
var dinnerItems = EatingTimes.find(/* query for dinner items */);
sortFoodItems(dinnerItems);
}
});
If you want to use sortFoodItems function in multiple files, create folder with name lib and put the function in file functions.js without var keyword to make it global. For example:
//lib/functions.js
sortFoodItems = function (foodItems) {
// code to sort out and return foodItems to particular method that calls it
};
You need to understand how Meteor reads your project directories. Read more about structuring your application in Meteor docs.
You could also create a global helper for this using Template.registerHelper(name, function) which would look like:
Template.registerHelper('menuItems', function(eatingTime, sortCriteria) {
//do some checking of your arguments
eatingTime = eatingTime || '/* your default eating time */';
sortCriteria = sortCriteria || {/* your default sort criteria */};
check(eatingTime, String);
check(sortCriteria, Object);
//find and sort your items in one mongo query or you could do separate find and sort if you want
menuItems = EatingTimes.find({time: eatingTime}, sortCriteria);
return menuItems;
});
This would be the most meteoric way of DRYing up your code.
and then in your template, you could call it as:
{{#each menuItems 'time args' 'sort arg'}}

Resources