You mean something like this:
var updateBlock = {};
if (phoneNumber)
updateBlock['shared.phones.$.phone_number'] = phoneNumber;
if (emailAddress)
updateBlock['shared.email.$.email_address'] = emailAddress;
Contacts.updateMany(
{
"originator": profile.owner_id
"owner_id": { "$ne": profile.owner_id }
},
{ "$set": updateBlock },
function(err, numAffected) {
// work with callback
}
)
That addresses your two "main" misconceptions here in that the "inequality" in the query condition requires the $ne operator and not the ! JavaScript expression. MongoDB does not use JavaScript expressions here for the query conditions.
The second "main" misconception is the construction of the "update block" with conditional keys. This is by contrast a "JavaScript Object" which you construct separately in order to specify only the keys you wish to effect.
However there is STILL A PROBLEM in that you want to use the positional $ operator. Presuming you actually have "arrays" in the document like this:
{
"originator": "Bill",
"owner_id": "Ted",
"shared": {
"phones": [ "5555 5555", "4444 4444" ],
"email": [ "bill@stalyns.org", "bill@example.com" ]
}
}
Then your "two-fold" new issue is that:
You must specify a query condition that matches the array element "in the query block" in order to obtain the "matched position" at which to update.
You can only return ONE matched array index via use of the positional $ operator and NOT TWO as would be inherent to updating such a document.
For those reasons ( and others ) it is strongly discouraged to have "multiple arrays" within a single document. The far better approach is to use a "singular" array, and use properties to denote what "type" of entry the list item actually contains:
{
"originator": "Bill",
"owner_id": "Ted",
"shared": [
{ "type": "phone", "value": "5555 5555" },
{ "type": "phone", "value": "4444 4444" },
{ "type": "email", "value": "bill@stalyns.org" },
{ "type": "email", "value": "bill@example.com" }
]
}
In this way you can actually address the "matched" element in which to update:
// phoneNumberMatch = "4444 4444";
// phoneNumber = "7777 7777";
// emailAddress = null; // don't want this one
// emailAddressMatch = null; // or this one
// profile = { owner_id: "Bill" };
var query = {
"originator": profile.owner_id,
"owner_id": { "$ne": profile.owner_id },
"shared": {
"$elemMatch": {
"type": (phoneNumber) ? "phone" : "email",
"value": (phoneNumber) ? phoneNumberMatch : emailAddressMatch
}
}
};
var updateBlock = {
"$set": {
"shared.$.value": (phoneNumber) ? phoneNumber : emailAddress
}
};
Contacts.updateMany(query, updateBlock, function(err, numAffected) {
// work with callback
})
In such a case and with a "binary" choice then you "can" use ternary conditions in construction since you are not reliant on "naming keys" within the construction.
If you want "either, or indeed both" supplied values in combination then you need a bit more advanced statement:
// phoneNumberMatch = "5555 5555";
// phoneNumber = "7777 7777";
// emailAddress = "bill@nomail.com";
// emailAddressMatch = "bill@example.com";
// profile = { owner_id: "Bill" };
var query = {
"originator": profile.owner_id,
"owner_id": { "$ne": profile.owner_id },
"$or": []
};
var updateBlock = { "$set": {} };
var arrayFilters = [];
if (phoneNumber) {
// Add $or condition for document match
query.$or.push(
{
"shared.type": "phone",
"shared.value": phoneNumberMatch
}
);
// Add update statement with named identifier
updateBlock.$set['shared.$[phone].value'] = phoneNumber;
// Add filter condition for named identifier
arrayFilters.push({
"phone.type": "phone",
"phone.value": phoneNumberMatch
})
}
if (emailAddress) {
// Add $or condition for document match
query.$or.push(
{
"shared.type": "email",
"shared.value": emailAddressMatch
}
);
// Add update statement with named identifier
updateBlock.$set['shared.$[email].value'] = emailAddress;
// Add filter condition for named identifier
arrayFilters.push({
"email.type": "email",
"email.value": emailAddressMatch
})
}
Contacts.updateMany(query, updateBlock, arrayFilters, function(err, numAffected) {
// work with callback
})
Noting of course here that the positional filtered $[<identifier>] syntax from MongoDB 3.6 and upwards is required in order to effect multiple array elements within a single update statement.
Much the same applies to the "original" structure I first described using "multiple" arrays in the documents instead of named properties on a "singular" array as the above examples deal with:
var query = {
"originator": "Bill",
"owner_id": { "$ne": "Bill" },
"$or": []
};
var updateBlock = { "$set": {} };
var arrayFilters = [];
if (phoneNumber) {
query.$or.push({
"shared.phones": phoneNumberMatch
});
updateBlock.$set['shared.phones.$[phone]'] = phoneNumber;
arrayFilters.push({
"phone": phoneNumberMatch
});
}
if (emailAddress) {
query.$or.push({
"shared.email": emailAddressMatch
});
updateBlock.$set['shared.email.$[email]'] = emailAddress;
arrayFilters.push({
"email": emailAddressMatch
});
}
Contacts.updateMany(query, updateBlock, arrayFilters, function(err, numAffected) {
// work with callback
})
Of course if you don't even have arrays at all ( the question posted lacks any example document ) then positional matches are not even needed in any form, but you do however still "conditionally" construct JavaScript object "keys" via construction code blocks. You cannot "conditionally" specify a "key" in JSON-like notation.