Mongodb: updating a variable field name - node.js

I want to add a new field with a variable name to an object in the DB : meaning, I don't know the name of the field, but it's held in a variable "newFieldName".
So what I want to do is basically this:
var newFieldName = "world";
db.bios.update(
{ _id: 3 },
{ $set: {
"hello."+newFieldName: "Amazing Grace"
}
}
)
After the update, I expect the object "hello" to have a field "world" with the value "Amazing Grace".
but this doesn't even compile, let alone work. How can I do it?

You can use an intermediary object:
var update = { $set : {} };
update.$set['hello.' + newFieldName] = 'Amazing Grace';
db.bios.update({ _id : 3 }, update, ...)

var some_object = Posts.findOne({...});
var new_value = 1337;
Posts.update(another_object._id,
{$set: {
[some_object.some_field]:new_value,
}
}
);

To answer #yossale & #robertklep, the inline version is in fact possible using an expression and the comma operator:
var newFieldName = "world", o;
db.bios.update(
{ _id: 3 },
{$set:(o = {}, o["hello."+newFieldName] = "Amazing Grace", o)}
)

Simple is that:
var newFieldName = "world";
db.bios.update(
{ _id: 3 },
{ $set: {
["hello."+newFieldName]: "Amazing Grace"
}
}
);
#Andy's answer solved my problem.

Related

Is there an easier way to convert javascript object to dynamodb update expression?

I am using nodejs aws-sdk/clients/dynamodb library with dynamodb. I need to update an item in the table. Below is the sample code to update an item:
params = {
TableName:table,
Key:{
"year": year,
"title": title
},
UpdateExpression: "set info.rating = info.rating + :val",
ExpressionAttributeValues:{
":val": 1
},
ReturnValues:"UPDATED_NEW"
};
I will have to specify each attribute in info in UpdateExpression. My info object is very big and I am looking for an easier way to do that. Is there a build-in method to support update an object to dynamodb item? something like:
params = {
TableName:table,
Key:{
"year": year,
"title": title
},
Item: info
};
The answer given by E.J. Brennan is great for cases where it's ok to replace the entire item. DocumentClient eases the hassle of dealing with DynamoDB attribute types, but the example given uses the put method. According to the docs put passes through to putItem which
Creates a new item, or replaces an old item with a new item
That means that it's not going to help with partial updates to existing items where you don't already have the full record (and can get away with a full replacement). For partial updates you have to use updateItem, or it's DocumentClient counterpart, update.
The AWS labs has published a utility to help with constructing update expressions to use with updateItem. Since I generally prefer to use DocumentClient, I unmarshall values with the utility function provided by DynamoDB's Converter (yes, I know it's a bit a back and forth, but it makes testing easier).
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient();
const { UpdateExpression, ExpressionAttributes } = require('#aws/dynamodb-expressions');
const { unmarshall } = AWS.DynamoDB.Converter;
const updateExpressionProps = ({ category, classification }) => {
attributes = new ExpressionAttributes();
expression = new UpdateExpression();
expression.set('category', category);
expression.set('classification', classification);
return {
UpdateExpression: expression.serialize(attributes),
ExpressionAttributeNames: attributes.names,
ExpressionAttributeValues: unmarshall(attributes.values),
};
};
const updateRequest = async ({ id, subject, category, classification }) =>
await db
.update({
TableName: 'table-name',
Key: {
id,
subject,
},
...updateExpressionProps({ category, classification }),
})
.promise();
This bit of code only updates the category and classification attributes on the record identified with id and subject without the hassle of manually building a correct UpdateExpression string. This example could easily be generalized into something reusable throughout your project.
I wrote the following utility method to convert a given object to UpdateExpression (SET only), ExpressionAttributeNames, and ExpressionAttributeValues.
const convertToCompositePathObj = (obj: Record<any, any>) => {
const res: Record<string, string | number | []> = {};
const getPropertyPath = (obj: Record<any, any>, current = "") => {
for (let key in obj) {
const value = obj[key];
const newKey = current ? [current, key].join(".") : key;
if (value && typeof value === "object" && !Array.isArray(value)) {
getPropertyPath(value, newKey);
} else res[newKey] = value;
}
};
getPropertyPath(sampleObject);
return res;
};
const generateDynamoDbUpdateExpression = (obj: Record<any, any>) => {
const compositePathObj = convertToCompositePathObj(sampleObject);
let counter = 0;
let updateExpression = "SET ";
const expressionAttNamesMap: any = {};
const expressionAttValuesMap: any = {};
for (let k in compositePathObj) {
const newUpdateExpression = k
.split(".")
.map((item) => {
const attName = `#${item}`;
if (!expressionAttNamesMap[attName]) {
expressionAttNamesMap[attName] = item;
}
return attName;
})
.join(".")
.concat(`= :${counter} AND `);
expressionAttValuesMap[`:${counter}`] = compositePathObj[k];
counter += 1;
updateExpression += newUpdateExpression;
}
updateExpression = updateExpression.substring(0, updateExpression.length - 5);
return {
UpdateExpression: updateExpression,
ExpressionAttributeNames: expressionAttNamesMap,
ExpressionAttributeValues: expressionAttValuesMap
};
};
// example usage:
const sampleObject = {
name: {
first: "John",
last: "Doe"
},
address: {
line1: "123 test st.",
line2: "Apt 123",
city: "Los Angeless",
state: "CA",
zip: 92763
},
phone: 8675768475
};
console.log(generateDynamoDbUpdateExpression(sampleObject));
/**
*****OUTPUT*****
{
UpdateExpression: "SET #name.#first= :0 AND #name.#last= :1 AND #address.#line1= :2 AND #address.#line2= :3 AND #address.#city= :4 AND #address.#state= :5 AND #address.#zip= :6 AND #phone= :7",
ExpressionAttributeNames:{
#name: "name"
#first: "first"
#last: "last"
#address: "address"
#line1: "line1"
#line2: "line2"
#city: "city"
#state: "state"
#zip: "zip"
#phone: "phone"
},
ExpressionAttributeValues:{
:0: "John"
:1: "Doe"
:2: "123 test st."
:3: "Apt 123"
:4: "Los Angeless"
:5: "CA"
:6: 92763
:7: 8675768475
}
}
**/
PS: I wrote this in a hurry so please excuse the formatting and any types.
https://codesandbox.io/s/aws-dynamic-update-expression-set-33tye7?file=/src/index.ts
You could use the Document Client:
Version 2.2.0 of the AWS SDK for JavaScript introduces support for the
document client abstraction in the AWS.DynamoDB namespace. The
document client abstraction makes it easier to read and write data to
Amazon DynamoDB with the AWS SDK for JavaScript. Now you can use
native JavaScript objects without annotating them as AttributeValue
types.
For example:
var docClient = new AWS.DynamoDB.DocumentClient({region: 'us-west-2'});
var params = {
Item: {
hashkey: 'key',
boolAttr: true,
listAttr: [1, 'baz', true]
mapAttr: {
foo: 'bar'
}
},
TableName: 'table'
};
docClient.put(params, function(err, data){
if (err) console.log(err);
else console.log(data);
});
https://aws.amazon.com/blogs/developer/announcing-the-amazon-dynamodb-document-client-in-the-aws-sdk-for-javascript/

