Virtual properties in SequelizeJS do not work - node.js

I have following schema in SequelizeJS:
var moment = require('moment');
module.exports = function(sequelize, DataTypes) {
var Account = sequelize.define('Account', {
suspended: {
type: DataTypes.BOOLEAN,
defaultValue: false
}
}, {
getterMethods: {
trialDaysLeft: function() {
return 5;
}
},
tableName: 'accounts'
});
return Account;
};
I want to get trialDaysLeft when I call account.trialDaysLeft property.
I am getting
TypeError: Property 'trialDaysLeft' of object [object Object] is not a function
Where am I wrong?

The name might not be the most intuitive but what getterMethods actually does is generate properties with getter methods on the instance objects.
So in this instance you would need to call instance.trialDaysLeft and not instance.trialDaysLeft() (which i'm guessing is what you're doing).

Related

How to call schema method inside another method in the same model using mongoose

I have a model named "Notification" and it has two methods. I want to call a method inside another method and query the same model using mongoose.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const NotificationSchema = new Schema({
code: { type: 'string', required: true, unique: true },
name: { type: 'string', required: true }
}, collection : "notification");
NotificationSchema.methods.MethodA = async () => {
// querying the same model
let query = await this.find({"code" : "abc"}).lean();
this.MethodB();
};
NotificationSchema.methods.MethodB = () => {
console.log("this is methodB");
};
module.exports = mongoose.model("Notification", NotificationSchema);
Now, can't query the same model and calling methodB in methodA is throwing an error
this.methodB is not a function
Could you please try using good old function definition instead of es6 arrow function definition as shown below?
NotificationSchema.methods.MethodA = async function() {
// querying the same model
let query = await this.find({"code" : "abc"}).lean();
this.MethodB();
};
NotificationSchema.methods.MethodB = function() {
console.log("this is methodB");
};

How to access a different schema in a virtual method?

I want to write a virtual (get) method for my MongoDb collection (Parts) which needs to access a different schema: I want it to assert if a document is 'obsolete' according to a timestamp available in a different (Globals) collection:
const partsSchema = new Schema({
...
updatedAt: {
type: Date,
},
...
}, {
toObject: { virtuals: true },
toJSON: { virtuals: true },
});
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
But when I do a find, I always get a {} in the obsolete field, and not a boolean value...
const p = await parts.find();
...
"obsolete": {},
...
Is there some way to accomplish my goal?
You can do this, but there are a few obstacles you need to hurdle. As #Mohammad Yaser Ahmadi points out, these getters are best suited for synchronous operations, but you can use them in the way you're using them in your example.
So let's consider what's happening here:
partsSchema.virtual('obsolete').get(async function() {
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' }).exec();
return this.updatedAt < timestamp.value;
});
Since the obsolete getter is an async function, you will always get a Promise in the obsolete field when you query your parts collection. In other words, when you do this:
const p = await parts.find();
You will get this:
...
"obsolete": Promise { <pending> },
...
So besides getting the query results for parts.find(), you also need to resolve the obsolete field to get that true or false result.
Here is how I would write your code:
partsSchema.virtual('obsolete').get(async function() {
const Globals = mongoose.model('name_of_globals_schema');
const timestamp = await Globals.findOne({ key: 'obsolescenceTimestamp' });
return this.updatedAt < timestamp.value;
});
Then when querying it...
parts.findOne({_id: '5f76aee6d1922877dd769da9'})
.then(async part => {
const obsolete = await part.obsolete;
console.log("If obsolete:", obsolete);
})

Sequelize upsert or create without PK

I'm unable to perform any kind of upsert or create within Sequelize (v: 6.9.0, PostGres dialect).
Using out-of-the-box id as PK, with a unique constraint on the name field. I've disabled timestamps because I don't need them, and upsert was complaining about them. I've tried manually defining the PK id, and allowing Sequelize to magically create it. Here's the current definition:
const schema = {
name: {
unique: true,
allowNull: false,
type: DataTypes.STRING,
}
};
class Pet extends Model { }
Pet.define = () => Pet.init(schema, { sequelize }, { timestamps: false });
Pet.buildCreate = (params) => new Promise((resolve, reject) => {
let options = {
defaults: params
, where: {
name: params.name
}
, returning: true
}
Pet.upsert(options)
.then((instance) => {
resolve(instance);
})
.catch(e => {
// message:'Cannot read property 'createdAt' of undefined'
console.log(`ERROR: ${e.message || e}`);
reject(e);
});
});
module.exports = Pet;
Upsert code:
// handled in separate async method, including here for clarity
sequelize.sync();
// later in code, after db sync
Pet.buildCreate({ name: 'Fido' });
In debugging, the options appear correct:
{
defaults: {
name: 'Fido'
},
returning:true,
where: {
name: 'Fido'
}
}
I've also tried findOrCreate and findCreateFind, they all return errors with variations of Cannot convert undefined or null to object.
I've tried including id: null with the params, exact same results.
The only way I've succeeded is by providing PK in the params, but that is clearly not scalable.
How can I upsert a Model instance without providing a PK id in params?
class Pet extends Model { }
//...you might have the id for the pet from other sources..call it petId
const aPet = Pet.findCreateFind({where: {id: petId}});
aPet.attribute1 = 'xyz';
aPet.attribute2 = 42;
aPet.save();

