Update if value not undefined - mongoose - node.js

I want to update the value if key in the payload should have some value (not undefined or any false value). I don't want to check every value that makes code lengthy.
await Parent.findByIdAndUpdate(
payload.TID,
{
healthTests: payload.foo,
otherHealthtest: payload.bar,
},
{ new: true, fields: { updatedAt: 0, createdAt: 0, __v: 0 } },
);

You can use Object.entires() to iterate keys and values and build your updateObj:
let updateObj = {};
for(let [key, value] of Object.entries(payload)){
if(value !== undefined){
updateObj[key] = value;
}
}
await Parent.findByIdAndUpdate(
payload.TID, updateObj,
{ new: true, fields: { updatedAt: 0, createdAt: 0, __v: 0 } },
);
example:
let payload = { foo: 1, bar: undefined, baz: 3};
let updateObj = {};
for(let [key, value] of Object.entries(payload)){
if(value !== undefined){
updateObj[key] = value;
}
}
console.log(updateObj);

Related

Keeping distinct status in an array using trip lifecycle

I want to create a status timeline. Similar to what we have with Uber or Lyft and I have my model schema to update so that I can keep track.
New request, Driver accepts, arrives, starts trip and ends trip.
I am keeping track of all these by updating the database and putting the time of update.
The problem I have now is it is possible to enter same entry eg
arrive pickup twice with different timestamp.
I do not want this to happen.
Once you arrive pickup, you cannot arrive pickup again, the most you can do should be able to return to the previous stage new request.
Here is my Schema
const StatusDataSchema = new Schema({
status: {
type: SchemaTypes.String,
enum: [
'new',
'acknowledged',
'assigned',
'start',
'arrived',
'end',
'rejected',
'cancelled',
'completed'
],
unique: true,
default: 'new'
},
message: {
type: SchemaTypes.String,
default: 'You created a new request'
},
createdAt: {
type: SchemaTypes.String,
required: false,
default: Date.now.toString()
},
updatedAt: {
type: SchemaTypes.Date,
required: false,
default: null
}
},
{
toJSON: {
virtuals: true,
versionKey: false,
transform: function (_, ret, __) {
const { _id, __v, ...rest } = ret;
return rest;
}
}
});
const DeliverySchema = SchemaFactory({
pickup: { type: PickupSchema, required: true },
dropoff: { type: DestinationSchema, required: true },
status: {
type: SchemaTypes.String,
enum: [
'new',
'acknowledged',
'assigned',
'toPickup',
'arrived',
'start',
'end',
'rejected',
'cancelled',
'completed'
],
unique: true,
default: 'new'
},
statusData: [StatusDataSchema],
})
Then I create like this
public async createReq(user: string, body: TripDTO) {
if (user !== body.user.id)
throw new ActionNotAllowedError(
'You cannot create a trip on the behalf of another user'
);
const data = await this.create({
...body
});
return data;
}
and I update like this
public async updateReq(id: string, body: StatusDeliveryDTO) {
var message = '';
var cancelReason = null;
var rejectReason = null;
var name = '';
if (body.driver == null) {
// do something
name = 'System';
} else {
name = body.driver.firstName;
}
if (body.status == 'assigned') {
message = `${name} has been assigned to you`;
} else if (body.status == 'new') {
message = 'You created a new request';
} else if (body.status == 'toPickup') {
message = `${name} is heading to your pickup location`;
} else if (body.status == 'arrived') {
message = `${name} has arrived your pickup`;
} else if (body.status == 'pickup') {
message = `${name} has picked`;
} else if (body.status == 'start') {
message = `${name} started`;
} else if (body.status == 'arrived') {
message = `${name} has arrived`;
} else if (body.status == 'completed') {
message = `${name} has completed`;
} else if (body.status == 'rejected') {
message = `${name} has rejected your request`;
rejectReason = body.reason;
} else if (body.status == 'cancelled') {
message = `${name} has canceled your request`;
cancelReason = body.reason;
} else {
message = body.status;
}
const data = await this.updateWithOperators(id, {
$push: {
statusData: {
status: body.status,
message: message,
createdAt: Date.now().toString()
}
},
cancelReason: cancelReason,
rejectReason: rejectReason,
...body
});
await NotificationService.dispatchTimeline(data.user.id, message);
return data;
}
The create function uses mongoose model.create while update uses model.findOneAndUpdate
How can I avoid the duplicate and keep a streamlined timeline.
Further code can be provided on request.

