MongoDB query: If two docs are referencing each other, eliminate one doc (Keep one combination only) - mongodb

I have docs like these:
{
_id:61af43169dae3a9c3e133a90
name:"user1",
status: "RECOMMENDED",
recommendedId:61b708b8041895f4c68a3b3d
}
{
_id:61b708b8041895f4c68a3b3d
name:"user2",
status: "RECOMMENDED"
recommendedId:61af43169dae3a9c3e133a90
}
Both users are recommended to each other, so, I don't want both documents having recommended Id populated. I just want one document having recommendedId populated (Keep one combo only)

I would try to prevent this from happening at the time of setting the value of recommendedId in the first place.
So before trying to set the value, you could do something like this:
const idToRecommend = Types.ObjectId()
const recommenders = await Foo.find({
_id: idToRecommend,
recommendedId: user._id
})
if (recommenders.length > 0) {
// We don't want to make the change, we already have a relationship recorded.
}
Cleaning up a db already tainted with these duplicate relationships is a different question, but I would do that as a one-off task rather than a matter of process.

Related

Dart/Flutter stream to a field/data in firebase [duplicate]

How can I listen to a specific field change with firestore js sdk ?
In the documentation, they only seem to show how to listen for the whole document, if any of the "SF" field changes, it will trigger the callback.
db.collection("cities").doc("SF")
.onSnapshot(function(doc) {
console.log("Current data: ", doc && doc.data());
});
You can't. All operations in Firestore are on an entire document.
This is also true for Cloud Functions Firestore triggers (you can only receive an entire document that's changed in some way).
If you need to narrow the scope of some data to retrieve from a document, place that in a document within a subcollection, and query for that document individually.
As Doug mentioned above, the entire document will be received in your function. However, I have created a filter function, which I named field, just to ignore document changes when those happened in fields that I am not interested in.
You can copy and use the function field linked above in your code. Example:
export const yourCloudFunction = functions.firestore
.document('/your-path')
.onUpdate(
field('foo', 'REMOVED', (change, context) => {
console.log('Will get here only if foo was removed');
}),
);
Important: The field function is not avoiding your function to be executed if changes happened in other fields, it will just ignore when the change is not what you want. If your document is too big, you should probably consider Doug's suggestion.
Listen for the document, then set a conditional on the field you're interesting in:
firebase.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(word).collection('Pronunciations').doc('Castilian-female-IBM').onSnapshot(function(snapshot) {
if (snapshot.data().audioFiles) { // eliminates an error message
if (snapshot.data().audioFiles.length === 2) {
audioFilesReady++;
if (audioFilesReady === 3) {
$scope.showNextWord();
}
}
}
}, function(error) {
console.error(error);
});
I'm listening for a document for a voice (Castilian-female-IBM), which contains an array of audio files, in webm and mp3 formats. When both of those audio files have come back asynchronously then snapshot.data().audioFiles.length === 2. This increments a conditional. When two more voices come back (Castilian-male-IBM and Latin_American-female-IBM) then audioFilesReady === 3 and the next function $scope.showNextWord() fires.
Just out of the box what I do is watching before and after with the before and after method
const clientDataBefore = change.before.data();
console.log("Info database before ", clientDataBefore);
const clientDataAfter = change.after.data();
console.log("Info database after ", clientDataAfter );
For example now you should compare the changes for a specific field and do some actions or just return it.
Some more about before.data() and after.data() here

Mutation cache update not working with vue-apollo and Hasura

I'm completely new to these technologies, and am having trouble wrapping my head around it, so bear with me. So, my situation is that I've deployed Hasura on Heroku and have added some data, and am now trying to implement some functionality where I can add and edit certain rows of a table. Specifically I've been following this from Hasura, and this from vue-apollo.
I've implemented the adding and editing (which works), and now want to also reflect this in the table, by using the update property of the mutation and updating the cache. Unfortunately, this is where I get lost. I'll paste some of my code below to make my problem more clear:
The mutation for adding a player (ADD_PLAYER_MUTATION) (same as the one in Hasura's documentation linked above):
mutation addPlayer($objects: [players_insert_input!]!) {
insert_players(objects: $objects) {
returning {
id
name
}
}
}
The code for the mutation in the .vue file
addPlayer(player, currentTimestamp) {
this.$apollo.mutate({
mutation: PLAYER_ADD_MUTATION,
variables: {
objects: [
{
name: player.name,
team_id: player.team.id,
created_at: currentTimestamp,
updated_at: currentTimestamp,
role_id: player.role.id,
first_name: player.first_name,
last_name: player.last_name
}
]
},
update: (store, { data: { addPlayer } }) => {
const data = store.readQuery({
query: PLAYERS
});
console.log(data);
console.log(addPlayer);
data.players.push(addPlayer);
store.writeQuery({ query: PLAYERS, data });
}
});
},
I don't really get the update part of the mutation. In most examples the { data: { x } } bit uses the function's name in the place of x, and so I did that as well, even though I don't really get why (it's pretty confusing to me at least). When logging data the array of players is logged, but when logging addPlayer undefined is logged.
I'm probably doing something wrong that is very simple for others, but I'm obviously not sure what. Maybe the mutation isn't returning the correct thing (although I'd assume it wouldn't log undefined in that case), or maybe isn't returning anything at all. It's especially confusing since the player is actually added to the database, so it's just the update part that isn't working - plus, most of the guides / tutorials show the same thing without really much explanation.
Okay, so for anyone as stupid as me, here's basically what I was doing wrong:
Instead of addPlayer in update: (store, { data: { addPlayer } }), it should be whatever the name of the mutation is, so in this case insert_players.
By default a mutation response from Hasura has a returning field, which is a list, and so the added player is the first element in the list, so you can get it like so: const addedPlayer = insert_players.returning[0];
I didn't want to just delete my question after realising what was wrong shortly after posting it, in case this is useful to other people like me, and so I'll leave it up.

Meteor Subscriptions Selecting the Entire Set?

I've defined a publication:
Meteor.publish('uninvited', function (courseId: string) {
return Users.find({
'profile.courses': {
$ne: courseId
}
});
});
So, in when a subscriber subscribes to this, I expect Users.find() to return only users that are not enrolled in that particular course. So, on my client, when I write:
this.uninvitedSub = MeteorObservable.subscribe("uninvited", this.courseId).subscribe(() => {
this.uninvited = Users.find().zone()});
I expect uninvited to contain only a subset of users, however, I'm getting the entire set of users regardless of whether or not they are enrolled in a particular course. I've made sure that my data is correct and that there are users enrolled in the course that I'm concerned with. I've also verified that this.courseId is working as expected. Is there an error with my code, or should I further look into my data to see if there's anything wrong with it?
**Note:
When I write this:
this.uninvitedSub = MeteorObservable.subscribe("uninvited", this.courseId).subscribe(() => {
this.uninvited = Users.find({
'profile.courses': {}
}).zone();
});
With this, it works as expected! Why? The difference is that my query now contains 'profile.courses': {}.

Subscribing to Meteor.Users Collection

// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
I want to now get the directory listings queried from the client like directory.findOne() from the browser's console. //Testing purposes
Doing directory=Meteor.subscribe('directory')/directory=Meteor.Collection('directory') and performing directory.findOne() doesn't work but when I do directory=new Meteor.Collection('directory') it works and returns undefined and I bet it CREATES a mongo collection on the server which I don't like because USER collection already exists and it points to a new Collection rather than the USER collection.
NOTE: I don't wanna mess with how Meteor.users collection handles its function... I just want to retrieve some specific data from it using a different handle that will only return the specified fields and not to override its default function...
Ex:
Meteor.users.findOne() // will return the currentLoggedIn users data
directory.findOne() // will return different fields taken from Meteor.users collection.
If you want this setup to work, you need to do the following:
Meteor.publish('thisNameDoesNotMatter', function () {
var self = this;
var handle = Meteor.users.find({}, {
fields: {emails: 1, profile: 1}
}).observeChanges({
added: function (id, fields) {
self.added('thisNameMatters', id, fields);
},
changed: function (id, fields) {
self.changed('thisNameMatters', id, fields);
},
removed: function (id) {
self.removed('thisNameMatters', id);
}
});
self.ready();
self.onStop(function () {
handle.stop();
});
});
No on the client side you need to define a client-side-only collection:
directories = new Meteor.Collection('thisNameMatters');
and subscribe to the corresponding data set:
Meteor.subscribe('thisNameDoesNotMatter');
This should work now. Let me know if you think this explanation is not clear enough.
EDIT
Here, the self.added/changed/removed methods act more or less as an event dispatcher. Briefly speaking they give instructions to every client who called
Meteor.subscribe('thisNameDoesNotMatter');
about the updates that should be applied on the client's collection named thisNameMatters assuming that this collection exists. The name - passed as the first parameter - can be chosen almost arbitrarily, but if there's no corresponding collection on the client side all the updates will be ignored. Note that this collection can be client-side-only, so it does not necessarily have to correspond to a "real" collection in your database.
Returning a cursor from your publish method it's only a shortcut for the above code, with the only difference that the name of an actual collection is used instead of our theNameMatters. This mechanism actually allows you to create as many "mirrors" of your datasets as you wish. In some situations this might be quite useful. The only problem is that these "collections" will be read-only (which totally make sense BTW) because if they're not defined on the server the corresponding `insert/update/remove' methods do not exist.
The collection is called Meteor.users and there is no need to declare a new one on neither the server nor the client.
Your publish/subscribe code is correct:
// in server.js
Meteor.publish("directory", function () {
return Meteor.users.find({}, {fields: {emails: 1, profile: 1}});
});
// in client.js
Meteor.subscribe("directory");
To access documents in the users collection that have been published by the server you need to do something like this:
var usersArray = Meteor.users.find().fetch();
or
var oneUser = Meteor.users.findOne();

MongoDB Social Network Adding Followers

I'm implementing a social network in MongoDB and I need to keep track of Followers and Following for each User. When I search for Users I want to display a list like Facebook with the User Name, Picture and number of Followers & Following. If I just wanted to display the User Name and Picture (info that doesn't change) it would be easy, but I also need to display the number of Followers & Following (which changes fairly regularly).
My current strategy is to embed the People a User follows into each User Document:
firstName: "Joe",
lastName: "Bloggs",
follows: [
{
_id: ObjectId("520534b81c9aac710d000002"),
profilePictureUrl: "https://pipt.s3.amazonaws.com/users/xxx.jpg",
name: "Mark Rogers",
},
{
_id: ObjectId("51f26293a5c5ea4331cb786a"),
name: "The Palace Bar",
profilePictureUrl: "https://s3-eu-west-1.amazonaws.com/businesses/xxx.jpg",
}
]
The question is - What is the best strategy to keep track of the number of Followers & Following for each User?
If I include the number of Follows / Following as part of the embedded document i.e.
follows: [
{
_id: ObjectId("520534b81c9aac710d000002"),
profilePictureUrl: "https://pipt.s3.amazonaws.com/users/xxx.jpg",
name: "Mark Rogers",
**followers: 10,**
**following: 400**
}
then every time a User follows someone requires multiple updates across all the embedded documents.
Since the consistency of this data isn't really important (i.e. Showing someone I have 10 instead of 11 followers isn't the end of the world), I can queue this update. Is this approach ok or can anyone suggest a better approach ?
You're on the right track. Think about which calculation is performed more - determining the number of followers/following or changing number of followers/following? Even if you're caching the output of the # of followers/following calculation it's still going to be performed one or two orders of magnitude more often than changing the number.
Also, think about the opposite. If you really need to display the number of followers/following for each of those users, you'll have to then do an aggregate on each load (or cache it somewhere, but you're still doing a lot of calcs).
Option 1: Cache the number of followers/following in the embedded document.
Upsides: Can display stats in O(1) time
Downsides: Requires O(N) time to follow/unfollow
Option 2: Count the number of followers/following on each page view (or cache invalidation)
Upsides: Can follow/unfollow in O(1) time
Downsides: Requires O(N) time to display
Add in the fact that follower/following stats can be eventually consistent whereas the counts have to be displayed on demand and I think it's a pretty easy decision to cache it.
I've gone ahead and implement the update followers/following based on the same strategy recommended by Mason (Option 1). Here's my code in NodeJs and Mongoose and using the AsyncJs Waterfall pattern in case anyone is interested or has any opinions. I haven't implemented queuing yet but the plan would be to farm most of this of to a queue.
async.waterfall([
function (callback) {
/** find & update the person we are following */
Model.User
.findByIdAndUpdate(id,{$inc:{followers:1}},{upsert:true,select:{fullName:1,profilePictureUrl:1,address:1,following:1,followers:1}})
.lean()
.exec(callback);
},
function (followee, callback) {
/** find & update the person doing the following */
var query = {
$inc:{following:1},
$addToSet: { follows: followee}
}
Model.User
.findByIdAndUpdate(credentials.username,query,{upsert:true,select:{fullName:1,profilePictureUrl:1,address:1,following:1,followers:1}})
.lean()
.exec(function(err,follower){
callback(err,follower,followee);
});
},
function(follower,followee,callback){
/** update the following count */
Model.User
.update({'follows._id':follower.id},{'follows.$.following':follower.following},{upsert:true,multi:true},function(err){
callback(err,followee);
});
},
function(followee,callback){
/** update the followers count */
Model.User
.update({'follows._id':followee.id},{'follows.$.followers':followee.followers},{upsert:true,multi:true},callback);
}
], function (err) {
if (err)
next(err);
else {
res.send(HTTPStatus.OK);
next();
}
});