Mongoose and Bluebird saveAsync not actually saving - node.js

I am attempting to update an existing record in Mongo DB and although the "save" completes successfully, The record in the DB is not actually updated. Here's a before an after of the data:
Before:
{
_id: '56be4ba9938e836b6f47e84a',
type: 'ec-input',
key: 'ec-input_2',
templateOptions: {
placeholder: 'My Placeholder...',
subtext: 'My Subtest',
label: 'My Label'
},
order: 1,
form: '56bd3e76413de7c862979b8c',
__v: 0
}
After:
{
_id: '56be4ba9938e836b6f47e84a',
type: 'ec-input',
key: 'ec-input_2',
templateOptions: {
placeholder: 'I am different now!!',
subtext: 'Me too..',
label: 'My Label'
},
order: 1,
form: '56bd3e76413de7c862979b8c',
__v: 0
}
The templateOptions field is configured as type mongoose.Schema.Types.Mixed on the mongoose model. Here's how i'm trying to save the update:
export function update(req, res) {
if (req.body._id) {
delete req.body._id;
}
Field.findByIdAsync(req.params.id)
.then(entity => {
var updated = _.merge(entity, req.body);
updated.saveAsync()
.spread(updated => {
if (entity) {
res.status(200).json(entity);
}
});
});
}
I set a breakpoint before updated.saveAsync is called and updated correctly has the modified data. I also set a breakpoint in the spread callback, and updated still has the modified data. After updated.saveAsync is complete, I check mongo directly, and the record was not updated.
Any ideas about what I can do to make this work would be greatly appreciated. Thanks!

Related

Mongodb How to update documents already found

I'm building a movie rating system.
After entering the user ID, content ID, and rating registered in the rating document,
It was implemented by updating the rating in the content document.
How can I update the content document while I have already found it like the code below?
router.post('/', authenticate, async (req: IRequest<IRating>, res) => {
try {
const document = await Rating.create({
contentId: req.body.contentId,
contentType: req.body.contentType,
rating: req.body.rating,
review: req.body.review,
userId: req.user?._id,
});
const content = await Content.findOne({
type: req.body.contentType,
_id: document._id,
});
if (content) {
await content.updateOne(
{},
{
average: (content.average + document.rating) / content.count + 1,
count: content.count + 1,
},
);
}
res.json({ success: true, document, content });
} catch (err) {
res.status(404).json({ success: false, message: 'sever error' });
}
});
You can update with pipeline instead of use 2 queries, which for your current code can look like:
await Content.findOneAndUpdate(
{
type: req.body.contentType,
_id: document._id,
},
[{$set: {
average: {$divide: [{$add: ["$content.average", document.rating]},
{$add: ["$content.count", 1]}]},
count: {$add: ["$content.count", 1]}
}}])
But I don't think this is the right way to calculate an average. You should consider multiplying the existing $content.average by $content.count before adding the new document.rating

Update document in MongoDB via NodeJS

So my knowledge of NodeJS and MongoDD are non-existent (just need to do a small code update for a friend) and I'm stuck.
Need to update a single document inside a collection via a unique id but can't seem to do it.
Here's the Model (I've trimmed it down and cut out all unnecessary data). I'm trying to update the field notes inside a transaction.
In short each entry in the given (an Agent) table will have a collection of multiple Transactions & Documents. I need to update a specific Transaction with the unique _id that is auto generated.
import { Schema, model } from 'mongoose';
interface Transaction {
first_name: string;
last_name: string;
type: string;
notes: string;
}
interface Agent {
org_id: number;
transactions: Array<Transaction>;
documents: Array<string>;
}
const transactionSchema = new Schema<Transaction>({
first_name: { type: String },
last_name: { type: String },
type: { type: String },
notes: String,
});
const transactionsSchema = new Schema<Agent>({
org_id: { type: Number },
transactions: [transactionSchema],
documents: [documentTypesSchema],
});
const AgentTransaction = model<Agent>(
'agent_transaction_table',
transactionsSchema
);
export default AgentTransaction;
Here's what I tried but didn't work (obviously), again I've trimmed out all unnecessary data. Just to clarify, the endpoint itself works, but the DB update does not.
import AgentTransaction from '../models/transaction'; // the above model
transaction.put('/notes', async (req, res) => {
const { org_id, transaction_id, notes } = req.body;
try {
const notesResult = await AgentTransaction.updateOne({
'transactions._id': transaction_id,
}, {
$set: {
'notes': notes
},
});
res
.status(200)
.json({ message: 'Updated', success: true, notesResult });
} catch (error) {
res.status(400).send(error);
}
});
So I figured it out. Maybe it'll help someone else as well.
const notesResult = await AgentTransaction.updateOne({
'transactions._id': { $in: [trunc2] },
}, {
$set: {
'transactions.$.notes': notes
},
});
The main issue was that the payload object needed to target the collection folder + the wildcard + the field, not just only the field.

Mongoose: updateMany() is not working as expected

