Mongodb - Get back object instead of array from find - mongodb

db.users.find();
Will return me an array of users:
[{
_id: 123
name: bob
},{
_id: 456
name: tom
}]
I need to map users to another collection by the id, so I would like to get an object back from mongo where the keys are _id and values are the user doc.
i.e.
users = {
123: {_id: 123, name: bob},
456: {_id, 456, name:tom}
}
Then I can access users directly from that object without having to iterate an array to find specific users.
id = 123;
user = users[id];

You can't get an object like this one from mongodb, but it's quite easy to build it yourself:
db.users.find(function (err, docs) {
var users = {};
docs.forEach(function (doc) {
users[doc._id] = doc;
});
do_whatever_you_want_next(users);
});

Posting my solution in a more modern syntax:
const pushSubscriptions = await PushSubscription.find({ token: { $in: tokens } }).exec();
const userTokens = pushSubscriptions.reduce(
(result, ps) => {
result[ps.token] = ps;
return result;
},
{});

Related

Mongooose: How to get a id from a find array

I have a group-based document application, an error is happening in the sharing part. The application is composed of the following parts: Groups, viewers, users and documents.
A group can have many viewers and users, a user can have many documents. I'm working on the part where viewers can see all documents of users associated with the group the viewer is associated with
My controller
router.get("link", async (req, res) => {
const group = await Group.find({ viewer: req.session.userId }).select("id")
console.log(group); // This console.log returns the id in a array: [ { _id: new ObjectId("6323a88670c0dd9aaa5017d2") } ]
console.log(group.id); // This console.log returns undefined
const user = await User.find({ group: group.id });
console.log(user); // Nothing because the group.id is undefined
const documents = await Document.find({
user: user.id,
});
return res.render("page", {
documents,
});
Group schema
name: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
viewer: [{
type: mongoose.Schema.Types.ObjectId,
ref: "User",
}],
createdAt: {
type: Date,
default: Date.now,
}
I'm not able to retrieve the id from Group.find; what could be happening?
Because you want to have one value. So you can use findOne. Due to using findOne, you can reach group._id.
const group = await Group.findOne({ viewer: req.session.userId }).select("id")
console.log(group); { _id: new ObjectId("6323a88670c0dd9aaa5017d2") }
If you try to take the value from your array, you should take 0. element of array. Because it is an array and has elements. You are trying to reach element's id value.
But which element's id ? You need to declare it. Therefore, need to use group[0]._id. But if you want to reach just one element, using findOne() is better.
const group = await Group.find({ viewer: req.session.userId }).select("id")
console.log(group[0]); { _id: new ObjectId("6323a88670c0dd9aaa5017d2") }
I hope, it is clear and helps you. Good luck

How to update document with subdocument, or create new one if none are found

I'm trying to create a new subdocument object in an array when a user calls a command OR update the existing document based on their id. However, everything I've tried either gives me errors or it overrides the existing subdocument with the current user which is not what I want.
enter image description here
Basically I want to add another object in the array ( "1": Object ) that is a second user tracking whether they've used the command or not.
I can't remember all variations on code, but the current code I'm using:
const query = {
_id: guild.id,
members : [{
_id: user.id
}]
}
const update = {
members: {
_id: user.id,
bot: user.bot,
commandUsed: true
}
}
const options = {upsert: true, new: true}
await mongo().then(async () => {
console.log('Updating to...in use')
try {
// Find the document
await commandUsageSchema.findOneAndUpdate(query, update, options, function(error, result) {
if (!error) {
// If the document doesn't exist
if (!result) {
// Create it
result = new userSchema;
}
// Save the document
result.save(function(error) {
if (!error) {
// Do something with the document
} else {
throw error;
}
})
}
})
is creating a duplicate key error which is frustrating. Here is the layout of my schemas:
const mongoose = require('mongoose')
const reqString =
{
type: String,
required: true
}
const userSchema = mongoose.Schema({
_id: reqString,
bot: Boolean,
commandUsed: Boolean
}, {unique: true})
const commandUseSchema = mongoose.Schema({
_id: reqString,
members: [userSchema]
})
module.exports = mongoose.model('command-usage-checker', commandUseSchema)
I'm using mongoose and mongoDB (of course) with javascript and DiscordJS.

UpdateOne is not working with array of objects in mongoDB

I try to update an object of an array in mongoDB. When the variable listID is not defined, it'll just update the first object of the array (but that's not what I want).
My goal is to add the word IDs, update the last_practiced field and increment the number of the word count field by the number of wordIDs.
I tried it with aggregation as well, but I couldn't get it to work.
My current query
Words.prototype.updatePersonalWordLists = async function(userID, listData) {
const listID = listData.list_id
const wordIDs = listData.word_ids
const last_practiced = listData.last_practiced
const numberOfNewWords = listData.word_ids.length
const lists = await personalWordLists.updateOne(
{"user_id": userID, "lists.$.list_id": listID },
{
$addToSet: { "lists.$.practiced_words": { $each: wordIDs}},
$set: {
"lists.$.last_practiced": last_practiced,
$inc: {"lists.$.wordCount": numberOfNewWords}
}
}
)
return lists
}
let userID = "609b974f6bd8dc6019d2f304"
let listData = {
list_id: "609d22188ea8aebac46f9dc3",
last_practiced: "03-04-2021 13:25:10",
word_ids: ["1", "2", "3", "4", "5"],
}
word.updatePersonalWordLists(userID, listData).then(res => console.log(res))
My scheme
const mongoose = require('mongoose');
const personalWordListsSchema = new mongoose.Schema({
user_id: {
type: String
},
lists: Array
});
module.exports = personalWordListsSchema
I hope someone can help me to figure this out. Thanks in advance.
Thanks to #joe's comment I was able to make it work.
I removed the $ from the filter portion and used the ´´´$inc´´´ outside of the ´´´$set´´´ portion.
Word IDs are pushed only if they are unique.
Date of last practice can be updated.
Word count increments too.
const list = await personalWordLists.updateOne(
{"user_id": userID, "lists.list_id": listID },
{
$addToSet: { "lists.$.practiced_words": { $each: wordIDs}},
$set: {
"lists.$.last_practiced": last_practiced
},
$inc: {"lists.$.wordCount": numberOfNewWords}
}
)
return list

Update model, delete its array and populate new array using MongoDB Mongoose

I wish to ask for advice on the best way to do the following performance-wise.
so, for example, I have a model
person :{
_id: "12312",
name: "hello",
phones: [
{ number : "123456" }
]
}
when I update the person model, like the name: "newname", I also wish to clear the phone array and populate it with a new one.
i was thinking of doing a findById/findOne().deleteArray().populateArray() chaining.
what would be the best path to chain it in Mongoose?
I figure deleting the array is the quickest since the id is a random alphanumeric and not some incrementing long value so might as well delete everything and recreate the array
thoughts?
This is my function in Express/Mongoose
exports.update = function(req, res) {
Person.findByIdAndUpdate(req.params.person_id, person, { new: true, runValidators: true }, function(err, person) {
if (err) res.send(err);
res.json(person);
});
};
You can set the phones array with an empty array with findOneAndUpdate like this:
const newDoc = {
name: "newname",
phones: []
}
let doc = await Person.findOneAndUpdate({ name: newDoc.name }, newDoc, {
new: true
});
console.log(doc);
or if you want to replace existing phones with new ones you can use an object like this:
const newDoc = {
name: "newname",
phones: ["1","2"]
}

updating existing Mongo collection object in Meteor

I saw the other answers like this, however I think mine is a little more specific.
I have Meteor.user() as Object {_id: "iymu2h9uysCiKFHvc", emails: Array[2], profile: Object, services: Object}
and I'm running a function to set the profile first name and last name here:
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
profile: {
first_name: $('#firstName').val(),
last_name: $('#lastName').val()
}
}
});
I also, however, would like to, on a different event, add a a notifications object to the profile.
I tried :
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
profile: {
notifications: {
rcptDwnldFile: Session.get('recpt-dwnld-file'),
rcptPaysInv: Session.get('recpt-pays-inv'),
invSentDue: Session.get('inv-sent-due'),
// the rest
}
}
}
});
but that overrides my first_name, last_name entries. I also tried $setOnInstert but i get a update failed: Access denied. Operator $setOnInsert not allowed in a restricted collection. but I thought that profile was writable by the user by default.
use this instead (more info link - see section Set Fields in Embedded Documents):
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
'profile.first_name': $('#firstName').val(),
'profile.last_name': $('#lastName').val()
}
});
and
thisId = Meteor.userId();
Meteor.users.update({ _id: thisId }, { $set: {
'profile.notifications.rcptDwnldFile': Session.get('recpt-dwnld-file'),
'profile.notifications.rcptPaysInv': Session.get('recpt-pays-inv'),
'profile.notifications.invSentDue': Session.get('inv-sent-due'),
// the rest
}
});