Updating an array object creates a new empty object - javascript

I have an array of orders containing user ID, the amount and the order itself. Each user can only have one order at a time but I'm trying to add an option to edit your order. I created a method that is supposed to be doing that:
'click .edit': function (event) {
var order = $('#editOrder').val();
var price = $('#editPrice').val();
Meteor.call('changeOrder', Router.current().data()._id, Meteor.userId(), order, price);
Session.set("editing", false);
},
changeOrder: function (id, user, order, amount) {
Polls.update({_id: id, 'Orders.User': user}, {$set: {'Orders.$': {
User: user,
Order: order,
Amount: parseFloat(amount)
}}});
},
This method actually works but the problem is every time I edit an order it creates a new object with the same user ID and empty Order and Amount properties.I honestly have no idea what could be causing an update function to insert void data.
Here's an example of the Poll structure with the extra empty order:
{
"_id" : "4wGAPfxCvKfH4L8JL",
"Company" : "FirmaTest",
"Restaurants" : [
"Trylinka",
"Da Grasso",
"Faster",
"Green Way",
"Telepizza",
"Piramida"
],
"Expires" : ISODate("2015-08-24T08:26:00.791Z"),
"Votes" : {
"Trylinka" : 1,
"Da Grasso" : 0,
"Faster" : 2,
"Green Way" : 3,
"Telepizza" : 0,
"Piramida" : 0
},
"Voted" : [
"TfQM7954a5SHoR9os"
],
"Winner" : "Green Way",
"Orders" : [
{
"User" : "TfQM7954a5SHoR9os",
"Order" : "Some chicken",
"Amount" : 15
},
{
"User" : "TfQM7954a5SHoR9os",
"Order" : null,
"Amount" : NaN
}
],
"Ordered" : [
"TfQM7954a5SHoR9os"
]
}
Solved
Turns out I was triggering a submit form located in the same page. Even though the event I was calling wasn't a 'form submit' it had a form in it and so the submit still went through. Also I used the same variable names in both the form submit and the button trigger so the insert methods still went through.
Thanks for the answers but it turns out the problem was somewhere else and my fault entirely :)

Is it possible you are querying _id with a string instead an ObjectID?
Try converting the id into an ObjectID with:
new Meteor.Collection.ObjectID(valuefromhtml)
So your code will be:
Polls.update({
_id: new Meteor.Collection.ObjectID(id),
'Orders.User': user
}, {
$set: {
'Orders.$': {
User: user,
Order: order,
Amount: parseFloat(amount)
}
}
});

Not sure what that bug is, but you have no need for "overwriting" your entire object with that update, which could be what causes this. You can just update the only two properties that are actually subject to change:
Polls.update(
{_id: id, 'Orders.User': user},
{$set:
{'Orders.$.Order': order, 'Orders.$.Amount': parseFloat(amount)}
}
);

Try to set upsert to false:
Polls.update({_id: id, 'Orders.User': user},
{$set:
{'Orders.$':
{
User: user,
Order: order,
Amount: parseFloat(amount)
}
}
},
{ upsert: false });
If this don't work, try with upsert: false on BraveKenny's answer.
I hope this helps.

Related

How to update specific data in array in MongoDB?