typeOrm using condition in where clause

#Injectable()
export class WeightPriceService {
constructor(readonly dbContext: DbContext) {}
async findPriceByWeight(weight: number, tariffType?: PackageMaterialType): Promise<number> {
const { price } = await this.dbContext.tariffs.findOne({
where: {
type: tariffType ? tariffType : ,
isActive: true,
weight: { min: LessThan(weight), max: MoreThan(weight) },
},
relations: ['weight'],
});
return price;
}
}
Is "tariffType" parameter is true I would like to check "type:tariffType" else do not want to check "type"
You can use the spread operator for this.
const obj = {
...(true && {my: 'obj'})
};
const truly = 1 === 1;
const falsy = 1 !== 1;
const myObject = {
foo: 'bar',
...(truly && {my: 'data'}),
...(falsy && {other: 'data'}),
something: 'else'
};
console.log(myObject);
When the condition is True it will inject the object otherwise wouldn't add anything.
In your case it would be
async findPriceByWeight(weight: number, tariffType?: PackageMaterialType): Promise<number> {
const { price } = await this.dbContext.tariffs.findOne({
where: {
...(tariffType && { type: tariffType }),
isActive: true,
weight: { min: LessThan(weight), max: MoreThan(weight) },
},
relations: ['weight'],
});
return price;
}

Mongoose / Node : How to add an object to an array?

I have a problem when I try to update an array with Mongoose/Node.
I want to add my new price for example, req.body value is : { price: 12 } or req.body is : { description: 'my description' } but when I do this the total array is replace by just my new object ! :/
Here is my model:
const restaurantSchema = mongoose.Schema({
userId: { type: Object, required: true },
name: { type: String },
menus: [{
name: { type: String },
price: { type: String },
description: { type: String },
}],
})
And my node Js code :
const menuUpdate = req.body;
const menuId = req.params.menuId;
const userId = userIdFromToken(req);
const filter = {
userId: userId,
"menus._id": menuId
};
const update = { $set: { "menus.$": menuUpdate } };
const options = {
upsert: true,
new: true
};
Restaurant.findOneAndUpdate(filter, update, options).then(() => {
return res.status(204).json({ message: "Menus updated " });
});
Thanks for your help,
David
====
I change my code with the help of #aks, like this...
const menuUpdate = req.body;
for (const [key, value] of Object.entries(menuUpdate)) {
this.menuKey = `${key}`;
this.menuValue = `${value}`;
}
if (this.menuKey === 'name') {
this.update = { $set: { "menus.$.name": this.menuValue } };
}
if (this.menuKey === 'price') {
this.update = { $set: { "menus.$.price": this.menuValue } };
}
if (this.menuKey === 'description') {
this.update = { $set: { "menus.$.description": this.menuValue } };
}
const menuId = req.params.menuId;
const userId = userIdFromToken(req);
const filter = {
userId: userId,
'menus._id': menuId,
};
const options = {
upsert: true,
new: true
};
Restaurant
.findOneAndUpdate(
filter,
this.update,
options,
)
.then ( () => {
return res.status(204).json({ message: 'Menus updated ' });
});
Is there a way to simplify that without if ?
Your Node code
menus: [{
name: "toto",
price: 25,
description: "custom sweet",
}]
Now You have to update only the price from 25 to 45 for that you have to send the whole array.
So you have to simple set the array with this value
And if you go to other approach
then on req.body add one more parameter i.e menuIndex: 2
And on you update request make the condition if menuIndex is 2 then update specific column
const menuUpdate = req.body;
const menuId = req.params.menuId;
const userId = userIdFromToken(req);
const filter = {
userId: userId,
"menus._id": menuId
};
let update = {};
if (req.body.menuIndex === 1) {
update = { $set: { "menus.$.name": req,body.val} };
}
if (req.body.menuIndex === 2) {
update = { $set: { "menus.$.price": req,body.val
} };
}
if (req.body.menuIndex === 3) {
update = { $set: { "menus.$.description": req,body.val} };
}
const options = {
upsert: true,
new: true
};
Restaurant.findOneAndUpdate(filter, update, options).then(() => {
return res.status(204).json({ message: "Menus updated " });
});