how do i generate an object via a for loop?

I am trying to generate an object via a for loop, the problem I am having is that the property name is not being generated instead it is just inserted as the variable name.
Here is an example:
for (let key in person) {
let obj = {key : person[key] };
console.log(obj);
}
If you run this it prints
{ key : "smith" }
The desired object would be
{ name : "smith" }
any ideas on how to achieve this? thank you in advanced.
What you want is :
const person = {
age: 18,
size: '1m74',
eyeColor: 'blue',
};
for (let key in person) {
const obj = {
[key] : person[key],
};
console.log(obj);
}
Look at here for explainations
Example with Array.forEach and Object.keys
const person = {
age: 18,
size: '1m74',
eyeColor: 'blue',
};
Object.keys(person).forEach((x) => {
const obj = {
[x]: person[x],
};
console.log(obj);
});
You can achieve using
for (let key in person) {
const obj = {};
obj[key] = person[key];
console.log(obj);
}
You can do this by :
obj = {name: person[key] }

Inside an array, how to check forript?

So I will be constantly retrieving an object with the following format:
student: {
"student_id": "12345",
"location": "below",
},
]
},
]
Thank you and will accept answer and upvote!
Something like this should do the trick:
var students = [];
function addStudent(student) {
// Check if we already know about this student.
var existingRecord = students.find(function (s) {
return s.student_id === student.student_id;
});
var classInfo = {
class_number: student.class_number,
location: student.location
};
if (!existingRecord) {
// This is the first record for this student so we construct
// the complete record and add it.
students.push({
student_id: student.student_id,
classes: [classInfo]
});
return;
}
// Add to the existing student's classes.
existingRecord.classes.push(classInfo);
}
You would then invoke it as follows:
addStudent({
"student_id": "67890",
"class_number": "abcd",
"location": "below",
});
Runnable JSBin example available here.
More available on Array.prototype.find at MDN.
This problem can be solved using indexing by student_id. For example:
var sourceArray = [{...}, {...}, ...];
var result = {};
sourceArray.forEach(function(student){
var classInfo = {
class_number: student.class_number,
location : student.location
};
if(result[student.student_id]){
result[student.student_id].classes.push(classInfo);
} else {
result[student.student_id] = {
student_id : student.student_id,
classes : [classInfo]
}
}
});
// Strip keys: convert to plain array
var resultArray = [];
for (key in result) {
resultArray.push(result[key]);
}
You can use also result format that contains objects, indexed by student_id or plain array resultArray.

