Getting the the last items in a Meteor Collection - meteor

I've got a Threads collection and I'd like to get the last post in each thread and publish those.
Anyone know the right query to do this?
I tried to do something like this, but it did not work. since the return value isn't a cursor.
var messages = Messages.find({ threadId: { $in: threadIds }});
// We found the messages. Now retrieve the last ones per thread.
return _.map(threadIds, function(threadId) {
console.log('Get %s', threadId);
Messages.find({ threadId: threadId }, {
limit: 1, sort: { createdAt: -1 }
});
});

I believe this solution is working for me.
I do have one gripe, which is that I'm not sure if the method will run if a new message is added to a thread.
var messages = Messages.find({ threadId: { $in: threadIds }});
// We found the messages. Now retrieve the last ones per thread.
var messageCursors = [];
_.each(threadIds, function(threadId) {
var lastMessage = Messages.find({ threadId: threadId }, {
sort: { createdAt: -1 }, limit: 1
});
messageCursors.push(lastMessage);
});
return messageCursors;

Related

How to access server aggregate on the client side within meteor

Bit of a noob question. I'm using meteor-native-mongo on the server to access the aggregate function in MongoDB, however, I'm not sure how I return and access the results on the client side. In the past subscribing and then accessing the collections on the client was pretty straightforward using the collection.find({}) function, however, I don't understand how to do it with the aggregate function. Can someone please explain.
Meteor.publish('companies', function(limit) {
db.collection('companies').aggregate([{ $group: { _id: { location: "$google_maps.geometry_location" }, companies: { $addToSet: { name: "$company_name" } }, count: { $sum: 1} } }, { $match: { count: { $gt: 1 } } }]).toArray((err, result) => {
console.log(result);
return result;
});
});
Use this.added, this.changed, this.removed from https://docs.meteor.com/api/pubsub.html#Subscription-added ...
Meteor.publish('companies', function(limit) {
var subscription = this;
db.collection('companies').aggregate([{ $group: { _id: { location: "$google_maps.geometry_location" }, companies: { $addToSet: { name: "$company_name" } }, count: { $sum: 1} } }, { $match: { count: { $gt: 1 } } }]).toArray((err, result) => {
subscription.added('companies-aggregate', 'geometry-grouping', {result: result});
});
});
// On the client:
var CompaniesAggregate = new Mongo.Collection('companies-aggregate');
// Inside a reactive context, like a helper
var result = CompaniesAggregate.findOne('geometry-grouping').result;
Naturally, to make it reactive, you'd have to know when the results of the aggregations would change. There is no automatic way to do that--you would have to resolve that logically, with your own code.
The best way to do that is to save the subscription variable in an array somewhere in a higher scope, and called changed on all the subscriptions for 'companies' for the geometry-grouping document, computing an updated result.
The commenter's solution won't be realtime; in other words, if one user makes a change to the e.g. company name or location, another user won't see those changes.

Meteor observe not working properly in 1.6

