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}}
Related
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();
}
});
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.
Is it possible to create new Meteor collections on-the-fly? I'd like to create foo_bar or bar_bar depending on some pathname which should be a global variable I suppose (so I can access it throughout my whole application).
Something like:
var prefix = window.location.pathname.replace(/^\/([^\/]*).*$/, '$1');
var Bar = new Meteor.Collection(prefix+'_bar');
The thing here is that I should get my prefix variable from URL, so if i declare it outside of if (Meteor.isClient) I get an error: ReferenceError: window is not defined. Is it possible to do something like that at all?
Edit : Using the first iteration of Akshats answer my project js : http://pastie.org/6411287
I'm not entirely certain this will work:
You need it in two pieces, the first to load collections you've set up before (on both the client and server)
var collections = {};
var mysettings = new Meteor.Collection('settings') //use your settings
//Startup
Collectionlist = mysettings.find({type:'collection'});
Collectionlist.forEach(function(doc) {
collections[doc.name] = new Meteor.Collection(doc.name);
})'
And you need a bit to add the collections on the server:
Meteor.methods({
'create_server_col' : function(collectionname) {
mysettings.insert({type:'collection', name: collectionname});
newcollections[collectionname] = new Collection(collectionname);
return true;
}
});
And you need to create them on the client:
//Create the collection:
Meteor.call('create_server_col', 'My New Collection Name', function(err,result) {
if(result) {
alert("Collection made");
}
else
{
console.log(err);
}
}
Again, this is all untested so I'm just giving it a shot hopefully it works.
EDIT
Perhaps the below should work, I've added a couple of checks to see if the collection exists first. Please could you run meteor reset before you use it to sort bugs from the code above:
var collections = {};
var mysettings = new Meteor.Collection('settings')
if (Meteor.isClient) {
Meteor.startup(function() {
Collectionlist = mysettings.find({type:'collection'});
Collectionlist.forEach(function(doc) {
eval("var "+doc.name+" = new Meteor.Collection("+doc.name+"));
});
});
Template.hello.greeting = function () {
return "Welcome to testColl.";
};
var collectionname=prompt("Enter a collection name to create:","collection name")
create_collection(collectionname);
function create_collection(name) {
Meteor.call('create_server_col', 'tempcoll', function(err,result) {
if(!err) {
if(result) {
//make sure name is safe
eval("var "+name+" = new Meteor.Collection('"+name+"'));
alert("Collection made");
console.log(result);
console.log(collections);
} else {
alert("This collection already exists");
}
}
else
{
alert("Error see console");
console.log(err);
}
});
}
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
Collectionlist = mysettings.find({type:'collection'});
Collectionlist.forEach(function(doc) {
collections[doc.name] = new Meteor.Collection(doc.name);
});
});
Meteor.methods({
'create_server_col' : function(collectionname) {
if(!mysettings.findOne({type:'collection', name: collectionname})) {
mysettings.insert({type:'collection', name: collectionname});
collections[collectionname] = new Meteor.Collection(collectionname);
return true;
}
else
{
return false; //Collection already exists
}
}
});
}
Also make sure your names are javascript escaped.
Things got much easier:
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
db.createCollection("COLLECTION_NAME", (err, res) => {
console.log(res);
});
Run this in your server method.
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);
};
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();
}
});