How to apply function for all Collection

I want to update my collection in server.js by using a function.
When I change one field I need to change multiple collections.
My question is how can I use a parameter as a Collection name. Is there any way for it or I must write a function for each Collection?
update: function(personID,option) {
return Personel.update(
{ id: personID },
{ $set: option },
{ multi: true }
);
},
I want to apply this logic for separate collections.
There is a trickier workaround for this problem. you need to actually bind all of your collection in a single object.
CollectionList = {};
CollectionList.Personel = new Mongo.Collection('personel');
CollectionList.secondCollection = new Mongo.Collection('second');
after that you pass as your collection name as a string into the method.
update: function(collectionName,personID,option){
return CollectionList[collectionName].update(
//..rest of your code
);
You can try this approach:
var Personel = new Mongo.Collection('personel');
var Items = new Mongo.Collection('items');
var SomeOtherCollection = new Mongo.Collection('someOtherCollection');
....
update: function(personID, option, collectionName) {
// Choose collection by given name
var Collection = {
Personel: Personel,
Items: Items,
SomeOtherCollection: SomeOtherCollection
}[collectionName];
return Collection.update(
{ id: personID },
{ $set: option },
{ multi: true }
);
},

MongoDB / Mongoose $pull (remove) Sub Document not working

Smashing my head into the keyboard over this.
Simply need to remove subdocument. Example below only has one item in OnCommands but there could be a many items there. I have tried find, findbyid, updatebyId, pull, one things after another. Tried by _id of subdoc and by generic searchinMost simple run without doing anything no errors.
I would be so greatful if you can show me what I am doing wrong, it's the last part of my code that isn't work.
Sample Data:
> db.EntryPoints.find({_id: ObjectId("569e4fabf1e4464495ebf652")}).pretty()
{
"__v" : 0,
"_id" : ObjectId("569e4fabf1e4464495ebf652"),
"name" : "bbbb",
"offCommands" : [ ],
"onCommands" : [
{
"data" : "11111",
"operation" : "on",
"command" : "ISY-HTTPGet",
"_id" : ObjectId("569e4faff1e4464495ebf653")
}
]
Model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema,
ObjectId = Schema.ObjectId;
var onCommandsSchema = new Schema({
command: String
,operation: String
,data: String
})
var offCommandsSchema = new Schema({
command: String
,operation: String
,data: String
})
mongoose.model('onCommands', onCommandsSchema);
mongoose.model('offCommands', offCommandsSchema);
// create a schema
var EntryPointsSchema = new Schema({
name: String
,onCommands: [onCommandsSchema]
,offCommands: [offCommandsSchema]
,description: String
}, { collection: 'EntryPoints' });
mongoose.model('EntryPoints', EntryPointsSchema);
var EntryPoints = mongoose.model('EntryPoints');
module.exports = EntryPoints;
Node Post Code:
router.post('/webservices/removeCommand', function (req, res) {
var EntryPoints = require('../data_models/automate_entrypoints.js');
EntryPoints.update(
{ _id: ObjectId(req.body._id) }
, {
$pull: {
onCommands: { id_: req.body._id }
}
}
, function (err, ouput) { console.log("data:", numAffected) }
);
});
Your code won't work because of the query part of your update: you want to match on the embedded document's _id, not on the main document. So change it to
var EntryPoints = require('../data_models/automate_entrypoints.js');
EntryPoints.update(
{ "onCommands._id": req.body._id },
{
"$pull": {
"onCommands": { "_id": req.body._id }
}
},
function (err, numAffected) { console.log("data:", numAffected) }
);

Resources