set dynamic keys on update mongodb - javascript

I have mongoDB companies collection
c00:
{
_id: c00,
name: 'acme',
results: [
0: { _id: 'a10', name: 'foo', visible: true },
1: { _id: 'a11', name: 'bar', visible: false }
],
},
c01:
{
_id: c01,
name: 'apra'
results: [
0: { _id: 'b10', name: 'foo', visible: false },
1: { _id: 'b11', name: 'bar', visible: true },
2: { _id: 'b12', name: 'qux', visible: true },
]
}
}
I need query that modify
company.c01.results with resultId === 'b11'`
to
{ _id: 'b11', name: 'bar', visible: false }
I have tried
CompanyModel
.update(
{ [`${companyId}.results`]: resultsId },
{
$set: { ['results.$.visible']: false },
}
)
but this didn't work.
Any help will be appreciated

You need to put the dynamic key at the time of $set as well
CompanyModel.update(
{ [`${companyId}.results._id`]: resultsId },
{ $set: { [`${companyId}.results.$.visible`]: false }}
)

Try
CompanyModel.update(
{ "company.c01.sets._id": "b11" },
{ "$set": { "company.$.c01.visible": false } }
)

Related

Mongo Aggregation pipeline update or push

I have a MongoDB Model which consist of array of members as obejcts.
const guestSchema = new mongoose.Schema({
salutation: {
type: String,
},
members: [membersSchema],
user: {
type: mongoose.Schema.ObjectId,
ref: 'User',
},
});
Members Schema:
const membersSchema = new mongoose.Schema({
name: String,
status: {
type: String,
enum: ['regular', 'helper'],
default: 'regular',
},
});
I want to achieve of doing an update in case documet with given ID exist or push to an array in case ID with document in array does not exist. I use aggregation pipeline, however I am not able to achieve pushing new document to array. Why can't I use push after else statement like this.
const subDocumentToUpsert = { 'name': mem.name, 'status': mem.status, '_id': ObjectId(mem.id)}
const subDocumentNoID = { 'name': mem.name, 'status': mem.status}
await Guest.findOneAndUpdate(
{ "_id": req.params.id },
[
{
$set: {
members: {
$cond: {
if: { $in: [subDocumentToUpsert._id, '$members._id'] },
then: {
$map: {
input: '$members',
as: 'sub_document',
in: {
$cond: {
if: { $eq: ['$$sub_document._id', subDocumentToUpsert._id] },
then: subDocumentToUpsert,
else: '$$sub_document',
},
},
},
},
else: {
$push: {
subDocumentNoID
},
},
},
},
},
},
},
]);
What is the best way of doing so? Thank you
You can do as follow:
db.collection.update({
_id: {
$in: [
1,
2
]
}
},
[
{
$set: {
members: {
$cond: {
if: {
$in: [
5,
"$members._id"
]
},
then: {
$map: {
input: "$members",
as: "sub",
in: {
$cond: {
if: {
$eq: [
"$$sub._id",
5
]
},
then: {
_id: 5,
status: "regular_updated",
name: "Negan_updated"
},
else: "$$sub"
},
},
},
},
else: {
$concatArrays: [
"$members",
[
{
_id: 5,
status: "regular_upserted",
name: "Negan_upserted"
}
]
]
}
}
}
}
}
}
],
{
multi: true
})
Explained:
Check if _id:5 exist in the subobject and update via $map/$cond only the object that has the _id:5.
In case there is no _id:5 add the new object to the array with $concatArrays.
Playground

mongoDB group by nested document with unknown path

i want group by value and my problem is screen_size and screen_resulation is changeable
is there is something like wildcard in nested document in mongodb ?
i have tried this but its not working
[
{
filters: {
screen_size: {
name: "حجم الشاشة",
value: "50",
},
screen_resulation: {
name: "دقة الشاشة",
value: "4k",
},
},
},
{
filters: {
screen_resulation: {
name: "دقة الشاشة",
value: "UHD",
},
screen_size: {
name: "حجم الشاشة",
value: "55",
},
},
},
{
filters: {
screen_resulation: {
name: "دقة الشاشة",
value: "4k",
},
screen_size: {
name: "حجم الشاشة",
value: "55",
},
},
},
];
{
$group : {
_id: "$filters.*.value", counter: { $sum: 1 }
}
}
You can get the desired result with the below approach as well:
[{$project: {
_id: '$_id',
filters: { $objectToArray: '$filters' }
}}, {$unwind: {
path: '$filters'
}}, {$group: {
_id: '$filters.v.value',
count: {$sum: 1}
}}]

Build nested array based on list of ancestors and depth value

I have category model referencing itself. Below is data of category in mongodb.
{ "_id":{"$oid":"5f55acc029d19e1ac402908f"},
"parents":null,
"name":"pizza",
"slug":"pizza",
"userID":"5f38c867b10f740e38b12198",
"ancestors":[],
}
{ "_id":{"$oid":"5f55b3c0a7b68b3bc0fe16c5"},
"parents":{"$oid":"5f55acc029d19e1ac402908f"},
"name":"premium",
"slug":"premium",
"userID":"5f38c867b10f740e38b12198",
"ancestors":[{
"_id":{"$oid":"5f55acc029d19e1ac402908f"},
"name":"pizza",
"parents":null,
"slug":"pizza",
"depth":{"$numberInt":"0"}
}],
}
{ "_id":{"$oid":"5f55b726b6b12042d09057c2"},
"parents":{"$oid":"5f55b3c0a7b68b3bc0fe16c5"},
"name":"peri peri chicken",
"slug":"peri-peri-chicken",
"userID":"5f38c867b10f740e38b12198",
"ancestors":[{
"_id":{"$oid":"5f55b3c0a7b68b3bc0fe16c5"},
"name":"premium",
"parents":"5f55acc029d19e1ac402908f",
"slug":"premium",
"depth":{"$numberInt":"1"}
},
{
"_id":{"$oid":"5f55acc029d19e1ac402908f"},
"parents":null,
"name":"pizza",
"depth":{"$numberInt":"0"},
"slug":"pizza"
}]
}
{ "_id":{"$oid":"5f55bb8be3088f473c4e15ac"},
"parents":null,
"name":"burger",
"slug":"burger",
"userID":"5f38c867b10f740e38b12198",
"ancestors":[]
}
I have following model in mongoose
const ItemCategorySchema = new Schema({
name: {
type: String,
required: true
},
slug: {
type: String,
index: true
},
parents: {
type: Schema.Types.ObjectId,
default: null,
ref: 'ItemCategory'
},
ancestors: [{
_id: {
type: Schema.Types.ObjectId,
ref: "ItemCategory",
index: true
},
name: { type: String },
parents: { type: String },
slug: { type: String },
depth: { type: Number }
}],
userID: {
type: String,
required: true
}
});
How can I build array like below using the information in ancestors and depth. I am using mongoose. Is there any function in mongoose to populate all category of self referencing into any number of level or depth?
const options = [
{ value: 'pizza', label: 'pizza',
options: [
{ value: 'premium', label: 'premium',
options: [
{ value: 'peri-peri-chicken', label: 'peri peri chicken' },
{ value: 'chicken-and-bacon', label: 'chicken and bacon'},
],
},
{ value: 'traditional', label: 'traditional',
options: [
{ value: 'beef-and-onion', label: 'beef and onion' },
],
},
],
},
{ value: 'burger', label: 'burger',
options: [
{ value: 'chicken', label: 'chicken' },
{ value: 'beef', label: 'beef' },
],
},
]

Push objects to new array with checked: true, if in mapped array

Can I please get some help with this scenario?
An array of strings
A function that maps the array of strings and applies to each one of them a name, key and adds one more object "checked: false".
A function that takes the mapped array and transforms it according to the argument passed, storing the value in to another array and changing the "checked" to value "true"
Ex:
const defaultProducts = [
"Laptop",
"Tablet",
"Phone",
"Ram",
"SSD",
"RasberyPi",
"Desktop",
"TV",
"Monitor"
];
const getDefaultProducts = () => {
return defaultProducts.map(products => {
return {
name: products,
checked: false
};
});
};
console.log(getDefaultProducts())
let forSale = []
function useProduct(product){
if(product in getDefaultProducts()) {
return{
product: forSale.push(product),
checked: true
};
};
return {product};
}
console.log(useProduct("Laptop"))
console.log(forSale)
returns
[ { name: 'Laptop', checked: false },
{ name: 'Tablet', checked: false },
{ name: 'Phone', checked: false },
{ name: 'Ram', checked: false },
{ name: 'SSD', checked: false },
{ name: 'RasberyPi', checked: false },
{ name: 'Desktop', checked: false },
{ name: 'TV', checked: false },
{ name: 'Monitor', checked: false } ]
{ product: 'Laptop' }
[]
Should return:
[ { name: 'Laptop', checked: false },
{ name: 'Tablet', checked: false },
{ name: 'Phone', checked: false },
{ name: 'Ram', checked: false },
{ name: 'SSD', checked: false },
{ name: 'RasberyPi', checked: false },
{ name: 'Desktop', checked: false },
{ name: 'TV', checked: false },
{ name: 'Monitor', checked: false } ]
{ product: 'Laptop' }
[{name:"Laptop", checked: true}]
In the part where you checked the condition as if product in getDefaultProducts () will not work since getDefaultProducts is an array of objects. You are comparing strings with each object such as:
"Laptop" === { name: "Laptop", checked: false }
which will return false always. Instead you can use find function:
function useProduct(product){
getDefaultProducts().find(el => {
if (el.name === product) {
el.checked = true
forSale.push(el)
}
});
return product;
}
Try with this:
function useProduct(product){
const found = getDefaultProducts().find(p => p.name === product)
if (found) {
found.checked = true
forSale.push(found)
}
return {product}
}

How to use lodash to get a value out of a complex array of objects?

So i'm getting this pretty complex array from an API with lots of nested arrays with objects etc. It looks like this:
public data: any[] = [
{
language: 'Dutch',
sources: [
{
source: 'De Redactie',
channels: [
{ channel: 'binnenland', value: false },
{ channel: 'buitenland', value: false },
{ channel: 'sport', value: false },
{ channel: 'cultuur en media', value: false },
{ channel: 'politiek', value: false },
{ channel: 'financien', value: false }
]
},
{
source: 'Tweakers',
channels: [
{ channel: 'hardware', value: false },
{ channel: 'software', value: false },
{ channel: 'tech', value: false },
{ channel: 'smartphones', value: false },
{ channel: 'audio', value: false },
{ channel: 'video', value: false }
]
}
]
},
{
language: 'English',
sources: [
{
source: 'BBC',
channels: [
{ channel: 'news', value: false },
{ channel: 'sport', value: false },
{ channel: 'weather', value: false },
{ channel: 'travel', value: false },
{ channel: 'politics', value: false }
]
},
{
source: 'Fox News',
channels: [
{ channel: 'u.s.', value: false },
{ channel: 'world', value: false },
{ channel: 'opinion', value: false },
{ channel: 'politics', value: false },
{ channel: 'entertainment', value: false },
{ channel: 'business', value: false }
]
}
]
}
]
And i can extract the value that i want (the channel property) with this function:
setChannel(channel: string, source: string, language: string) {
for (const i of this.data) {
if (i.language === language) {
for (const j of i.sources) {
if (j.source === source) {
for (const k of j.channels) {
if (k.channel === channel) {
console.log(k.channel);
}
}
}
}
}
}
}
Now i'm sure that there is a function in lodash to simplify this. Because triple nested for's and if's is not a nice way of coding. But i can't seem to find it in the documentation. Could someone please point me in the right direction?
Lodash's filter() and flatMap() will get you there:
const result = _(data).filter({language: 'Dutch'})
.flatMap('sources')
.filter({source: 'De Redactie'})
.flatMap('channels')
.find({channel: 'sport'});
const data = [
{
language: 'Dutch',
sources: [
{
source: 'De Redactie',
channels: [
{ channel: 'binnenland', value: false },
{ channel: 'buitenland', value: false },
{ channel: 'sport', value: false },
{ channel: 'cultuur en media', value: false },
{ channel: 'politiek', value: false },
{ channel: 'financien', value: false }
]
},
{
source: 'Tweakers',
channels: [
{ channel: 'hardware', value: false },
{ channel: 'software', value: false },
{ channel: 'tech', value: false },
{ channel: 'smartphones', value: false },
{ channel: 'audio', value: false },
{ channel: 'video', value: false }
]
}
]
},
{
language: 'English',
sources: [
{
source: 'BBC',
channels: [
{ channel: 'news', value: false },
{ channel: 'sport', value: false },
{ channel: 'weather', value: false },
{ channel: 'travel', value: false },
{ channel: 'politics', value: false }
]
},
{
source: 'Fox News',
channels: [
{ channel: 'u.s.', value: false },
{ channel: 'world', value: false },
{ channel: 'opinion', value: false },
{ channel: 'politics', value: false },
{ channel: 'entertainment', value: false },
{ channel: 'business', value: false }
]
}
]
}
]
const result = _(data).filter({language: 'Dutch'})
.flatMap('sources')
.filter({source: 'De Redactie'})
.flatMap('channels')
.filter({channel: 'sport'})
.first();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
That's actually a pretty "quick & dirty" solution, because I didn't use any fallback default values. But you can read the docs about .find and .get.
const data = [{
language: 'Dutch',
sources: [{
source: 'De Redactie',
channels: [{
channel: 'binnenland',
value: false
},
{
channel: 'buitenland',
value: false
},
{
channel: 'sport',
value: false
},
{
channel: 'cultuur en media',
value: false
},
{
channel: 'politiek',
value: false
},
{
channel: 'financien',
value: false
}
]
},
{
source: 'Tweakers',
channels: [{
channel: 'hardware',
value: false
},
{
channel: 'software',
value: false
},
{
channel: 'tech',
value: false
},
{
channel: 'smartphones',
value: false
},
{
channel: 'audio',
value: false
},
{
channel: 'video',
value: false
}
]
}
]
},
{
language: 'English',
sources: [{
source: 'BBC',
channels: [{
channel: 'news',
value: false
},
{
channel: 'sport',
value: false
},
{
channel: 'weather',
value: false
},
{
channel: 'travel',
value: false
},
{
channel: 'politics',
value: false
}
]
},
{
source: 'Fox News',
channels: [{
channel: 'u.s.',
value: false
},
{
channel: 'world',
value: false
},
{
channel: 'opinion',
value: false
},
{
channel: 'politics',
value: false
},
{
channel: 'entertainment',
value: false
},
{
channel: 'business',
value: false
}
]
}
]
}
];
const setChannel = (channel = '', source = '', language = '') => {
// get all sources for the given language
const sourcesForLanguage = _.get(_.find(data, ['language', language]), 'sources');
// get all channels for given source
const channelsForSource = _.get(_.find(sourcesForLanguage, ['source', source]), 'channels');
// get the channel object for given channel
const selectedChannel = _.find(channelsForSource, ['channel', channel]);
console.log(selectedChannel);
}
setChannel('news', 'BBC', 'English');
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

Categories

Resources