Publish cursor with simplified array data - mongodb

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.

Related

How to guarantee unique primary key with one update query

In my Movie schema, I have a field "release_date" who can contain nested subdocuments.
These subdocuments contains three fields :
country_code
date
details
I need to guarantee the first two fields are unique (primary key).
I first tried to set a unique index. But I finally realized that MongoDB does not support unique indexes on subdocuments.
Index is created, but validation does not trigger, and I can still add duplicates.
Then, I tried to modify my update function to prevent duplicates, as explained in this article (see Workarounds) : http://joegornick.com/2012/10/25/mongodb-unique-indexes-on-single-embedded-documents/
$ne works well but in my case, I have a combination of two fields, and it's a way more complicated...
$addToSet is nice, but not exactly what I am searching for, because "details" field can be not unique.
I also tried plugin like mongoose-unique-validator, but it does not work with subdocuments ...
I finally ended up with two queries. One for searching existing subdocument, another to add a subdocument if the previous query returns no document.
insertReleaseDate: async(root, args) => {
const { movieId, fields } = args
// Searching for an existing primary key
const document = await Movie.find(
{
_id: movieId,
release_date: {
$elemMatch: {
country_code: fields.country_code,
date: fields.date
}
}
}
)
if (document.length > 0) {
throw new Error('Duplicate error')
}
// Updating the document
const response = await Movie.updateOne(
{ _id: movieId },
{ $push: { release_date: fields } }
)
return response
}
This code works fine, but I would have preferred to use only one query.
Any idea ? I don't understand why it's so complicated as it should be a common usage.
Thanks RichieK for your answer ! It's working great.
Just take care to put the field name before "$not" like this :
insertReleaseDate: async(root, args) => {
const { movieId, fields } = args
const response = await Movie.updateOne(
{
_id: movieId,
release_date: {
$not: {
$elemMatch: {
country_code: fields.country_code,
date: fields.date
}
}
}
},
{ $push: { release_date: fields } }
)
return formatResponse(response, movieId)
}
Thanks a lot !

Inconsistent results with Meteor's pub/sub feature

I'm experiencing inconsistent results with Meteor's pub/sub feature, and I suspect it's a source of confusion for a lot of developers hitting the threshold of an MVP built in Meteor becoming a production app.
Maybe this is a limitation of MergeBox:
Let's say I have a collection called Events, in which I have document-oriented structures, ie, nested Array, Objects. An Events document might look like so:
// an Events document //
{
_id: 'abc',
name: 'Some Event',
participation: {
'userOneId': {
games: {
'gameOneId': {
score: 100,
bonus: 10
}
},
{
'gameTwoId': : {
score: 100,
bonus: 10
}
}
}
},
'userTwoId': {
games: {
'gameOneId': {
score: 70,
bonus: 15
}
},
contests: {
'contestOneId': [2, 3, 6, 7, 4],
'contestTwoId': [9, 3, 7, 2, 1],
}
}
},
}
}
So at these events, users can optionally participate in games of certain types and contests of certain types.
Now, I want to restrict subscriptions to the Events collection based on the user (show only this user's participation), and, sometimes I'm only interested in changes to one subset of the data (like, show only the user's scores on 'gameOneId').
So I've created a publication like so:
Meteor.publish("events.participant", function(eventId, userId) {
if(!Meteor.users.findOne({_id: this.userId})) return this.ready();
check(eventId, String);
check(userId, String);
const includeFields = {
name: 1,
[`participation.${userId}`]: 1
};
return Events.find({_id: eventId}, {fields: includeFields});
});
This publication seems to work fine on the client if I do:
// in onCreated of events template //
template.autorun(function() {
const
eventId = FlowRouter.getParam('id'),
userId = Meteor.userId(),
subscription = template.subscribe('events.participant', eventId, userId);
if (subscription.ready()) {
const event = Events.findOne({_id: eventId}, parseIncludeFields(['name', `participation.${userId}`]));
template.props.event.set(event);
}
});
Happily, I can use the Event document returned that includes only the name field and all of the user's participation data.
But, later, in another template if I do:
// in onCreated of games template //
template.autorun(function() {
const
eventId = FlowRouter.getParam('id')
gameId = FlowRouter.getParam('gameId'),
userId = Meteor.userId(),
subscription = template.subscribe('events.participant', eventId, userId);
if(subscription.ready()) {
const event = Events.findOne({_id: eventId}, {fields: {[`participation.${userId}.games.${gameId}`]: 1}});
template.props.event.set(event);
}
});
I sometimes get back the data at event.participation[userId].games[gameId], and sometimes I don't - the Object that's suppose to be at gameId is non-existent, even the it exists in the Mongo document, and the subscription should include it. Why?
The only difference is between the two calls to Events.findOne() is that in the latter, I'm not requesting the name field. But, if this is a problem, why?. If minimongo already has the document, who cares if I request parts of it?
The subscriptions in both templates are identical - I'm doing this because the games template is available at a route, so the user could go straight to the games url, by-passing the events template altogether, so I want to be sure the client has the document it needs to render correctly.
The only way I've gotten around this is to make a straight Meteor method call to the server in the games template to fetch the subset of interest, but this seems like a cop-out.
If you've read this far, you're a champ!

Update nested array object (put request)

I have an array inside a document of a collection called pown.
{
_id: 123..,
name: pupies,
pups:[ {name: pup1, location: somewhere}, {name: pup2, ...}]
}
Now a user using my rest-service sends the entire first entry as put request:
{name: pup1, location: inTown}
After that I want to update this element in my database.
Therefore I tried this:
var updatedPup = req.body;
var searchQuery = {
_id : 123...,
pups : { name : req.body.name }
}
var updateQuery = {
$set: {'pups': updatedPup }
}
db.pown.update(searchQuery, updateQuery, function(err, data){ ... }
Unfortunately it is not updating anythig.
Does anyone know how to update an entire array-element?
As Neil pointed, you need to be acquainted with the dot notation(used to select the fields) and the positional operator $ (used to select a particular element in an array i.e the element matched in the original search query). If you want to replace the whole element in the array
var updateQuery= {
"$set":{"pups.$": updatedPup}
}
If you only need to change the location,
var updateQuery= {
"$set":{"pups.$.location": updatedPup.location}
}
The problem here is that the selection in your query actually wants to update an embedded array element in your document. The first thing is that you want to use "dot notation" instead, and then you also want the positional $ modifier to select the correct element:
db.pown.update(
{ "pups.name": req.body.name },
{ "$set": { "pups.$.locatation": req.body.location }
)
That would be the nice way to do things. Mostly because you really only want to modify the "location" property of the sub-document. So that is how you express that.

Meteor Reactive Data Query for Comments with Usernames and Pictures

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

Does Moongoose 3.8.8 support $position operator?

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.