{
_id:xxxxstoreid
store:{
products:[
{
_id:xxxproductid,
name:xxx,
img:url,
}
]
}
}
since i cannot predict the request for update, params may have just name or it may have both.
here is my query,it updates successfully but it removes other fields if they are not present in the params.
eg:
var params={_id:xxid,name:'xxx',img:'xxx'}
or
var params={_id:xxid,name:'xxx'}
in this case if params have just name it removes img field and updates.
User.update({'store.products._id':params._id},{$set:{"store.products":params}},callback);
You need to supply the multiple keys to $set with the positional $ operator to update both matched keys.
I prefer the modern ES6 way of object manipulation:
let params = { "_id" : "xxxproductid", "name" : "xxx", "img" : "yyy" };
let update = [
{ 'store.products._id': params._id },
{ "$set": Object.keys(params).filter(k => k != '_id')
.reduce((acc,curr) =>
Object.assign(acc,{ [`store.products.$.${curr}`]: params[curr] }),
{ })
}
];
User.update(...update,callback);
Which would produce the call to MongoDB as ( with mongoose.set('debug', true) ) turned on so we see the request:
Mongoose: users.update({ 'store.products._id': 'xxxproductid' }, { '$set': { 'store.products.$.name': 'xxx', 'store.products.$.img': 'yyy' } }, {})
Where basically you take your input params and supply the _id as the first argument for the "query" :
{ 'store.products._id': params._id },
The rest takes the "keys" from the object via Object.keys which makes an "array" which we can "filter" with Array.filter() and then pass to Array.reduce to transform those keys into an Object.
Inside the .reduce() we call Object.assign() which "merges" objects with the given keys, generated in this form:
Object.assign(acc,{ [`store.products.$.${curr}`]: params[curr] }),
Using the template syntax to assign the "current" (curr) "key" into the new key name, again using the ES6 key assignment syntax []: which allows variable names in object literals.
The resulting "merged" object is passed back to be assigned to the "root" object where $set is used for the key of the update, so the "generated" keys are now children of that.
I use an array for the arguments purely for debugging purposes, but then that also allows cleaner syntax on the actual .update() using the "spread" ... operator to assign the arguments:
User.update(...update,callback);
Clean and simple, and some JavaScript techniques that you should learn for object and array manipulation. Mostly since the MongoDB query DSL is basically "Objects" and "Arrays". So learn to manipulate them.
function updateProducts(params) {
var query = {'store.products': {$elemMatch: {_id: params._id}}}
var updateObject = null;
if (params.name && params.img) {
updateObject = {$set: {'store.products.$': params}}
} else if(params.name && !params.img) {
updateObject = {$set: {'store.products.$.name': params.name}}
} else if (params.img && !params.name) {
updateObject = {$set: {'store.products.$.img': params.img}}
}
User.update(query, updateObject, callback)
}
The below query will use $ positional operator to locate the array element at index found from matching the array by _id in the query document followed by updating the fields for the element with the params values.
var params = {_id:1, name:'xxx',img:'yyy'};
var id = params['_id']; // Get id to locate matching array index
delete params['_id']; // Remove id from the object
Object.keys(params).forEach(function (key){params['store.products.$.'+key] = params[key]; delete params[key];}) // Transforms remaining object keys to include the positional $ placeholder to { "store.products.$.name" : "xxx", "store.products.$.img" : "yyy" }
User.update('store.products._id':id},{$set:params},callback);
Related
I have a Node.js program that is using Mongo Atlas search indexes and is utilizing the Aggregate function inside of the MongoDB driver. In order to search, the user would pass the search queries inside of the query parameters of the URL. That being said, I am trying to build a search object based on if a query parameter exists or not. In order to build the search object I am currently using object spread syntax and parameter short-circuiting, like so:
const mustObj = {
...(query.term && {
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
})
}
This is a shortened version, as there are many more parameters, but you get the jest.
In a MongoDB search query, if you have multiple parameters that must meet a certain criteria, they have to be included inside of an array called must, like so:
{
$search: {
compound: {
must: [],
},
},
}
So, in order to include my search params I must first turn my mustObj into an array of objects using Object.keys and mapping them to an array, then assigning the searches 'must' array to the array I've created, like so:
const mustArr = Object.keys(mustObj).map((key) => {
return { [key === 'text2' ? 'text' : key]: mustObj[key] };
});
searchObj[0].$search.compound.must = mustArr;
What I would like to do is, instead of creating the mustObj and then looping over the entire thing to create an array, is to just create the array using the spread syntax and short-curcuiting method I used when creating the object.
I've tried the below code, but to no avail. I get the 'object is not iterable' error:
const mustArr = [
...(query.term && {
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
})
]
In all, my question is, is what I'm asking even possible? And if so, how?
Corrected based on #VLAZ comment:
while spread with array [...(item)], item has to be array (iterable).
When you use short-circuit, the item as below,
true && [] ==> will be `[]` ==> it will work
false && [] ==> will be `false` ==> wont work (because false is not array)
try some thing like (Similar to #Chau's suggestion)
const mustArr = [
...(query.term ? [{
text: {
query: query.term,
path: ['name', 'description', 'specs'],
fuzzy: {
maxEdits: 2.0,
},
},
}] : [])
]
I have an array, clients, which I want to run array.find() on. This array contains objects, and usually looks something like this:
[ { customId: 'user1', clientId: 'TPGMNrnGtpRYtxxIAAAC' },
{ customId: 'user2', clientId: 'G80kFbp9ggAcLiDjAAAE' } ]
This is where I encounter a problem. I am trying to use find() to see if any object (or part of an object) in the array matches a certain variable, recipient, which usually contains a value like user1. the code I am using to do this is:
function checkID(recipient) {
return recipient;
}
var found = clients.find(checkID);
This always returns the first object in the array. Am I using find() wrong, or is there a better way to do this?
find takes a predicate (a function that returns true if item is a match and false if item is not a match).
const arr = [ { customId: 'user1', clientId: 'TPGMNrnGtpRYtxxIAAAC' },
{ customId: 'user2', clientId: 'G80kFbp9ggAcLiDjAAAE' } ]
const result = arr.find(item => item.customId === 'user1')
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// This should evaluate to true for a match and to false for non-match
The reason you're getting the first item of your array all the time, is because your checkId function is returning something which evaluates to true. So, the first item is evaluated and produces a truthy result, and therefore it gets picked as the first match.
If unfamiliar with the lambda syntax () => {}, then that line is similar to:
const result = arr.find(function (item) { return item.customId === 'user1' })
You are using find wrong.
If recipient contains information about the target value you should name the first param of checkID with a different name. And compare any property of it with recipient.
var found = clients.find(function(element) { return element.prop1 === recipient.anyProp; });
To check the objects in the array for the presence of a certain customId, put the value you're searching for in an object, and pass that object to find():
let clients = [{
customId: "user1",
clientId: "TPGMNrnGtpRYtxxIAAAC"
},
{
customId: "user2",
clientId: "G80kFbp9ggAcLiDjAAAE"
}
];
function checkID(el){
return el.customId === this.param;
}
let found = clients.find(checkID, {param: "user1"});
console.info(found);
I am currently using array filters to update the nested object.
My structure is -
Category Collection -
{
name:Disease,
_id:ObjectId,
subCategory:[{
name:Hair Problems,
_id:ObjectId,
subSubCategory:[{
name: Hair Fall,
_id:ObjectId
},{
name: Dandruff,
_id:ObjectId
}]
}]
}
I want to update the subsubcategory with id 1.1.1 which I am doing by using array filters.
let query = { 'subCategories.subSubCategories._id': subSubId };
let update = { $set: { 'subCategories.$.subSubCategories.$[j]': data } };
let option = { arrayFilters: [{ 'j._id': subSubId }], new: true };
await Categories.findOneAndUpdate(query, update, option
This code is working fine but array filters change the object id of subsubCategory. Is there any other alternative to do so without changing the ObjectId.
Thanks in advance
You can loop over the keys which you are getting as payload and put inside the $set operator.
const data = {
firstKey: "key",
secondKey: "key2",
thirdKey: "key3"
}
const object = {}
for (var key in data) {
object[`subCategories.$.subSubCategories.$[j].${key}`] = data[key]
}
let query = { 'subCategories.subSubCategories._id': subSubId };
let update = { '$set': object };
let option = { 'arrayFilters': [{ 'j._id': subSubId }], 'new': true };
await Categories.findOneAndUpdate(query, update, option)
Problem is in $set line there you have not mentioned specific fields to be update instead subCategory.$.subSubCategory.$[j] will replace complete object element that matches the _id filter. Hence your _id field is also getting updated. You have to explicitly mention the field name after array element identifier. See example below:
Suppose you want to update name field in subSubCategories from Dandruff to new Dandruff. Then do this way:
let update = { $set: { 'subCategories.$.subSubCategories.$[j].name': "new Dandruff" } };
This will only update name field in subSubCategories array
I need to find nested objects in MongoDb using the Node.js Driver.
I'm having trouble accessing nested properties when the property name is dynamic. Here's my code:
//This gives expected results but "name1" isn't dynamic
collection.find({ 'followers.name1': { $exists: false } })
//Here's what I tried that does not give expected results
const username = "name1"
let query = { followers: {} }
query.followers[username] = { $exists: false }
collection.find(query)
Here's an example of the database structure:
{
"_id":"xxxxxxxxxxx",
"dateAdded":"2017-09-20T08:36:40.325Z",
"followers":{
"name1":{
"followedOn":"2017-09-20T08:36:40.325Z",
"unfollowedOn":null
},
"name2":{
"followedOn":"2017-09-20T08:36:40.325Z",
"unfollowedOn":null
}
}
}
Edit: my question is not a duplicate of the one marked as a duplicate. MongoDb find() argument is not an object literal. That's the whole point of my question, using like it like an object literal doesn't work.
I found the solution myself in the end. The key needs to be a string so you need to do:
const username = 'name1'
let query = {}
query['followers.'+username] = { $exists: false }
collection.find(query)
quick question about Firebase lists.
I'm trying to access multiple lists to iterate through that are in a single object. The object looks like this:
user : {
playlists: {}, // List 1
joined: {}, // List 2
favorites: {} // List 3
}
Now I know I can access all three lists by doing three separate requests like:
firebase.list('user/playlists');
firebase.list('user/joined');
firebase.list('user/favorites');
But I'm trying to clean up my code and do it all in one by using:
firebase.list('user');
Then accessing all the lists it returns inside of the user. The only problem is, the lists aren't returned with keys, it just says "object" instead. When I use firebase.object('user') the keys are available.
This is what it looks like when I request 'user' as a list:
As you can see the key is inside the object and not outside of it ($key). How do I go about accessing these objects like I would if the key was actually the key (eg. user.playlists).
For anyone interested I figured it out. I used the .map() to convert parts of the object into arrays.
getPlaylist(_playlistId: string, _query: Object = {}) {
return this._database.object(`playlists/${_playlistId}`, _query).map(
result => {
return {
info: result.info,
leader: result.leader,
favorites: (result.favorites) ? Object.values(result.favorites) : [],
followers: (result.followers) ? Object.values(result.followers) : [],
songs: (result.songs) ? Object.values(result.songs) : []
};
}
);
}
For example, favorites is an object that I want to be returned as a list:
Check if exists If exists, map obj to array else assign to empty array
(result.favorites) ? Object.values(result.favorites) : []