I recently upgraded from 1.2 to Meteors latest version 1.6.0.1.
I was using observe in a publication and an observe on the client to get changes.
in 1.2 no problems at all, but in 1.6 observed changes are not received in a "changed" client callback, but the client does get the ddp message. I can verify that by looking in Chromes dev tools > websocket, see the incoming message, but it's never fired in a client callback. This only happens when changing 2-3 documents at a time.
So when I delete a few documents from the DB, the publication fires off the callbacks, and the client receives them in the websocket messages, but it only fires once in the "observe" callback on the client.
Here is my code.
Client -
CollectionTest = new Meteor.Collection('collectionTest');
CollectionTest.find({}).observe({
added: function (doc) {
console.log("ADDED DOC ", doc);
},
changed: function (newDoc, oldDoc) {
console.log("CHANGED DOC new ", newDoc);
},
removed: function (doc) {
console.log("REMOVED DOC ", doc);
}
});
Server Publication -
Meteor.publish("ddpPub", function () {
var self = this,
ready = false;
var userId = self.userId;
var subHandle = TestData.find({}).observeChanges({
added: function (id, fields) {
if (ready) {
self.changed("collectionTest", userId, {
type: "added",
data: {
id: id,
fields: fields
}
});
}
},
changed: function (id, fields) {
if (ready) {
self.changed("collectionTest", userId, {
type: "changed",
data: {
id: id,
fields: fields
}
});
}
},
removed: function (id) {
if (ready) {
self.changed("collectionTest", userId, {
type: "removed",
data: id
});
}
}
});
self.added("collectionTest", userId);
self.ready();
ready = true;
self.onStop(function () {
subHandle.stop();
});
});
Attached are images from me removing the documents from the DB. The websocket messages, and then my console on the client. Showing it only fires once for 5 documents.
Showing the document id's I am deleting
DDP messages in 'websocket' confirmed they get to client
Single client message in client callback showing only document changed
UPDATE: 12/15/17 - 7:17pm PST
After working on this for a couple hours, finding some related meteor posts with observe callbacks and “Meteor.call” not working inside, the solution or hack is to wrap the “Meteor.call” in a “setTimeout” with the value of 0, and it fixes it.
I tried that here, and it didn’t work, but then I tried throttle the response, and it works! Not sure if it's a reliable fix, but it's the only one I found so far.
I am not sure why this works, or what causes the problem in the first place, any explanation would be welcome.
Server Publication -
Meteor.publish("ddpPub", function () {
var self = this,
ready = false;
var userId = self.userId;
var subHandle = TestData.find({}).observeChanges({
added: function (id, fields) {
if (ready) {
console.log("ADDING PUBLICATION");
self.changed("collectionTest", userId, {
type: "added",
data: {
id: id,
fields: fields
}
});
}
},
changed: function (id, fields) {
if (ready) {
console.log("CHANGING PUBLICATION");
self.changed("collectionTest", userId, {
type: "changed",
data: {
id: id,
fields: fields
}
});
}
},
removed: function (id) {
if (ready) {
console.log("REMOVING PUBLICATION");
ratePub(id, function (data) {
console.log("OBJECT DATA IS ", data);
self.changed("collectionTest", userId, data);
});
}
}
});
self.added("collectionTest", userId);
self.ready();
ready = true;
self.onStop(function () {
subHandle.stop();
});
});
var returnPub = function (id, callback) {
console.log("RETURNING PUB ");
callback({
id: id,
type: "removed",
data: id
});
};
var ratePub = _.rateLimit(returnPub, 10);

Upvoting and downvoting posts - Meteor

Up and downvotes are functional yet I'd like to do a check like "If the user is a downvoter or an upvoter" and do the right thing which is explained below
upvote: function(postId) {
check(this.userId, String);
check(postId, String);
var affected = Posts.update({
_id: postId,
upvoters: {$ne: this.userId}
},{
$addToSet: {
upvoters: this.userId
},
$inc: {
upvotes: 1
}
});
if (! affected)
throw new Meteor.Error('invalid', "You already up-voted this post");
},
downvote: function(postId) {
check(this.userId, String);
check(postId, String);
var affected = Posts.update({
_id: postId,
downvoters: {$ne: this.userId},
}, {
$addToSet: {
downvoters: this.userId
},
$inc: {
downvotes: 1
}
});
if (! affected)
throw new Meteor.Error('invalid', "You already down-voted this post");
},
With my code above, users can upvote and downvote once, but they can do both...
I wrote the code for what happens if a user is a downvoter and clicks upvote but I couldn't figure out how to check if the user is a downvoter or an upvoter.
$pull: {
downvoters: this.userId
},
$addToSet: {
upvoters: this.userId
},
$inc: {
downvotes: -1
},
$inc: {
upvotes: 1
});
EDIT: Even though the accepted answer works fine, I found an issue with it. When you click fast, it might increment the vote count 2-3 times. Instead of incrementing vote count, I only insert userId and simply count how many IDs there are inside the upvoters/downvoters array which gives the same result & it never inserts the same userId twice.
Inside the helpers for the count:
return this.upvoters.length
Also, inArray is a useful tool for checking if the value you have is inside an array.
if($.inArray(Meteor.userId(), this.upvoters)) //gives true if the current user's ID is inside the array
You will have to fetch the post and see if it contains the user's id in its downvoters array:
var post = Posts.findOne(postId);
if (post.downvoters && _.contains(post.downvoters, this.userId)) {
Posts.update({
_id: postId
},
{
$pull: {
downvoters: this.userId
},
$addToSet: {
upvoters: this.userId
},
$inc: {
downvotes: -1,
upvotes: 1
}
}
});
}

Client data not being updated in Meteor application

