Multiple method calls on Collection.observe() event in Meteorjs - mongodb

I have a 1 cursor to calculate total number of voters. I have two methods where first counts the total number of voters in my favour, second method counts the total number of voters done voting.
LIB/collection.js
infavourcount = new Mongo.Collection('infavourcount');
votedone = new Mongo.Collection('votedone');
SERVER/publish.js [count voters in my favour]
function upsertInFavourCount() {
var yes = voters.find({favour: {$regex:"Y", $options: 'i'}}).count();
var maybe = voters.find({favour: {$regex:"M", $options: 'i'}}).count();
var no = total - (yes + maybe);
infavourcount.upsert('infavour',
{
yes: yes ,
maybe: maybe,
no:no
}
);
}
// null name means send to all clients
Meteor.publish(null,function() {
upsertInFavourCount();
return infavourcount.find();
});
SERVER/publish.js [count successful votings]
function upsertVoteDone() {
var done = voters.find({voted: {$regex:"Y", $options: 'i'}}).count();
votedone.upsert('votedone',
{
done: done
}
);
}
Meteor.publish(null,function() {
upsertVoteDone();
return votedone.find();
});
var cursor = voters.find();
cursor.observe({
changed: upsertVoteDone
});
CLIENT/template/home.js
Template.home.onCreated(function(){
Meteor.subscribe('voters');
Meteor.subscribe('infavourcount');
Meteor.subscribe('votedone');
});
Template.home.helpers({
yesvote : function() {
return infavourcount.findOne().yes;
},
maybevote : function() {
return infavourcount.findOne().maybe;
},
novote : function() {
return infavourcount.findOne().no;
},
votedone : function() {
return votedone.findOne().done;
}
});
My problem is how to call multiple upsert methods in one observe method of Meteor published collection.

I got my solution by the way. The answer to this is add an function on "changed" or "added" or "removed" event and call n number of upsert methods therein.
cursor.observe({
changed: function(id, fields){
upsertInFavourCount();
upsertVoteDone();
}
});

Related

Recursive Publishing - Angular-meteor

