Sequelize: isNewRecord is always false - node.js

I have a Sequelize model called Test with a unique field value (primary key). I'm using SQLite as the database management system.
If I use the bulkCreate() option ignoreDuplicates: true, then bulkCreate will ignore the new data if they already exist in the database. That works great, but the objects returned by bulkCreate() always have isNewRecord: false, even when a new record was inserted into the database.
Code
const items = [ {value: 'a'}, {value: 'b'} ]; // Items to be saved in database.
const results = await Test.bulkCreate(items, {
ignoreDuplicates: true // Ignore duplicate records
});
After the first execution of bulkCreate() and the database empty, the value of results:
[
Test {
dataValues: {
value: 'a',
createdAt: 2020-07-12T12:01:08.695Z,
updatedAt: 2020-07-12T12:01:08.695Z
},
_previousDataValues: {
value: 'a',
createdAt: 2020-07-12T12:01:08.695Z,
updatedAt: 2020-07-12T12:01:08.695Z
},
_changed: Set {},
_options: {
isNewRecord: true,
_schema: null,
_schemaDelimiter: '',
include: undefined
},
isNewRecord: false
},
Test {
dataValues: {
value: 'b',
createdAt: 2020-07-12T12:01:08.695Z,
updatedAt: 2020-07-12T12:01:08.695Z
},
_previousDataValues: {
value: 'b',
createdAt: 2020-07-12T12:01:08.695Z,
updatedAt: 2020-07-12T12:01:08.695Z
},
_changed: Set {},
_options: {
isNewRecord: true,
_schema: null,
_schemaDelimiter: '',
include: undefined
},
isNewRecord: false
}
]
I expected isNewRecord: true after the first execution. What am I missing?
Environment
Windows 10 Pro
NodeJS v12.16.2
sequelize: 6.3.3
sqlite3: 5.0.0
Sequelize docs
isNewRecord of Model:
ignoreDuplicates of bulkCreate():

