MongoDB: Updating array item in collection is not working - javascript

I have a structure like this:
{
...
_id: <projectId>
en-GB: [{
_id: <entryId>,
key: 'some key',
value: 'some value',
}]
}
And I've tried updating it with Mongoose (and raw mongodb too) like this:
const result = await Project
.update({
_id: projectId,
'en-GB._id': entryId,
}, {
$set: {
'en-GB.$.key': 'g000gle!!',
},
})
.exec();
I've checked that the IDs are correct. But it doesn't update anything:
{ n: 0, nModified: 0, ok: 1 }
What am I doing wrong? Thanks

As discussed in the comments on the question, the issue is directly related to passing in a string representation of an id in the query as opposed to using an ObjectId. In general, it's good practice to treat the use of ObjectIds as the rule and the use of string representations as special exceptions (e.g. in methods like findByIdAndUpdate) in order to avoid this issue.
const { ObjectId } = require('mongodb');
.update({
_id: ObjectId(projectId),
'en-GB._id': ObjectId(entryId),
})

Related

firestore search query for sub-map [in JavaScript]

how to search a document where members map contains a particular map_id
chatRoom: {
MVyMPi78DOwVQ5w5GnYe: {
...
members: {
<member_1_id>: {
id: xxxxx,
unreadmessagecount: xx
},
<member_2_id>: {
id: xxxxx,
unreadmessagecount: xx
}
},
La234Pi78DOwVQ5w5GnYe: {
...
members: {
<member_1_id>: {
id: xxxxx,
unreadmessagecount: xx
},
<member_2_id>: {
id: xxxxx,
unreadmessagecount: xx
}
}
}
like for above structure get all chatrooms where members map contains member_id assume member_id is known
You can use dot notation to query documents based on nested field as shown below:
const q = query(collection(db, 'chatRoom'), where('members.user1', '==', {<obj>}))
This won't work for your use-case unless you know the exact object i.e. { uid: 'user1', unreadMessageCount: 1 }.
As a workaround, you can use orderBy() clause that'll only return documents where the field exists like this:
const q = query(collection(db, 'chatRoom'), orderBy('members.user1'))
However, this is actually querying the whole collection and so writing security rules will be a bit difficult. For example, if you use allow read: if request.auth.uid in resource.data.members, the rule will fail as there will be many documents where this returns false.
I would recommend using storing all members' UIDs in an array as follows:
{
...
group: false,
memberIds: ['user_1_uid', 'user_3_uid']
}
Then you can use array-contains operator to query all chat rooms of a user like this:
const q = query(collection(db, 'chatRoom'), where('memberIds', 'array-contains', 'some_uid'))
Then you can use the following security rules to ensure only a chat member can read/write the document:
allow read, write: if request.auth.uid in resource.data.memberIds

mongodb convert data to string. how to fix?

I want to get data from my database to work with variables.
Here is my database query:
db.progress.find({username:socket},function(err,res){
what do I get information on:
[ { _id: 61e180b54e0eea1454f8e5e6,
username: 'user',
exp: 0,
fraction: 'undf'} ]
if i send a request
db.progress.find({username:socket},{exp:1},function(err,res){
then in return I will get
[ { _id: 61e180b54e0eea1454f8e5e6, exp: 0 } ]
how do i extract only 0 from the received data. I would like to do something like this.
var findprogress = function(socket,cb){
db.progress.find({username:socket},function(err,res){
cb(res.exp,res.fraction)
})
}
findprogress(socket,function(cb){
console.log(cb.exp)//0
console.log(cb.fraction)//undf
})
but I don't know how to implement it correctly.
I recommend aggregate in this case so you can more easily manipulate your projected data. For example:
db.progress.aggregate([
{ $match: { username: socket } },
{ $project: { _id: 0, exp: 1 } }
])
This way you directly tell the query to not include the objectId, which is typically included by default.
find returns an array, so you will need to select the array element before accessing the field:
var findprogress = function(socket,cb){
db.progress.find({username:socket},function(err,res){
cb(res[0].exp,res[0].fraction)
})
}
Try to give 0 to _id:
db.progress.find({username:socket},{exp:1 , _id:0},function(err,res){

Is there a way to update an object in an array of a document by query in Mongoose?

I have got a data structure:
{
field: 1,
field: 3,
field: [
{ _id: xxx , subfield: 1 },
{ _id: xxx , subfield: 1 },
]
}
I need to update a certain element in the array.
So far I can only do that by pulling out old object and pushing in a new one, but it changes the file order.
My implementation:
const product = await ProductModel.findOne({ _id: productID });
const price = product.prices.find( (price: any) => price._id == id );
if(!price) {
throw {
type: 'ProductPriceError',
code: 404,
message: `Coundn't find price with provided ID: ${id}`,
success: false,
}
}
product.prices.pull({ _id: id })
product.prices.push(Object.assign(price, payload))
await product.save()
and I wonder if there is any atomic way to implement that. Because this approach doesn't seem to be secured.
Yes, you can update a particular object in the array if you can find it.
Have a look at the positional '$' operator here.
Your current implementation using mongoose will then be somewhat like this:
await ProductModel.updateOne(
{ _id: productID, 'prices._id': id },//Finding Product with the particular price
{ $set: { 'prices.$.subField': subFieldValue } },
);
Notice the '$' symbol in prices.$.subField. MongoDB is smart enough to only update the element at the index which was found by the query.

Mongoose virtuals are not added to the result

I'm building a quiz editor where rounds contain questions and questions can be in multiple rounds. Therefor I have the following Schemes:
var roundSchema = Schema({
name: String
});
var questionSchema = Schema({
question: String,
parentRounds: [{
roundId: { type: Schema.Types.ObjectId, ref: 'Round'},
isOwner: Boolean
}]
});
What I want is to query a round, but also list all questions related to that round.
Therefor I created the following virtual on roundSchema:
roundSchema.virtual('questions', {
ref : 'Question',
localField : '_id',
foreignField : 'parentRounds.roundId'
});
Further instantiating the Round and Question model and querying a Round results in an object without questions:
var Round = mongoose.model('Round', roundSchema, 'rounds');
var Question = mongoose.model('Question', questionSchema, 'questions');
Round.findById('5ba117e887f66908ae87aa56').populate('questions').exec((err, rounds) => {
if(err) return console.log(err);
console.log(rounds);
process.exit();
});
Result:
Mongoose: rounds.findOne({ _id: ObjectId("5ba117e887f66908ae87aa56") }, { projection: {} })
Mongoose: questions.find({ 'parentRounds.roundId': { '$in': [ ObjectId("5ba117e887f66908ae87aa56") ] } }, { projection: {} })
{ _id: 5ba117e887f66908ae87aa56, __v: 0, name: 'Test Roundname' }
As you can see, I have debugging turned on, which shows me the mongo queries. It seems like the second one is the one used to fill up the virtual field.
Executing the same query using Mongohub DOES result in a question:
So why doesn't Mongoose show that questions array I'm expecting?
I've also tried the same example with just one parentRound and no sub-objects, but that also doesn't work.
Found the answer myself...
Apparently, I have to use
console.log(rounds.toJSON({virtuals: true}));
instead of
console.log(rounds);
Why would Mongoose do such a devil thing? :(

in mongodb, whats the easiest way to get an array of a single field from the documents

Assume, I have these documents in my collection:
{
_id: xxx,
region: "sw"
},
{
_id: yyy,
region: "nw"
}
I want to end up with an array like this:
['sw', 'nw']
I have tried mongodb aggregation/group and mapreduce but I always end up with an array of documents that then have to be looped through again to get to a single array. Is there a way to achieve this in a single mongodb query or will it always require a query and then further processing?
Try this:
db.foo.aggregate(
{$group: {_id: null, region: {$push: "$region"}}}
).result[0].region
Or if you want to use map-reduce:
db.foo.mapReduce(
function() { emit(null, this.region)},
function(key, values) {
result = {region: []};
values.forEach(function(x){ result.region.push(x); });
return result;
},
{out: {inline: 1}}
).results[0].value.region
Or using group (thanks to #Travis for adding this one):
db.foo.group({
reduce: function (curr, result) {
result.regions.push(curr.region);
},
initial: { regions: [] }
})[0].regions
Note
Using each of this methods you have to remember about BSON Document Size Limits

Categories

Resources