I am wanting to load a topic, 25 of its comments and up to 5 sub comments for each comment, repeated recursively over each comment/sub-comment until all related comments are found.
I'm currently using an angular directive to recursively subscribe and add to the local collection whenever the comment has children. It works quite well, but there is some lag (to be expected) between loading the initial 25 comments, and loading their children, then their children and so on.
This issue isn't a problem when just loading a page at a time. It becomes an issue when using infinite scrolling and increasing that initial 25 comment limit. It will cause the page to jump up and down a bit as the sub comments disappear and reappear once loaded again.
I was wondering how I could recursively look up all comments prior to sending to the local client so I don't need to make more than one round trip for each topic.
I have a demo loaded up at ck-gaming.com
If you scroll to the bottom it will load more and you'll see it jump all over as the sub comments are reloaded into the page.
The two options I can think of would be to use a resolve to wait for all collections prior to loading the page or using recursive publish to get them all first.
Thoughts? Ideas?
Ok, my first attempt that I would like some thoughts on if possible.
For the publishing I decided to go with publish-composite to make publishing from the same collection easier.
for the publication I wrote:
Meteor.publishComposite('oneDiscussion', function (slug, options) {
var query = {};
query.find = function () {
return Discussions.find({ slug: slug }, { limit: 1 });
};
var mainChildQuery = Comments.find({ slug: slug }, { limit: 1 });
query.children = [];
query.children[0] = {};
query.children[0].find = function (discussion) {
return mainChildQuery;
};
query.children[0].children = [];
query.children[0].children[0] = {};
query.children[0].children[0].find = function (comment) {
return Meteor.users.find({ _id: comment.author.id }, { limit: 1, fields: { profile: 1, roles: 1, createdAt: 1, username: 1 } });
};
query.children[0].children[1] = {};
query.children[0].children[1].find = function (parent) {
Counts.publish(this, 'numberOfComments', Comments.find(
{ parent_id: parent._id }
), { noReady: true });
console.log(options)
return Comments.find({ parent_id: parent._id }, options);
};
// var parentQuery = Comments.find({ slug: slug });
var parent = mainChildQuery.fetch();
var children = Comments.find({ parent_id: parent[0]._id }, { limit: 25 }).fetch();
var childrenIds = _.pluck(children, '_id');
var getChildren = function (children_ids, thisParent) {
var i = 0;
thisParent.children = [];
var recursive = function getEm(children, parent) {
_.each(children, function (id) {
// parent.children[i] = new Children(id);
var query = Comments.find({ parent_id: id }, { limit: 5, sort: { date: -1 } });
parent.children[i] = {
find: function () {
return Comments.find({ parent_id: id }, { limit: 5, sort: { date: -1 } });
}
};
var children1 = query.fetch();
var newChildrenIds = _.pluck(children1, '_id');
i++;
if (newChildrenIds.length > 0) {
getEm(newChildrenIds, parent);
}
});
}
recursive(children_ids, thisParent);
};
getChildren(childrenIds, query.children[0].children[1]);
return query;
});
Seems to be working ok so far, though running it on my desktop it's not as performant as I would think it should be. I'll deploy it and see if there's a difference online. I'll update when I get home and can update the live site. If anyone can find something wrong with what I've written it would be much appreciated.
I've came up with what I think is the best solution. I improved on the function above and so far I'm really enjoying the results.
Here is the publish function:
Meteor.publishComposite('comments', function (item_id, options) {
/**
* TODO: Add query to find a user for each comment.
*/
/**
* Start building our query.
* Add the latest 25 (depending on options) child comments of the viewed item
* to the query.
*/
var query = {
find: function () {
return Comments.find({ parent_id: item_id }, options);
}
};
// Query the database for the first 25? comments, we'll need their _id's
var mainChildren = Comments.find({ parent_id: item_id }, options).fetch();
// pluck the id's from the initial comments
var mainChildrenIds = _.pluck(mainChildren, '_id');
/**
* Builds the remaining query based on the id's plucked from the children
* above.
* #param childrens_id The id's we just plucked from the above query
* #param thisParent This is the parent query
*/
var getChildren = function (children_ids, parentQuery) {
// initiate i to 0
var i = 0;
// add a child array to the current parent query.
parentQuery.children = [];
var recursive = function getem(children, parent) {
_.each(children, function (id) {
var query = Comments.find({ parent_id: id }, { limit: 5, sort: { date: 1 } });
parent.children[i] = {
find: function () {
return query;
}
};
var children1 = query.fetch();
var newChildrenIds = _.pluck(children1, '_id');
i++;
if (newChildrenIds.length > 0) {
getem(newChildrenIds, parent);
}
});
};
// recursively build the query if there are children found.
recursive(children_ids, parentQuery);
};
// initiate the query build function
getChildren(mainChildrenIds, query);
return query;
});
I created an example app you can get on GitHub here
And you can view it running on meteorpad here
what it does
All the function does is build the publishComposite query, recursively looping over the children id's, as long as there are children id's. When there are no more children it stops.
to use it
You want a collection of comments (or whatever you're nesting) that have a parent_id field. This field will be filled with either the parent post Id, parent Item id (if say making a store with reviews/comments). The parent post id would of course be the comment or post you are commenting on. See the example for more information.

Error with count of docs where criteria applies to embedded array

I have a mongoDB collection called Assignments, which have multiple bids (embedded arrays). When one of those bids are set as accepted:true, they are considered an accepted_bid.
I want a function which returns all the docs (or a count of docs) that have one bid out of many (embedded arrays), which are owned by the logged in user.
The following does not work. I'm looking to have {{stats.count}} in the HTML file.
Template.dashboard.stats = function() {
return Assignments.find({completed:true}, {
transform: function(doc) {
if(doc.bids) {
var accepted_bid = _(doc.bids).findWhere({owner:Meteor.userId(),accepted:true});
doc.bid = accepted_bid;
}
return doc;
}
});
};
Im not sure if this would work but it returns a count:
Template.dashboard.helpers({
stats: function() {
var assignments = Assignments.find({
completed: true
}, {
transform: function(doc) {
if (doc.bids) {
var accepted_bid = _(doc.bids).findWhere({
owner: Meteor.userId(),
accepted: true
});
if(accepted_bid) doc.bid = accepted_bid;
}
return doc;
}
}).fetch();
return _(assignments).chain().pluck("bid").compact().value().length;
}
});
It can be used with {{stats}}

Add new data from restful api to angularjs scope

I'm trying to create a list with endless scroll in angularjs. For this I need to fetch new data from an api and then append it to the existing results of a scope in angularjs. I have tried several methods, but none of them worked so far.
Currently this is my controller:
userControllers.controller('userListCtrl', ['$scope', 'User',
function($scope, User) {
$scope.users = User.query();
$scope.$watch('users');
$scope.orderProp = 'name';
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
var promise = user.query();
$scope.users = $scope.users.concat(promise);
}
}, false);
}
]);
And this is my service:
userServices.factory('User', ['$resource',
function($resource) {
return $resource('api/users', {}, {
query: {
method: 'GET',
isArray: true
}
});
}
]);
How do I append new results to the scope instead of replacing the old ones?
I think you may need to use $scope.apply()
When the promise returns, because it isnt
Part of the angular execution loop.
Try something like:
User.query().then(function(){
$scope.apply(function(result){
// concat new users
});
});
The following code did the trick:
$scope.fetch = function() {
// Use User.query().$promise.then(...) to parse the results
User.query().$promise.then(function(result) {
for(var i in result) {
// There is more data in the result than just the users, so check types.
if(result[i] instanceof User) {
// Never concat and set the results, just append them.
$scope.users.push(result[i]);
}
}
});
};
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
$scope.fetch();
}
}, false);