Save array values based on ObjectId using mongoose

I try to save each document in an array as ObjectId, like that:
{
materials: {
active: "Steel",
description: "List of materials",
text: "Materials",
value: ["5c44ea8163bfea185e5e2dfb", "5c44ea8163bfea185e5e2dfc"]
}
}
I used an array of promises to save asynchronous each value and save the callback _id:
const reference = {
materials: {
...project.materials,
value: await Promise.all(project.materials.value.map(
async (value) => {
const { _id } = await Material.findOneAndUpdate({ name: value.name }, value, { upsert: true, new: true, setDefaultsOnInsert: true }).exec();
return mongoose.Types.ObjectId(_id);
}
))
},
...
}
There is another more simple way ?

How to get the ID of an inserted or updated record in Sequelize upsert?

i need to get the id for the inserted/updated record when using .upsert() in sequelize.
right now .upsert() returns a boolean indicating whether the row was created or updated.
return db.VenueAddress.upsert({
addressId:address.addressId,
venueId: venue.venueId,
street: address.street,
zipCode: address.zipCode,
venueAddressDeletedAt: null
}).then(function(test){
//test returned here as true or false how can i get the inserted id here so i can insert data in other tables using this new id?
});
I don't think that returning the upserted record was available when the OP asked this question, but it has since been implemented with this PR. As of Sequelize v4.32.1, you can pass a boolean returning as a query param to select between returning an array with the record and a boolean, or just a boolean indicating whether or not a new record was created.
You do still need to provide the id of the record you want to upsert or a new record will be created.
For example:
const [record, created] = await Model.upsert(
{ id: 1, name: 'foo' }, // Record to upsert
{ returning: true } // Return upserted record
);
I wanted upsert to return the created or updated object. It doesn't because only PGSQL supports it directly, apparently.
So I created a naive implementation that will - probably in a non-performant way, and possibly with all sorts of race conditions, do that:
Sequelize.Model.prototype.findCreateUpdate = function(findWhereMap, newValuesMap) {
return this.findOrCreate({
where: findWhereMap,
defaults: findWhereMap
})
.spread(function(newObj, created) {
// set:
for(var key in newValuesMap) {
newObj[key] = newValuesMap[key];
}
return newObj.save();
});
};
Usage when trying to create/update a move in a game (contrived example alert!):
models.Game
.findOne({where: {gameId: gameId}})
.then(function(game) {
return db.Move.findCreateUpdate(
{gameId: gameId, moveNum: game.moveNum+1},
{startPos: 'kr4', endPos: 'Kp2'}
);
});
This is what worked for me:
Model.upsert({
title:your title,
desc:your description,
location:your locations
}).then(function (test) {
if(test){
res.status(200);
res.send("Successfully stored");
}else{
res.status(200);
res.send("Successfully inserted");
}
})
It will check db to find based on your primary key. If it finds then, it will update the data otherwise it will create a new row/insert into a new row.
i know this is an old post, but in case this helps anyone
const upsert = async (model: any, values: any, condition: any): Promise<any> => {
const obj = await model.findOne({ where: condition })
if (obj) {
// only do update is value is different from queried object from db
for (var key in values) {
const val = values[key]
if (parseFloat(obj[key]) !== val) {
obj.isUpdatedRecord = true
return obj.update(values)
}
}
obj.isUpdatedRecord = false
return obj
} else {
// insert
const merged = { ...values, ...condition }
return model.create(merged)
}
}
It isn't using upsert, but .bulkCreate has an updateOnDuplicate parameter, which allows you to update certain fields (instead of creating a new row) in the event that the primary key already exists.
MyModel.bulkCreate(
newRows,
{
updateOnDuplicate: ["venueId", ...]
}
)
I believe this returns the resulting objects, and so I think this might enable the functionality you're looking for?
janmeier said:
This is only supported by postgres, so to keep the API consistent across dialects this is not possible.
please see : https://github.com/sequelize/sequelize/issues/3354
I believe my solution is the most up to date with most minimal coding.
const SequelizeModel = require('sequelize/lib/model')
SequelizeModel.upsert = function() {
return this.findOne({
where: arguments[0].where
}).then(obj => {
if(obj) {
obj.update(arguments[0].defaults)
return
}
return this.create(arguments[0].defaults)
})
}
I know this is an old post, but in case this helps anyone...you can get the returned id or any other value in this way based on OP data.
var data = {
addressId:address.addressId,
venueId: venue.venueId,
street: address.street,
zipCode: address.zipCode,
venueAddressDeletedAt: null
}
const result = await db.VenueAddress.upsert(data, { returning: true });
console.log('resulttttttttttttttttt =>', result)
res.status(200).json({ message: 'Your success message', data: result[0].id});
Noticed how I passed { returning: true } and get the value from the result data.
Super old, but if it helps someone:
const [city, created] = await City.upsert({
id: 5,
cityName: "Glasgow",
population: 99999,
});
created is the boolean saying whether the item was created, and in city you have the whole item, where you can get your id.
No need of returning, and this is db agnostic :)
The only solution for SQLite in Sequelize 6.14.0 is to query the inserted row again
I haven't found a solution that works besides a new SELECT query.
It does work in PostgreSQL however.
Presumably, this is because RETURNING was only implemented relatively recently in SQLite 3.35.0 from 2021: https://www.sqlite.org/lang_returning.html and Sequelize doesn't use that version yet.
I've tried both:
Model.upsert with returning: true: did not work on SQLite. BTW, as mentioned at: https://sequelize.org/api/v6/class/src/model.js~model#static-method-upsert returning already defaults to true now, so you don't need to pass it explicitly
Model.bulkCreate with updatOnDuplicate
In both of those cases, some dummy value is returned when the object is present, not the one that is actually modified.
Minimal runnable examples from https://cirosantilli.com/sequelize
update_on_duplicate.js
#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const { DataTypes, Sequelize } = require('sequelize')
let sequelize
if (process.argv[2] === 'p') {
sequelize = new Sequelize('tmp', undefined, undefined, {
dialect: 'postgres',
host: '/var/run/postgresql',
})
} else {
sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.sqlite',
})
}
function assertEqual(rows, rowsExpect) {
assert.strictEqual(rows.length, rowsExpect.length)
for (let i = 0; i < rows.length; i++) {
let row = rows[i]
let rowExpect = rowsExpect[i]
for (let key in rowExpect) {
assert.strictEqual(row[key], rowExpect[key])
}
}
}
;(async () => {
const Integer = sequelize.define('Integer',
{
value: {
type: DataTypes.INTEGER,
unique: true, // mandatory
},
name: {
type: DataTypes.STRING,
},
inverse: {
type: DataTypes.INTEGER,
},
},
{
timestamps: false,
}
);
await Integer.sync({ force: true })
await Integer.create({ value: 2, inverse: -2, name: 'two' });
await Integer.create({ value: 3, inverse: -3, name: 'three' });
await Integer.create({ value: 5, inverse: -5, name: 'five' });
let rows
// Initial state.
rows = await Integer.findAll({ order: [['id', 'ASC']]})
assertEqual(rows, [
{ id: 1, value: 2, name: 'two', inverse: -2 },
{ id: 2, value: 3, name: 'three', inverse: -3 },
{ id: 3, value: 5, name: 'five', inverse: -5 },
])
// Update.
rows = await Integer.bulkCreate(
[
{ value: 2, name: 'TWO' },
{ value: 3, name: 'THREE' },
{ value: 7, name: 'SEVEN' },
],
{ updateOnDuplicate: ["name"] }
)
// PostgreSQL runs the desired:
//
// INSERT INTO "Integers" ("id","value","name") VALUES (DEFAULT,2,'TWO'),(DEFAULT,3,'THREE'),(DEFAULT,7,'SEVEN') ON CONFLICT ("value") DO UPDATE SET "name"=EXCLUDED."name" RETURNING "id","value","name","inverse";
//
// but "sequelize": "6.14.0" "sqlite3": "5.0.2" does not use the desired RETURNING which was only added in 3.35.0 2021: https://www.sqlite.org/lang_returning.html
//
// INSERT INTO `Integers` (`id`,`value`,`name`) VALUES (NULL,2,'TWO'),(NULL,3,'THREE'),(NULL,7,'SEVEN') ON CONFLICT (`value`) DO UPDATE SET `name`=EXCLUDED.`name`;
//
// so not sure how it returns any IDs at all, is it just incrementing them manually? In any case, those IDs are
// all wrong as they don't match the final database state, Likely RETURNING will be added at some point.
//
// * https://stackoverflow.com/questions/29063232/sequelize-upsert
// * https://github.com/sequelize/sequelize/issues/7478
// * https://github.com/sequelize/sequelize/issues/12426
// * https://github.com/sequelize/sequelize/issues/3354
if (sequelize.options.dialect === 'postgres') {
assertEqual(rows, [
{ id: 1, value: 2, name: 'TWO', inverse: -2 },
{ id: 2, value: 3, name: 'THREE', inverse: -3 },
// The 6 here seems to be because the new TWO and THREE initially take up dummy rows,
// but are finally restored to final values.
{ id: 6, value: 7, name: 'SEVEN', inverse: null },
])
} else {
assertEqual(rows, [
// These IDs are just completely wrong as mentioned at: https://github.com/sequelize/sequelize/issues/12426
// Will be fixed when one day they use RETURNING.
{ id: 4, value: 2, name: 'TWO', inverse: undefined },
{ id: 5, value: 3, name: 'THREE', inverse: undefined },
{ id: 6, value: 7, name: 'SEVEN', inverse: undefined },
])
}
// Final state.
rows = await Integer.findAll({ order: [['id', 'ASC']]})
assertEqual(rows, [
{ id: 1, value: 2, name: 'TWO', inverse: -2 },
{ id: 2, value: 3, name: 'THREE', inverse: -3 },
{ id: 3, value: 5, name: 'five', inverse: -5 },
{ id: 6, value: 7, name: 'SEVEN', inverse: null },
])
})().finally(() => { return sequelize.close() });
upsert.js
#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const { DataTypes, Sequelize } = require('sequelize')
let sequelize
if (process.argv[2] === 'p') {
sequelize = new Sequelize('tmp', undefined, undefined, {
dialect: 'postgres',
host: '/var/run/postgresql',
})
} else {
sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.sqlite',
})
}
function assertEqual(rows, rowsExpect) {
assert.strictEqual(rows.length, rowsExpect.length)
for (let i = 0; i < rows.length; i++) {
let row = rows[i]
let rowExpect = rowsExpect[i]
for (let key in rowExpect) {
assert.strictEqual(row[key], rowExpect[key])
}
}
}
;(async () => {
const Integer = sequelize.define('Integer',
{
value: {
type: DataTypes.INTEGER,
unique: true,
},
name: {
type: DataTypes.STRING,
},
inverse: {
type: DataTypes.INTEGER,
},
},
{
timestamps: false,
}
);
await Integer.sync({ force: true })
await Integer.create({ value: 2, inverse: -2, name: 'two' });
await Integer.create({ value: 3, inverse: -3, name: 'three' });
await Integer.create({ value: 5, inverse: -5, name: 'five' });
let rows
// Initial state.
rows = await Integer.findAll({ order: [['id', 'ASC']]})
assertEqual(rows, [
{ id: 1, value: 2, name: 'two', inverse: -2 },
{ id: 2, value: 3, name: 'three', inverse: -3 },
{ id: 3, value: 5, name: 'five', inverse: -5 },
])
// Update.
rows = [(await Integer.upsert({ value: 2, name: 'TWO' }))[0]]
if (sequelize.options.dialect === 'postgres') {
assertEqual(rows, [
{ id: 1, value: 2, name: 'TWO', inverse: -2 },
])
} else {
// Unexpected ID returned due to the lack of RETURNING, we wanted it to be 1.
assertEqual(rows, [
{ id: 3, value: 2, name: 'TWO', inverse: undefined },
])
}
rows = [(await Integer.upsert({ value: 3, name: 'THREE' }))[0]]
if (sequelize.options.dialect === 'postgres') {
assertEqual(rows, [
{ id: 2, value: 3, name: 'THREE', inverse: -3 },
])
} else {
assertEqual(rows, [
{ id: 3, value: 3, name: 'THREE', inverse: undefined },
])
}
rows = [(await Integer.upsert({ value: 7, name: 'SEVEN' }))[0]]
if (sequelize.options.dialect === 'postgres') {
assertEqual(rows, [
{ id: 6, value: 7, name: 'SEVEN', inverse: null },
])
} else {
assertEqual(rows, [
{ id: 6, value: 7, name: 'SEVEN', inverse: undefined },
])
}
// Final state.
rows = await Integer.findAll({ order: [['value', 'ASC']]})
assertEqual(rows, [
{ id: 1, value: 2, name: 'TWO', inverse: -2 },
{ id: 2, value: 3, name: 'THREE', inverse: -3 },
{ id: 3, value: 5, name: 'five', inverse: -5 },
{ id: 6, value: 7, name: 'SEVEN', inverse: null },
])
})().finally(() => { return sequelize.close() });
package.json
{
"name": "tmp",
"private": true,
"version": "1.0.0",
"dependencies": {
"pg": "8.5.1",
"pg-hstore": "2.3.3",
"sequelize": "6.14.0",
"sql-formatter": "4.0.2",
"sqlite3": "5.0.2"
}
}
In both of those examples, we see that PostgreSQL runs the desired:
INSERT INTO "Integers" ("id","value","name") VALUES (DEFAULT,2,'TWO'),(DEFAULT,3,'THREE'),(DEFAULT,7,'SEVEN') ON CONFLICT ("value") DO UPDATE SET "name"=EXCLUDED."name" RETURNING "id","value","name","inverse";
which works due to RETURNING, but sequelize does not use the desired RETURNING
INSERT INTO `Integers` (`id`,`value`,`name`) VALUES (NULL,2,'TWO'),(NULL,3,'THREE'),(NULL,7,'SEVEN') ON CONFLICT (`value`) DO UPDATE SET `name`=EXCLUDED.`name`;
Tested on Ubuntu 21.10, PostgreSQL 13.5.
Which I myself resolved as follows:
return db.VenueAddress.upsert({
addressId:address.addressId,
venueId: venue.venueId,
street: address.street,
zipCode: address.zipCode,
venueAddressDeletedAt: null
},{individualHooks: true}).then(function(test){
// note individualHooks
});

Resources