You need to access _options.isNewRecord.
As this issue states that the isNewRecord only be set to true if the instance has not yet been persisted to the database.
In your case, because you have saved the instance (the first time or second time, it doesn't matter, you already save it) so isNewRecord is set to false.
So as I mention above, if you want to check if your instance is just saved the first time or not, access _options.isNewRecord.

The isNewRecord field is true if the Model instance does not exist in the database.
const user = new User({ ...userData });
console.log(user.isNewRecord); // true
await user.save();
console.log(user.isNewRecord); // false

Related

Nodejs spread operator return some unexpected keys in returned object [duplicate]

This question already has answers here:
How do you turn a Mongoose document into a plain object?
(9 answers)
Closed 2 years ago.
There I have res is original object
{
time: 2020-07-26T10:39:38.135Z,
isTransfered: true,
_id: 5f1d5d6b60755e75b48770a6,
receiverAccountNumber: '12345678',
transfererAccountNumber: '11111111',
receiverName: 'Lê Công Tuyền',
transfererName: 'Nguyễn Thanh Tuấn',
amount: 1000000,
content: "test chuyefo'seajfg",
payFeeBy: 'transferer',
type: { name: 'internal', bankCode: 'TUB' },
__v: 0
}
And I got this result (called res2) is returned object using spread operator res2 = {...res} :
{
'$__': InternalCache {
strictMode: true,
selected: {},
// alot of key-value
'$setCalled': Set(0) {},
ownerDocument: undefined,
fullPath: undefined,
emitter: EventEmitter {
_events: [Object: null prototype],
_eventsCount: 2,
_maxListeners: 0,
[Symbol(kCapture)]: false
},
'$options': { skipId: true, isNew: false, willInit: true }
},
isNew: false,
errors: undefined,
_doc: {
time: 2020-07-26T10:39:38.135Z,
isTransfered: true,
_id: 5f1d5d6b60755e75b48770a6,
receiverAccountNumber: '12345678',
transfererAccountNumber: '11111111',
receiverName: 'Lê Công Tuyền',
transfererName: 'Nguyễn Thanh Tuấn',
amount: 1000000,
content: "test chuyefo'seajfg",
payFeeBy: 'transferer',
type: { name: 'internal', bankCode: 'TUB' },
__v: 0
},
'$locals': {},
'$op': null,
'$init': true
}
I really dont know about this behavior of spread operator, that a lot of new key-value generated and the object I want to get is in _doc key.
Code is run on Nodejs v12 (nvm use 12)
This is probably because the res here is a mongoose document which has these values.
When you are doing a mongoose query by default it returns a document object. In order to receive a plain object use lean(). If you use lean then you won't be getting these unnecessary data when using spread operator.
Schema.findOne(query).lean()
However, if you need a document object from mongoose then in this case you can try the following to get rid of other values you don't need.
let res2 = { ...res.toObject() };

Aggregate function returns null GraphQL

I am testing a basic aggregation function using counts from Sequelize and here's my type Counts:
type Creserve {
id: ID!
rDateStart: Date!
rDateEnd: Date!
grade: Int!
section: String!
currentStatus: String!
user: User!
cartlab: Cartlab!
}
type Counts {
section: String!
count: Int
}
type Query {
getBooking(id: ID!): Creserve!
allBookings: [Creserve]
getBookingByUser(userId: ID): Creserve
upcomingBookings: [Creserve]
countBookings: [Counts]
}
I am using countBookings as my query for aggregate functions and here's my resolver for the query:
countBookings: async (parent, args, {models}) =>
{
const res = await models.Creserve.findAndCountAll({
group: 'section',
attributes: ['section', [Sequelize.fn('COUNT', 'section'), 'count']]
});
return res.rows;
},
The query that it outputs is this:
Executing (default): SELECT "section", COUNT('section') AS "count" FROM "Creserve" AS "Creserve" GROUP BY "section";
And tried this query in my psql shell and it's working fine:
section | count
---------+-------
A | 2
R | 2
However, when I tried querying countBookings in my GraphQL Playground, section is returned but not the count:
{
"data": {
"countBookings": [
{
"section": "A",
"count": null
},
{
"section": "R",
"count": null
}
]
}
}
Is there something I missed out? Or is this a bug? This is the answer I tried following to with this example: https://stackoverflow.com/a/45586121/9760036
Thank you very much!
edit: returning a console.log(res.rows) outputs something like this:
[ Creserve {
dataValues: { section: 'A', count: '2' },
_previousDataValues: { section: 'A', count: '2' },
_changed: {},
_modelOptions:
{ timestamps: true,
validate: {},
freezeTableName: true,
underscored: false,
underscoredAll: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: null,
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: [],
indexes: [],
name: [Object],
omitNull: false,
hooks: [Object],
sequelize: [Sequelize],
uniqueKeys: {} },
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: [Array] },
__eagerlyLoadedAssociations: [],
isNewRecord: false },
Creserve {
dataValues: { section: 'R', count: '2' },
_previousDataValues: { section: 'R', count: '2' },
_changed: {},
_modelOptions:
{ timestamps: true,
validate: {},
freezeTableName: true,
underscored: false,
underscoredAll: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: null,
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: [],
indexes: [],
name: [Object],
omitNull: false,
hooks: [Object],
sequelize: [Sequelize],
uniqueKeys: {} },
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true,
attributes: [Array] },
__eagerlyLoadedAssociations: [],
isNewRecord: false } ]
Here's for res.count:
Executing (default): SELECT "section", COUNT('section') AS "count" FROM "Creserve" AS "Creserve" GROUP BY "section";
[ { count: '2' }, { count: '2' } ]
Problem
Actually you are doing everything right here... but what is happening here is the sequlize doesn't return plain object... It always returns the data in form of instance like that
[ Creserve {
dataValues: { section: 'A', count: '2' },
_previousDataValues: { section: 'A', count: '2' },
_changed: {},
_modelOptions:
{ timestamps: true,
Solution
I am not sure but there is no other way instead of looping and makes
response to json object...
const array = []
res.rows.map((data) => {
array.push(data.toJSON())
})
return array

How does the Sequelize POJO JSON.stringify magic work?

In sequelize if you do a
db.MyTable.findAll({}).function(response){
console.log(response);
}
You will see output that looks like this (depending on what your table looks like, of course):
[ { dataValues:
{ id: 1,
text: 'sdf',
complete: false,
createdAt: Thu Jan 19 2017 11:55:38 GMT-0500 (Eastern Standard Time),
updatedAt: Thu Jan 19 2017 11:55:38 GMT-0500 (Eastern Standard Time) },
_previousDataValues:
{ id: 1,
text: 'sdf',
complete: false,
createdAt: Thu Jan 19 2017 11:55:38 GMT-0500 (Eastern Standard Time),
updatedAt: Thu Jan 19 2017 11:55:38 GMT-0500 (Eastern Standard Time) },
_changed: {},
'$modelOptions':
{ timestamps: true,
instanceMethods: {},
classMethods: {},
validate: {},
freezeTableName: false,
underscored: false,
underscoredAll: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: null,
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: [],
hooks: {},
indexes: [],
name: [Object],
omitNul: false,
sequelize: [Object],
uniqueKeys: {},
hasPrimaryKeys: true },
'$options':
{ isNewRecord: false,
'$schema': null,
'$schemaDelimiter': '',
raw: true,
attributes: [Object] },
hasPrimaryKeys: true,
__eagerlyLoadedAssociations: [],
isNewRecord: false } ]
Suffice to say, it is a big complex object with a bunch of metaData on it.
However, if you try and turn that big complicated object into a string using either:
console.log(JSON.stringify(dbTodo));
or
res.json(dbTodo);
you will get back just the information for the actual entity (the items in the dataValues property of the big complex object). For my test table here, it looks like this:
[{
"id":1,
"text":"sdf",
"complete":false,
"createdAt":"2017-01-19T16:55:38.000Z",
"updatedAt":"2017-01-19T16:55:38.000Z"
}]
Which is significantly simpler (and just what I want).
How does this magic happen? Is sequelize creating their own versions of JSON.stringify() and express' res.json?
What are the rules around when it happens and when it doesn't happen?
I've searched through the sequelize docs and haven't found a good explanation.
There are two ways this behavior could manifest.
The missing properties are non-enumerable. Properties defined by Object.defineProperty set to enumerable: false (or simply don't supply a value for the enumerable setting) are non-enumerable properties, and are ignored by JSON.stringify. You can check a property's enumerable setting in the output of Object.getOwnPropertyDescriptor(obj, propName).
The object has (or has in its prototype chain) a toJSON method:
If an object being stringified has a property named toJSON whose value is a function, then the toJSON() method customizes JSON stringification behavior: instead of the object being serialized, the value returned by the toJSON() method when called will be serialized.
If the object has or inherits a toJSON method, then the return value of that method will be the target for stringification. You can check if one exists on some object obj simply by reading the value of obj.toJSON.

Keystone.js / mongoose virtual fields lean record

I'm trying to produce a lean record for a REST API that include virtual fields.
The official documentation for how to implement virtual fields for Mongoose:
http://mongoosejs.com/docs/guide.html
My model:
var keystone = require('keystone')
, Types = keystone.Field.Types
, list = new keystone.List('Vendors');
list.add({
name : {
first: {type : Types.Text}
, last: {type : Types.Text}
}
});
list.schema.virtual('name.full').get(function() {
return this.name.first + ' ' + this.name.last;
});
list.register();
Now, let's query the model:
var keystone = require('keystone'),
vendors = keystone.list('Vendors');
vendors.model.find()
.exec(function(err, doc){
console.log(doc)
});
Virtual field name.full is not here:
[ { _id: 563acf280f2b2dfd4f59bcf3,
__v: 0,
name: { first: 'Walter', last: 'White' } }]
But if we do this:
vendors.model.find()
.exec(function(err, doc){
console.log(doc.name.full); // "Walter White"
});
Then the virtual shows.
I guess the reason is that when I do a console.log(doc) the Mongoose document.toString() method is invoked which does not include virtuals by default. Fair enough. That's understandable.
To include the virtuals in any of the conversion methods you have to go:
doc.toString({virtuals: true})
doc.toObject({virtuals: true})
doc.toJSON({virtuals: true})
However, this includes keys I don't want for my REST API to pump out to my users:
{ _id: 563acf280f2b2dfd4f59bcf3,
__v: 0,
name: { first: 'Walter', last: 'White', full: 'Walter White' },
_: { name: { last: [Object], first: [Object] } },
list:
List {
options:
{ schema: [Object],
noedit: false,
nocreate: false,
nodelete: false,
autocreate: false,
sortable: false,
hidden: false,
track: false,
inherits: false,
searchFields: '__name__',
defaultSort: '__default__',
defaultColumns: '__name__',
label: 'Vendors' },
key: 'Vendors',
path: 'vendors',
schema:
Schema {
paths: [Object],
subpaths: {},
virtuals: [Object],
nested: [Object],
inherits: {},
callQueue: [],
_indexes: [],
methods: [Object],
statics: {},
tree: [Object],
_requiredpaths: [],
discriminatorMapping: undefined,
_indexedpaths: undefined,
options: [Object] },
schemaFields: [ [Object] ],
uiElements: [ [Object], [Object] ],
underscoreMethods: { name: [Object] },
fields: { 'name.first': [Object], 'name.last': [Object] },
fieldTypes: { text: true },
relationships: {},
mappings:
{ name: null,
createdBy: null,
createdOn: null,
modifiedBy: null,
modifiedOn: null },
model:
{ [Function: model]
base: [Object],
modelName: 'Vendors',
model: [Function: model],
db: [Object],
discriminators: undefined,
schema: [Object],
options: undefined,
collection: [Object] } },
id: '563acf280f2b2dfd4f59bcf3' }
I can always of course just delete the unwanted keys, but this doesn't seem quite right:
vendors.model.findOne()
.exec(function(err, doc){
var c = doc.toObject({virtuals: true});
delete c.list;
delete c._;
console.log(c)
});
This produces what I need:
{ _id: 563acf280f2b2dfd4f59bcf3,
__v: 0,
name: { first: 'Walter', last: 'White', full: 'Walter White' },
id: '563acf280f2b2dfd4f59bcf3' }
Is there not a better way of getting a lean record?
I think you want the select method.. something like this:
vendors.model.findOne()
.select('_id __v name').
.exec(function(err, doc){
console.log(c)
});
Also personally I prefer setting virtuals: true on the schema rather than the document, but depends on use case I guess.
One solution would be to use a module like Lodash (or Underscore) which allows you pick a whitelist of property names:
vendors.model.findOne()
.exec(function(err, doc){
var c = _.pick(doc, ['id', 'name.first', 'name.last', 'name.full']);
console.log(c)
});
Given your use-case of serving this data via REST API, I think explicitly defining a whitelist of property names is safer. You could even define a virtual property on your schema which returns the predefined whitelist:
list.schema.virtual('whitelist').get(function() {
return ['id', 'name.first', 'name.last', 'name.full'];
});
and use it in multiple places, or have different versions of your whitelist, all managed at the model layer.

Model.findOne not returning docs but returning a wrapper object

I have defined a Model with mongoose like this:
var mongoose = require("mongoose")
var Schema = mongoose.Schema
var userObject = Object.create({
alias: String,
email: String,
password: String,
updated: {
type: Date,
default: Date.now
}
})
var userSchema = new Schema(userObject, {strict: false})
var User = mongoose.model('User', userSchema)
module.exports = User
Then I created a user that I can perfectly find through mongo console like this:
db.users.findOne({ email: "coco#coco.com" });
{
"_id" : ObjectId("55e97420d82ebdea3497afc7"),
"password" : "caff3a46ebe640e5b4175a26f11105bf7e18be76",
"gravatar" : "a4bfba4352aeadf620acb1468337fa49",
"email" : "coco#coco.com",
"alias" : "coco",
"updated" : ISODate("2015-09-04T10:36:16.059Z"),
"apps" : [ ],
"__v" : 0
}
However, when I try to access this object through a node.js with mongoose, the object a retrieve is not such doc, but a wrapper:
This piece of code...
// Find the user for which the login queries
var User = require('../models/User')
User.findOne({ email: mail }, function(err, doc) {
if (err) throw err
if (doc) {
console.dir(doc)
if(doc.password == pass) // Passwords won't match
Produces this output from console.dir(doc)...
{ '$__':
{ strictMode: false,
selected: undefined,
shardval: undefined,
saveError: undefined,
validationError: undefined,
adhocPaths: undefined,
removing: undefined,
inserting: undefined,
version: undefined,
getters: {},
_id: undefined,
populate: undefined,
populated: undefined,
wasPopulated: false,
scope: undefined,
activePaths: { paths: [Object], states: [Object], stateNames: [Object] },
ownerDocument: undefined,
fullPath: undefined,
emitter: { domain: null, _events: {}, _maxListeners: 0 } },
isNew: false,
errors: undefined,
_doc:
{ __v: 0,
apps: [],
updated: Fri Sep 04 2015 12:36:16 GMT+0200 (CEST),
alias: 'coco',
email: 'coco#coco.com',
gravatar: 'a4bfba4352aeadf620acb1468337fa49',
password: 'caff3a46ebe640e5b4175a26f11105bf7e18be76',
_id: { _bsontype: 'ObjectID', id: 'Uét Ø.½ê4¯Ç' } },
'$__original_validate': { [Function] numAsyncPres: 0 },
validate: [Function: wrappedPointCut],
_pres: { '$__original_validate': [ [Object] ] },
_posts: { '$__original_validate': [] } }
Therefore, passwords won't match because doc.password is undefined.
Why is this caused?
That's exactly the purpose of mongoose, wrapping mongo objects. It's what provides the ability to call mongoose methods on your documents. If you'd like the simple object, you can call .toObject() or use a lean query if you don't plan on using any mongoose magic on it at all. That being said, the equality check should still hold as doc.password returns doc._doc.password.

Resources