I have a Meteor application with a publish of:
Meteor.publish('my_items', function() {
var selector = {owner_id: this.userId};
var items = ItemOwnership.find(selector, {fields: {item_id: 1}}).fetch();
var itemIds = _.pluck(items, 'item_id');
return Items.find({
_id: {$in: itemIds},
item_archived_ts: { $exists: false }
});
});
and a subscription of this:
Meteor.subscribe('my_items');
The application allows for the user to add items to the 'Items' collection and this is done by calling a server method. The Items collection on the server is updated with the new record, but the client-side equivalent collection is not showing the new record. Is there anything obviously wrong with what I am doing, or some way to debug this?
p.s. there are no client/server-side errors occurring?
I found a way to accomplish this using the reywood:publish-composite Meteor package. Here is the publish that achieves this:
Meteor.publishComposite('my_items', {
find: function () {
var selector = {owner_id: this.userId};
return ItemOwnership.find(selector, {fields: {item_id: 1}});
},
children: [
{
find: function(IOrecord){
return Items.find({
_id: IOrecord.item_id,
item_archived_ts: { $exists: false }
});
}
}
]
});

How do I make a collection reactive based on the contents of another?

In short, I want to do:
Meteor.publish('items', function(){
return Item.find({categoryId: Categories.find({active: true} });
});
The flag 'active' as part of 'Categories' changes regularly.
I also tried unsub/resub to the Items collection by leveraging reactivity on the Categories collections, and it works, unfortunately it re-triggers on ANY modification to the Categories collection, regardless if it affected the 'active' flag or not.
What are my options?
Nothing solved the issue of the items not being 'deleted' locally when the category is flagged as inactive on the server. Solution (ish) is to:
Client:
Categories.find({active: true}).observeChanges({
added: function(){
itemsHandle && itemsHandle.stop();
itemsHandle = Meteor.subscribe("items");
}
});
Server:
Meteor.publish('items', function(){
var category = Categories.findOne({active: true});
return category && Items.find({categoryId: Categories.findOne({active: true}._id);
});
I realize this isn't perfect (still uses client side code), but it works and its the cleanest I could think of. I hope it helps someone!
A possible solution is to create a dependency object, watch for all categories change, and trigger the dep change if the active flag was toggled. Something along these lines:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Meteor.publish('items', function(){
activeDep.depend();
return Item.find({categoryId: Categories.find({active: true} });
});
Note: I'm only verifying whether the number of active categories have changes so that I don't have to keep the active list in the memory. This may or may not be appropriate depending on how your app works.
Edit: Two-sided flavor mentioned in the comments:
Client:
var activeCount = Categories.find({active: true}).count();
var activeDep = new Deps.Dependency();
Deps.autorun(function() {
var activeCountNow = Categories.find({active: true}).count();
if(activeCountNow !== activeCount) {
activeCount = activeCountNow;
activeDep.changed();
}
});
Deps.autorun(function(){
activeDep.depend();
Meteor.subscribe('items', new Date().getTime());
});
Server:
Meteor.publish('items', function(timestamp) {
var t = timestamp;
return Item.find({categoryId: Categories.find({active: true} });
});
Meteor.startup(function() {
Categories.find().observe({
addedAt: function(doc) {
trigger();
},
changedAt: function(doc, oldDoc) {
if(doc.active != oldDoc.active) {
trigger();
}
},
removedAt: function(oldDoc) {
trigger();
}
});
});
Now, the trigger function should cause the publish to rerun. This time it's easy when it's on the client (change subscription param). I'm not sure how to do this on the server - perhaps run publish again.
I use the following publish to solve a similar issue. I think it is only the one line nesting of queries that limits the reactivity. Breaking one query out inside the publish function seems to avoid the issue.
//on server
Meteor.publish( "articles", function(){
var self= this;
var subscriptions = [];
var observer = Feeds.find({ subscribers: self.userId }, {_id: 1}).observeChanges({
added: function (id){
subscriptions.push(id);
},
removed: function (id){
subscriptions.splice( subscriptions.indexOf(id)) , 1);
}
});
self.onStop( function() {
observer.stop();
});
var visibleFields = {_id: 1, title: 1, source: 1, date: 1, summary: 1, link: 1};
return Articles.find({ feed_id: {$in: subscriptions} }, { sort: {date: -1}, limit: articlePubLimit, fields: visibleFields } );
});
//on client anywhere
Meteor.subscribe( "articles" );
Here is another SO example which gets the search criteria from the client through subscribe if you decide that is acceptable.
Update: Since the OP struggled to get this going I made a gist and launched a working version on meteor.com. If you just need the publish function it is as above.

Resources