Mongoose: How to set _id manually before saving?

With the following code given:
const schema = new Schema({
_id: {
type: String
},
name: {
type: String,
required: true,
trim: true
}
}
schema.pre('validate', (next) => {
console.log(this.name);
this._id = crypto.createHash('md5').update(this.name).digest("hex");
next();
});
const myObject = new MyObject({ name: 'SomeName' });
myObject.save();
The application throws this error message:
MongooseError: document must have an _id before saving
My Question is, how is it possible to set the _id manually for a model?
And why is this.name undefined
(next) => ... is arrow function where this is lexical and refers to enclosing scope, which is module.exports in Node.js module scope.
In order to get dynamic this inside a function, it should be regular function:
schema.pre('validate', function (next) { ... })

Node.js => TypeError: Object [object Object] has no method 'save'

I'm new to Node.js and i'm trying to run a sort of insert query. Here is my code:
exports.savetea = function(req, res){
var quantity = req.query.quantity;
var Sequelize = require('sequelize');
var sequelize = new Sequelize('nodehmssm', 'root', 'root', {host: "localhost", port: 3306, dialect: 'mysql'});
var Tea = sequelize.import(__dirname + "/model/tea");
Tea.build({quantity: quantity, status: "active"}).save();
res.send("Tea added...");
};
My tea.js file is as follows:
module.exports = function(sequelize, DataTypes) {
return sequelize.define("tea", {
id : DataTypes.INTEGER,
quantity : DataTypes.INTEGER,
status : DataTypes.STRING,
created_at : DataTypes.DATE
});
};
Whenever I run the code, I get the error
TypeError: Object [object Object] has no method 'save'
Also to mention, the settings are good as I can run the login code select * from where...
What am I doing wrong?
A Somewhat Misleading Error Message
In Sequelize, you cannot save an object with undefined properties.
Since your model had no defaultValue, and you didn't define id or created_at, the .build() method was not creating a .save() method on the object it returned.
For more on the documentation, checkout their documentation on built instances and this section on how to implement autoincrement.
I know this is an old question, but perhaps I can help someone that has that problem.
Actualy the problem is that you are calling a .save() method of something that doesnt have such a method.
This is a common mistake. You are not using the .build() method correctly. See example below.
What you are doing:
Tea.build({quantity: quantity, status: "active"}).save();
What you should be doing is this:
var actualObject = Tea.build({quantity: quantity, status: "active"});
actualObject.save() //etc...
You see an error because Tea is actualy an object of the model NOT a instace of that model that you just built.
Hope that helps.
The type error you are getting is stating the object Tea has no method called .save. In your code:
Tea.build({quantity: quantity, status: "active"}).save();
Tea is your object and build is calling an instance method called .save where you want to insert your JSON data. When you call either a class or instance method from a specific sequelize model, you will get the error you found.
I recommend for you to use a template for your sequelize models to help resolve this common mistake. Below is my suggestion:
modelTemplate.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('User', {
col1: DataTypes.STRING,
col2: DataTypes.STRING
},{
classMethods: {
doSomething: function(successcb, errcb, request) {}
},
instanceMethods: {
saveSomething: function(data) {}
}
});
};
For your specific answer. I would adjust your call to the model with the following lines:
var data_json = {quantity: quantity, status: "active"};
Tea.build().save(data_json);
tea.js
module.exports = function(sequelize, DataTypes) {
return sequelize.define('tea', {
id : DataTypes.INTEGER,
quantity : DataTypes.INTEGER,
status : DataTypes.STRING
},{
classMethods: {
doSomething: function(successcb, errcb, request) {}
},
instanceMethods: {
save: function(data) {}
var new_instance = Tea.build({
quantity: data[0].quantity,
status: data[0].status
});
//saving instance
// you can also build, save and access the object with chaining:
new_instance
.save()
.success(function(){}).error("instance error in .save() method");
}
});
};
Edit: made a correction to a comma in the template.

Resources