I am trying to implement a commenting system in a huge app and always run in the problem about cross reactiveness and publications.
The specific problem:
When a user writes a comment, I want to show the user's name and a profile picture. The comments are in one collection, the names and pictures in another.
When I make a subscription for every comment on this page and for every user whose id is in a comment of this page serversided, the app does not update the users available on the client when a new comment is added because "joins" are nonteactive on the server.
When I do that on the client, i have to unsubscribe and resubscribe all the time, a new comment is added and the load gets higher.
what is the best practise of implementing such a system in meteor? how can i get around that problem without a huge overpublishing?
As there is not official support for joins yet,among all the solutions out there in community
I found https://github.com/englue/meteor-publish-composite this package very helpful and I'm using it in my app.
This example perfectly suits your requirement https://github.com/englue/meteor-publish-composite#example-1-a-publication-that-takes-no-arguments
Meteor.publishComposite('topTenPosts', {
find: function() {
// Find top ten highest scoring posts
return Posts.find({}, { sort: { score: -1 }, limit: 10 });
},
children: [
{
find: function(post) {
// Find post author. Even though we only want to return
// one record here, we use "find" instead of "findOne"
// since this function should return a cursor.
return Meteor.users.find(
{ _id: post.authorId },
{ limit: 1, fields: { profile: 1 } });
}
},
{
find: function(post) {
// Find top two comments on post
return Comments.find(
{ postId: post._id },
{ sort: { score: -1 }, limit: 2 });
},
children: [
{
find: function(comment, post) {
// Find user that authored comment.
return Meteor.users.find(
{ _id: comment.authorId },
{ limit: 1, fields: { profile: 1 } });
}
}
]
}
]
});
//client
Meteor.subscribe('topTenPosts');
and the main thing is it is reactive
Related
const populateprofile = (func) => {
return func
.populate("questionID")
.populate("profileID")
.populate("comments.profileID")
.populate("comments.subcomments.profileID")
.populate("comments.subcomments.subcomments.profileID")
.populate("comments.subcomments.subcomments.subcomments.profileID")
.populate("comments.subcomments.subcomments.subcomments.subcomments.profileID")
.exec()
}
console.time()
const docAfterUpdate = await populateprofile(answerModel.findOneAndUpdate({ _id: feedId }, { $push: { comments: newComment } }, { new: true }))
console.timeEnd()
res.json({ answer: docAfterUpdate }).status(200)
i am simply updating a comment for an answer assuming the schema of answer that it contains comments and those comments may contain subcomments which in turn may contain their own subcomments and so on.
so this query is talking 900ms on average and this is what i can do from what is in my knowledge, so would there be any better way and improve this query ?
I hate to risk asking a duplicate question, but perhaps this is different from Passing Variables to a MongoDB View which didn't have any clear solution.
Below is a query to find the country for IP Address 16778237. (Outside the scope of this query, there is a formula that turns an IPV4 address into a number.)
I was wondering if we could abstract away this query out of NodeJS code, and make a view, so the view could be called from NodeJS. But the fields ipFrom and ipTo are indexed to get the query to run fast against millions of documents in the collection, so we can't return all the rows to NodeJS and filter there.
In MSSQL maybe this would have to be a stored procedure, instead of a view. Just trying to learn what is possible in MongoDB. I know there are functions, which are written in JavaScript. Is that where I need to look?
db['ip2Locations'].aggregate(
{
$match:
{
$and: [
{
"ipFrom": {
$lte: 16778237
}
},
{
"ipTo": {
$gte: 16778237
}
},
{
"active": true
}
],
$comment: "where 16778237 between startIPRange and stopIPRange and the row is 'Active',sort by createdDateTime, limit to the top 1 row, and return the country"
}
},
{
$sort:
{
'createdDateTime': - 1
}
},
{
$project:
{
'countryCode': 1
}
},
{
$limit: 1
}
)
Part 2 - after more research and experimenting, I found this is possible and runs with success, but then see trying to make a view below this query.
var ipaddr = 16778237
db['ip2Locations'].aggregate(
{
$match:
{
$and: [
{
"ipFrom": {
$lte: ipaddr
}
},
{
"ipTo": {
$gte: ipaddr
}
},
{
"active": true
}
],
$comment: "where 16778237 between startIPRange and stopIPRange and the row is 'Active',sort by createdDateTime, limit to the top 1 row, and return the country"
}
},
{
$sort:
{
'createdDateTime': - 1
}
},
{
$project:
{
'countryCode': 1
}
},
{
$limit: 1
}
)
If I try to create a view with a "var" in it, like this;
db.createView("ip2Locations_vw-lookupcountryfromip","ip2Locations",[
var ipaddr = 16778237
db['ip2Locations'].aggregate(
I get error:
[Error] SyntaxError: expected expression, got keyword 'var'
In the link I provided above, I think the guy was trying to figure how the $$user-variables work (no example here: https://docs.mongodb.com/manual/reference/aggregation-variables/). That page refers to $let, but never shows how the two work together. I found one example here: https://www.tutorialspoint.com/mongodb-query-to-set-user-defined-variable-into-query on variables, but not $$variables. I'm
db.createView("ip2Locations_vw-lookupcountryfromip","ip2Locations",[
db['ip2Locations'].aggregate(
...etc...
"ipFrom": {
$lte: $$ipaddr
}
I tried ipaddr, $ipaddr, and $$ipaddr, and they all give a variation of this error:
[Error] ReferenceError: $ipaddr is not defined
In a perfect world, one would be able to do something like:
get['ip2Locations_vw-lookupcountryfromip'].find({$let: {'ipaddr': 16778237})
or similar.
I'm getting that it's possible with Javascript stored in MongoDB (How to use variables in MongoDB query?), but I'll have to re-read that; seems like some blogs were warning against it.
I have yet to find a working example using $$user-variables, still looking.
Interpretation
You want to query a view from some server side code, passing a variable to it.
Context
Can we use an external variable to recompute a View? Take the following pipeline:
var pipeline = [{ $group:{ _id:null, useless:{ $push:"$$NOW" } } }]
We can pass system variables using $$. We can define user variables too, but the user defined variables are made out of:
Collection Data
System Variables.
Also, respect to your Part2:
A variable var variable="what" will be computed only once. Redefine variable="whatever" makes no difference in the view, it uses "what".
Conclusion
Views can only be re-computed with system variables, or user variables dependant on those system variables or collection data.
Added an answer to the post you link too.
I have a collection of Messages on my mongodb.
I have created two publish functions:
Meteor.publish("messages", function (self, buddy) {
check(self, String);
check(buddy, String);
return Messages.find({participants: {$all : [self, buddy] }});
});
and,
Meteor.publish("conversations", function(self){
check(self, String);
return Messages.find(
{ participants: { $in : [self] } },
{ participants: { $elemMatch: { $ne: self } }, messages: { $slice: -1 }}
);
});
And I subscribe to both of these on the client:
Meteor.subscribe("conversations", user);
return Messages.find();
and,
Meteor.subscribe("messages", user, buddy);
return Messages.find();
The subscriptions are located in different templates.
The problem is that when I return the data from the conversation subscription the data is the same as from the messages subscription. I see the same results in both subscriptions even though they have different queries...
How can I solve this issue?
This is normal behavior, the same collection contains data for both subscriptions. You need to filter on the client as well.
This https://www.discovermeteor.com/blog/query-constructors/ outlines a pattern for handling this.
The basic idea is to have the query part as common code for both the server and the client so it is self consistent.
I need to publish a simplified version of posts to users. Each post includes a 'likes' array which includes all the users who liked/disliked that post, e.g:
[
{
_id: user_who_liked,
liked: 1 // or -1 for disliked
},
..
]
I'm trying to send a simplified version to the user who subscribes an array which just includes his/her like(s):
Meteor.publish('posts', function (cat) {
var _self = this;
return Songs.find({ category: cat, postedAt: { $gte: Date.now() - 3600000 } }).forEach(function (post, index) {
if (_self.userId === post.likes[index]._id) {
// INCLUDE
} else
// REMOVE
return post;
})
});
I know I could change the structure, including the 'likes' data within each user, but the posts are usually designed to be short-lived, to it's better to keep that data within each post.
You need to use this particular syntax to find posts having a likes field containing an array that contains at least one embedded document that contains the field by with the value this.userId.
Meteor.publish("posts", function (cat) {
return Songs.find({
category: cat,
postedAt: { $gte: Date.now() - 3600000 },
"likes._id":this.userId
},{
fields:{
likes:0
}
});
});
http://docs.mongodb.org/manual/tutorial/query-documents/#match-an-array-element
EDIT : answer was previously using $elemMatch which is unnecessary because we only need to filter on one field.
Does Moongoose 3.8.8 (the lastest version) support $position (http://docs.mongodb.org/manual/reference/operator/update/position/) operator from MongoDB 2.6.0?
In the following code example the new elements is inserted in the end of the array userActivity.activities:
model:
var userActivity = new schema({
userId: {type:String, required:true, unique:true},
activities: [activity]
});
var activity = new schema({
act: {type: Number, required:true},
});
query:
var activity = { act: 1 };
model.userActivity.update(
{ _id: dbact._id },
{ $push: { activities: {
$each: [ activity ],
$position: 0
}
}
},
function (err, numAffected) {
if (!err) {
// do something
}
});
This actually doesn't matter and never matters for any "framework" implementation and I do not mind explaining why.
Every single "framework" ( such as Mongoose, Mongoid, Doctrine, MongoEngine, etc, etc, etc ) are all basically built upon a basic "driver" implementation that has in most cases been developedby the MongoDB staff themselves. So the basic functionality is always ther even if you need to "delve" down to a level in order to use those "native" methods.
So here would be the native usage example in this case:
List.collection.update(
{},
{ "$push": {
"list": {
"$each": [ 1, 2, 3 ],
"$position": 0 }
}
},function(err,NumAffected) {
console.log("done");
});
Note the "collection" method used from the model, which is getting the "raw" collection details from the driver. So you are using it's method and not some "wrapped" method that may be doing additional processing.
The next and most basic reason is if you cannot find the method and application of the operators that you need the here is a simple fact.
Every single operation as used by the methods in every framework and basic driver method is essentially a call to the "runCommand" method in the basic API. So since that basic call is available everywhere ( in some form or another, because it has to be ), then you can do everything that you find advertised on the MongoDB site with every language implementation on any framework.
But the short call to your particular request is, since this is not actually a method call but is simply part of the BSON arguments as passed in, then of course there is no restriction by a particular language driver to actually use this.
So you can use these new argument without of course updating to the most recent version. But you probably will get some nice methods to do so if you actually do.
Yes, you should be able to use it directly as Mongoose will pass through the update clause:
Model.update(
query, /* match the document */
{ $push:
{ yourArrayField:
{
$each: [ 1, 2, 3 ],
$position: 0
}
}
}, function (err, res) { /* callback */ });
The above would insert the values 1, 2, 3 at the front of the array named yourArrayField.
As it's just a pass-through, you'll need to make sure it works with the server version that you're connecting the client to.