Does Meteor have a distinct query for collections?

I'd like to return distinct fields in my collection. I know these are the docs for mongo operators, but I'm not familiar enough with the query language to know if this is possible?
Meteor.publish("distinctCollection", function() {
return Collection.find({ ... }, fields: { "myField": 1 });
});
Collection.find({}).distinct('myField', true);
To use, put the following in [project]/client/lib/a.js:
LocalCollection.Cursor.prototype.distinct = function (key,random) {
var self = this;
if (self.db_objects === null)
self.db_objects = self._getRawObjects(true);
if (random)
self.db_objects = _.shuffle(self.db_objects);
if (self.reactive)
self._markAsReactive({ordered: true,
added: true,
removed: true,
changed: true,
moved: true});
var res = {};
_.each(self.db_objects,function(value){
if(!res[value[key]]){
res[value[key]] = value;
}
});
return _.values(res);
};

Using $inc to increment a document property with Mongoose

I would like to increment the views count by 1 each time my document is accessed. So far, my code is:
Document
.find({})
.sort('date', -1)
.limit(limit)
.exec();
Where does $inc fit in here?
Never used mongoose but quickly looking over the docs here it seems like this will work for you:
# create query conditions and update variables
var conditions = { },
update = { $inc: { views: 1 }};
# update documents matching condition
Model.update(conditions, update).limit(limit).sort('date', -1).exec();
Cheers and good luck!
I ran into another problem, which is kind of related to $inc.. So I'll post it here as it might help somebody else. I have the following code:
var Schema = require('models/schema.js');
var exports = module.exports = {};
exports.increase = function(id, key, amount, callback){
Schema.findByIdAndUpdate(id, { $inc: { key: amount }}, function(err, data){
//error handling
}
}
from a different module I would call something like
var saver = require('./saver.js');
saver.increase('555f49f1f9e81ecaf14f4748', 'counter', 1, function(err,data){
//error handling
}
However, this would not increase the desired counter. Apparently it is not allowed to directly pass the key into the update object. This has something to do with the syntax for string literals in object field names. The solution was to define the update object like this:
exports.increase = function(id, key, amount, callback){
var update = {};
update['$inc'] = {};
update['$inc'][key] = amount;
Schema.findByIdAndUpdate(id, update, function(err, data){
//error handling
}
}
Works for me (mongoose 5.7)
blogRouter.put("/:id", async (request, response) => {
try {
const updatedBlog = await Blog.findByIdAndUpdate(
request.params.id,
{
$inc: { likes: 1 }
},
{ new: true } //to return the new document
);
response.json(updatedBlog);
} catch (error) {
response.status(400).end();
}
});