I'm using mongoose to handle my DB queries. I'm trying to update a set of records entirely using this method. Mode code looks like this:
// prepare database query
const filter = { type: 'company' };
const update = req.body.payload; // payload contains the array of objects (i.e. updated records)
const options = { new: true, runValidators: true }
// find and update the taxonomy record
await Taxonomy.updateMany(filter, update, options);
But whenever I run this query I'm getting following error in the console:
Error [MongooseError]: Invalid update pipeline operator: "_id"
I suppose there is something wrong in my update payload. The req.body.payload looks like this:
[
{
_id: '5ef3d08c745428001d92f896',
type: 'company',
name: 'Company Size',
__v: 0
},
{
_id: '5ef3cdc5745428001d92f893',
type: 'company',
name: 'Company Industry',
__v: 0
}
]
Can you please tell me what actually is wrong here?
This is not the right usage of updateMany() - it is aimed to update many documents with a single change.
To update many documents use bulkwrite() (docs) :
async function myUpdateMany(Model, objectsArray) {
try {
let ops = []
for (let obj of (objectsArray || [])) {
ops.push({
updateOne: {
filter: { platformId: obj.platformId },
update: obj,
upsert: false, // set "true" if you want to add a new document if it doesn't exist
}
})
}
Model.bulkWrite(ops, { ordered: false });
} catch (err) {
throw Error("myUpdateMany error: " + err)
}
}
Regarding runValidators, according to this, it seems to work by default.

How to write the parameter to update a particular property's value in an object from an array?

How to update quantity value based on title in the movies array and Item id (123)
I only manage to update value at the first layer like name (David), but don't know how to update the second layer with additional filter for the array (movies).
From:
Item:
{
id: 123,
name: 'David',
movies: [
{
id: 1,
title: 'The lord of the ring',
quantity: 1
},
{
id: 2,
title: 'Star Wars',
quantity: 1
}
]
}
To:
Item:
{
id: 123,
Name: 'David',
movies: [
{
id: 1,
title: 'The lord of the ring',
quantity: 2
},
{
id: 2,
title: 'Star Wars',
quantity: 1
}
]
}
By the way, I'm using aws DynamoDB document client in node.js, it will be nice if you can share me how you do it in your update parameter.
There is no way to update an object inside of a list without replacing it.
You probably want to restructure your table to emulate a relational data model. AWS has some documentation on this.
As an example, create your table like this:
aws dynamodb create-table \
--table-name movie-table \
--attribute-definitions AttributeName=rId,AttributeType=N AttributeName=rKey,AttributeType=S \
--key-schema AttributeName=rId,KeyType=HASH AttributeName=rKey,KeyType=RANGE
The table will have generically named hash and range keys. This script demonstrates how to structure the data and add to the "count":
const { DynamoDB } = require('aws-sdk');
const client = new DynamoDB.DocumentClient({ region: 'us-east-1' });
const addItem = (rId, rKey, attributes) => {
const item = { rId, rKey };
Object.assign(item, attributes);
return client.put({ TableName: 'movie-table', Item: item }).promise();
};
// NOTE: this is where the count attribute gets iterated
const addToCount = (rId, rKey) => client.update({
TableName: 'movie-table',
Key: { rId, rKey },
UpdateExpression: 'ADD #count :n',
ExpressionAttributeNames: { '#count': 'count' },
ExpressionAttributeValues: { ':n': 1 },
}).promise();
const run = async () => {
await addItem(123, 'USER|123', { name: 'David' });
await addItem(1, 'MOVIE|1', { title: 'The lord of the ring' });
await addItem(2, 'MOVIE|2', { title: 'Star Wars' });
await addItem(123, 'COUNT|1', { count: 1 });
await addItem(123, 'COUNT|2', { count: 1 });
await addToCount(123, 'COUNT|1');
};
run();
This is what the table looks like after the script runs:
I know this is a bit old but there is a way. Using the document client SDK, you can reference object properties and array elements in the UpdateExpression. However, you can't run any logic so you have to know/assume/expect that the element indexes are enough.
For example, you can do something like this:
let params = {
TableName: 'your-table-name',
Key: { id: 123 },
UpdateExpression: 'set movies[0].quantity = :x',
ExpressionAttributeValues: { ':x': 5 }
};
const client = AWS.DynamoDB.DocumentClient();
client.update(params);
NOTE: You cannot make the index an Expression Attribute Value. You would have to dynamically build that update expression based on the index you know has to be updated. It's not a perfect solution but it could get the job done.
For reference, I derived this from the base (non-DocumentClient) example from here: Adding Nested Map Attributes

Missing property at client side Nodejs

My simple problem is:
I had an mongoose object at server side:
...
item = {
name: "Test",
id: 1
}
// item was an mongo schema
// id and name was define in model String and Number
Then I add into item new field mentions:
item.mention = [{ id: 1, ... }]
But I can't get mention at client side.
My response code:
res,json({ status: 1, message: 'success', data: item })
The response was data: { name: "Test", id: 1 }
I don't want to add mention into my mongo schema.
So, what's my problem?
How can I fix that?
Thanks!
The problem is that item is a MongooseDocument, not a plain javascript object.
There are multiple ways to achieve what you want.
1) Using .lean()
Documents returned from queries with the lean option enabled are plain
javascript objects, not MongooseDocuments
Model.findOne().lean().exec((err, item) => {
item.mention = [{ id: 1 }];
res.json(item);
});
This option will increase the performance since you ignore the overhead of creating the Mongoose Document
2) Using: .set()
item.set('mention', [{ id: 1}], { strict: false });
4) Using spread operator
res.json({
status: 1,
message: 'success',
data: { mention: [{ id: 5 }], ...item }
})
4) Using Object.assign as #Alexis Facques explained.
Taking advantage of Object.assign() should resolve your problem too.
res.json({
status: 1,
message: 'success',
data: Object.assign({ mention: [{id: 1...}] },item)
})

Resources