{
"_id":{"$oid":"5f5287db8c4dbe22383eca58"},
"__v":0,
"createdAt":{"$date":"2020-09-12T11:35:45.965Z"},
"data":["Buy RAM","Money buys freedom"],
"updatedAt":{"$date":"2020-09-12T11:38:10.637Z"}
}
I want to update the first element in this data array field as Buy SSD.
How can I do it using NodeJS?
db.collection.findOneAndUpdate({
"_id.$oid": "5f5287db8c4dbe22383eca58",
data: "Buy RAM"
}, {
$set: {
"data.$" "Buy SSD"
}
})
This query updates the first element in the data array inside the document that matches "_id.$oid": "5f5287db8c4dbe22383eca58", using $ positional identifier and sets it to the new value.
For more Array Update Operators in mongodb, here is a reference: mongodb manual
You can use filtered positional operator
db.collectionName.updateOne(
{
"_id.$oid": "5f5287db8c4dbe22383eca58"
},
{
$set: {
"data.$[element]": "Buy SSD"
}
},
{
{ arrayFilters: [ { element: "Buy Ram" } ] }
})
Caution: It will update all array element matching the text. In this case, "Buy Ram"
You can use str.replace()
var product =
[{
"_id":{"$oid":"5f5287db8c4dbe22383eca58"},
"__v":"0",
"createdAt":{"$date":"2020-09-12T11:35:45.965Z"},
"data":["Buy RAM","Money buys freedom"],
"updatedAt":{"$date":"2020-09-12T11:38:10.637Z"}
}]
var new_product = JSON.stringify(product).replace("Buy RAM", "Something");
console.log(new_product);
UpdateOne -> Updates a single Document:
db.collection.updateOne(filter, update, options)
You will probably gonna filter using the _id field, and use $set to update the specific field.
Use the dot-notation to access and set fields deep inside objects, without affecting the other properties of those objects.
you want to update the 1st array entry in "data", and array keys are 0 indexed - that's the key 0.
so the query will look something like that:
db.collection.update(
{ _id: { "$oid": "56476e04e5f19d86ece5b81d"}, // probb ObjectId Instance
{ $set:
{
"data.0": "Buy SSD" // Using dot-notation
}
}
)
for more advanced use, you can use the MongoDB's positional operator $ without explicitly specifying the position of the element in the array.
The positional operator allows you to use a condition like this:
{"Order.name": "test"}
and then reference the found array entry like so:
{"Order.$ // <- the dollar represents the first matching array key index
Example:
/* DATA
{
"_id" : "43434",
"Order" : [
{"name" : "test", "items" : ["", "new_value", "" ]},
{"name" : "test2", "items" : ["", "", "" ]}
]
}
*/
db.collection.update(
{ _id: "43434", "Order.name": "test2"},
{ $set:
{
"Order.$.items.1": "new_value2" // positional operator & dot-notation.
}
}
)
>>> db.collection.find()
{
"_id" : "43434",
"Order" : [
{"name" : "test", "items" : ["", "new_value", "" ]},
{"name" : "test2", "items" : ["", "new_value2", "" ]}
]
}

Meteor collection find and update value within object in subarray

I'm having some trouble determining how to find a document within a collection, and a value within an object in a subarray of that document — and then update a value within an object in that array.
I need to do the following:
find by _id
find object in ratings array that matches the user + post keys
update the report value within that object
For example, the documents in my collection are set up like below.
{
"_id" : "mz32AcxhgBLoviRWs",
"ratings" : [
{
"user" : "mz32AcxhgBLoviRWs",
"post" : "SMbR6s6SaSfsFn5Bv",
"postTitle" : "fdsfasdf",
"date" : "2017-09-27",
"rating" : "4",
"review" : "sdfa",
"report" : "a report"
},
{
"user" : "mz32AcxhgBLoviRWs",
"post" : "iZbjMCFR3cDNMo57W",
"postTitle" : "today",
"date" : "2017-09-27",
"rating" : "4",
"review" : "sdfa",
"report" : "some report"
}
]
}
It seems that you want just one update, not three separated queries.
Collection.update({
_id: <id>,
ratings: {
$elemMatch: {
user: <user>,
post: <post>
}
}
}, {
$set: {
'ratings.$.report': <report>
}
});
Documentation: $elemMatch, <array>.$.

Update multiple documents in MongoDB by altering object in array

I have a simple application with registration/login and it is basically a coursera/udemy type, where the app lists specific courses and users can like them or enroll in them. I have been trying to make a mongodb function that updates a user in the database and since users can like the courses it has to update all courses too (courses have a field "usersLiked", which is an array and keep all user documents which have liked it).
The course structure is the following:
{
"_id" : ObjectId("5977662564aac9f6c8d48884"),
"title" : "Title",
"lecturer" : "Lecturer",
"length" : 30,
"coverPhoto" : "Photo",
"usersLiked": [
{
"_id" : ObjectId("597763e346a7a463cbb8f529"),
"fullname" : "Name",
"username" : "Username",
"password" : "Hash",
"city" : "City",
"street" : "Street",
"website" : "Website"
}
],
"lectures" : [
{
"title" : "Introduction",
"number" : 1,
"url" : "someURL"
}
]
}
And the user structure:
{
"_id" : ObjectId("597763e346a7a463cbb8f529"),
"fullname" : "Name",
"username" : "Username",
"password" : "Hash",
"enrolledCourses" : [
...
]
}
],
"city" : "City",
"street" : "Street",
"website" : "Website"
}
So now I am calling this function when I want to change. It changes the userCollection but in the courseCollection it does nothing, while it should get all courses and if some of them have an object with username(the user's username) in the "usersLiked" array it should modify the user there too.
const updateUser = (username, details) => {
usersCollection
.update({
username: username,
}, {
$set: {
fullname: details.fullname,
city: details.city,
street: details.street,
website: details.website,
},
});
coursesCollection
.updateMany(
{
usersLiked: {
$elemMatch: {
username: username,
},
},
},
{
$set: {
'usersLiked.username': details.username,
'usersLiked.city': details.city,
'usersLiked.street': details.street,
'usersLiked.website': details.website,
},
}
);
};
Your match on the course update looks valid but you are trying to set values into an array and according to the Mongo docs you need to provide an array indexer or a positional operator.
The following will allow the set command to operate on the first element within the usersLiked array which matches the given username.
coursesCollection.updateMany(
{
usersLiked: {
$elemMatch: {
username: username,
},
},
},
{
$set: {
'usersLiked.$.username': details.username,
'usersLiked.$.city': details.city,
'usersLiked.$.street': details.street,
'usersLiked.$.website': details.website
},
}
)
You could also choose which element in the usersLiked array to update e.g. usersLiked.1.username but I suspect each course only has one element in usersLiked for a given username in which case using the $ operator (which means: the first matching array element) should suffice.

mongoDB: get field value of an array object

I want to get the value of the field 30 in the object (in the array test) with the id ePce6fBAHx9KeKjuM.
{
"_id" : "nAwt3b76c24mZfqxz",
"title" : "test",
"test" : [
{
"4" : false,
"15" : false,
"30" : false,
"75" : true,
"id" : "ePce6fBAHx9KeKjuM"
}
]
}
So this result would be false
I tried something like
var result = Collection.findOne({_id: 'nAwt3b76c24mZfqxz'}).test;
But this would give me the complete array. But I need the selected object and only a selected field of this object (ie. 30).
test is just a JS array. Use normal array syntax to access its elements:
var result = Collection.findOne({_id: 'nAwt3b76c24mZfqxz'}).test["30"];
EDIT:
To retrieve the whole object with only 1 element of the array use projection, as of zangw's answer. Following your comment to test element itself:
db.getCollection('a').find(
// put your nested document's condition instead of `$exists`
{_id: 'nAwt3b76c24mZfqxz', test:{ $elemMatch: { "30": {$exists: true}}}},
// add other fields you like to retrieve, e.g. "title":1
{"test.30":1}
)
Try this one
Collection.find({{_id: 'nAwt3b76c24mZfqxz'}}, {'test.30': 1, 'test.id': 1});
To select the whole test array as following without _id
Collection.find({{_id: 'nAwt3b76c24mZfqxz'}}, {'test': 1, '_id': 0});

Query nested document with mongoose

I know this question has been asked a lot of times but I'm kinda new to mongo and mongoose as well and I couldn't figure it out !
My problem:
I have a which looks like this:
var rankingSchema = new Schema({
userId : { type : Schema.Types.ObjectId, ref:'User' },
pontos : {type: Number, default:0},
placarExato : {type: Number, default:0},
golVencedor : {type: Number, default:0},
golPerdedor : {type: Number, default:0},
diferencaVencPerd : {type: Number, default:0},
empateNaoExato : {type: Number, default:0},
timeVencedor : {type: Number, default:0},
resumo : [{
partida : { type : Schema.Types.ObjectId, ref:'Partida' },
palpite : [Number],
quesito : String
}]
});
Which would return a document like this:
{
"_id" : ObjectId("539d0756f0ccd69ac5dd61fa"),
"diferencaVencPerd" : 0,
"empateNaoExato" : 0,
"golPerdedor" : 0,
"golVencedor" : 1,
"placarExato" : 2,
"pontos" : 78,
"resumo" : [
{
"partida" : ObjectId("5387d991d69197902ae27586"),
"_id" : ObjectId("539d07eb06b1e60000c19c18"),
"palpite" : [
2,
0
]
},
{
"partida" : ObjectId("5387da7b27f54fb425502918"),
"quesito" : "golsVencedor",
"_id" : ObjectId("539d07eb06b1e60000c19c1a"),
"palpite" : [
3,
0
]
},
{
"partida" : ObjectId("5387dc012752ff402a0a7882"),
"quesito" : "timeVencedor",
"_id" : ObjectId("539d07eb06b1e60000c19c1c"),
"palpite" : [
2,
1
]
},
{
"partida" : ObjectId("5387dc112752ff402a0a7883"),
"_id" : ObjectId("539d07eb06b1e60000c19c1e"),
"palpite" : [
1,
1
]
},
{
"partida" : ObjectId("53880ea52752ff402a0a7886"),
"quesito" : "placarExato",
"_id" : ObjectId("539d07eb06b1e60000c19c20"),
"palpite" : [
1,
2
]
},
{
"partida" : ObjectId("53880eae2752ff402a0a7887"),
"quesito" : "placarExato",
"_id" : ObjectId("539d0aa82fb219000054c84f"),
"palpite" : [
2,
1
]
}
],
"timeVencedor" : 1,
"userId" : ObjectId("539b2f2930de100000d7356c")
}
My question is, first: How can I filter the resumo nested document by quesito ? Is it possible to paginate this result, since this array is going to increase. And last question, is this a nice approach to this case ?
Thank you guys !
As noted, your schema implies that you actually have embedded data even though you are storing an external reference. So it is not clear if you are doing both embedding and referencing or simply embedding by itself.
The big caveat here is the difference between matching a "document" and actually filtering the contents of an array. Since you seem to be talking about "paging" your array results, the large focus here is on doing that, but still making mention of the warnings.
Multiple "filtered" matches in an array requires the aggregation framework. You can generally "project" the single match of an array element, but this is needed where you expect more than one:
Ranking.aggregate(
[
// This match finds "documents" that "contain" the match
{ "$match": { "resumo.quesito": "value" } },
// Unwind de-normalizes arrays as documents
{ "$unwind": "$resumo" },
// This match actually filters those document matches
{ "$match": { "resumo.quesito": "value" } },
// Skip and limit for paging, which really only makes sense on single
// document matches
{ "$skip": 0 },
{ "$limit": 2 },
// Return as an array in the original document if you really want
{ "$group": {
"_id": "$_id",
"otherField": { "$first": "$otherField" },
"resumo": { "$push": "$resumo" }
}}
],
function(err,results) {
}
)
Or the MongoDB 2.6 way by "filtering" inside a $project using the $map operator. But still you need to $unwind in order to "page" array positions, but there is possibly less processing as the array is "filtered" first:
Ranking.aggregate(
[
// This match finds "documents" that "contain" the match
{ "$match": { "resumo.quesito": "value" } },
// Filter with $map
{ "$project": {
"otherField": 1,
"resumo": {
"$setDifference": [
{
"$map": {
"input": "$resumo",
"as": "el",
"in": { "$eq": ["$$el.questio", "value" ] }
}
},
[false]
]
}
}},
// Unwind de-normalizes arrays as documents
{ "$unwind": "$resumo" },
// Skip and limit for paging, which really only makes sense on single
// document matches
{ "$skip": 0 },
{ "$limit": 2 },
// Return as an array in the original document if you really want
{ "$group": {
"_id": "$_id",
"otherField": { "$first": "$otherField" },
"resumo": { "$push": "$resumo" }
}}
],
function(err,results) {
}
)
The inner usage of $skip and $limit here really only makes sense when you are processing a single document and just "filtering" and "paging" the array. It is possible to do this with multiple documents, but is very involved as there is no way to just "slice" the array. Which brings us to the next point.
Really with embedded arrays, for paging that does not require any filtering you just use the $slice operator, which was designed for this purpose:
Ranking.find({},{ "resumo": { "$slice": [0,2] } },function(err,docs) {
});
Your alternate though is to simply reference the documents in the external collection and then pass the arguments to mongoose .populate() to filter and "page" the results. The change in the schema itself would just be:
"resumo": [{ "type": "Schema.Types.ObjectId", "ref": "Partida" }]
With the external referenced collection now holding the object detail rather than embedding directly in the array. The use of .populate() with filtering and paging is:
Ranking.find().populate({
"path": "resumo",
"match": { "questio": "value" },
"options": { "skip": 0, "limit": 2 }
}).exec(function(err,docs) {
docs = docs.filter(function(doc) {
return docs.comments.length;
});
});
Of course the possible problem there is that you can no longer actually query for the documents that contain the "embedded" information as it is now in another collection. This results in pulling in all documents, though possibly by some other query condition, but then manually testing them to see if they were "populated" by the filtered query that was sent to retrieve those items.
So it really does depend on what you are doing and what your approach is. If you regularly intend to "search" on inner arrays then embedding will generally suit you better. Also if you really only interesting in "paging" then the $slice operator works well for this purpose with embedded documents. But beware growing embedded arrays too large.
Using a referenced schema with mongoose helps with some size concerns, and there is methodology in place to assist with "paging" results and filtering them as well. The drawback is that you can no longer query "inside" those elements from the parent itself. So parent selection by the inner elements is not well suited here. Also keep in mind that while not all of the data is embedded, there is still the reference to the _id value of the external document. So you can still end up with large arrays, which may not be desirable.
For anything large, consider that you will likely be doing the work yourself, and working backwards from the "child" items to then match the parent(s).
I am not sure that you can filter sub-document directly with mongoose. However you can get the parent document with Model.find({'resumo.quesito': 'THEVALUE'}) (you should also and an index on it)
Then when you have the parent you can get the child by comparing the quesito
Additionnal doc can be found here: http://mongoosejs.com/docs/subdocs.html

Categories

Resources