I use Sequelize to store and save data to a database.
var sequelize = require('../database.js').sequelize,
Sequelize = require('../database.js').Sequelize;
User = sequelize.define('user', {
authID: Sequelize.STRING,
name: Sequelize.STRING,
});
User.prototype.createRider = () => {
console.log('test');
};
module.exports = User;
I'm trying to extend the model with a new method to store a specific kind of user, but User.prototype.createRider doesn't work. What can be done?
There is an "expansion of models" section in their documentation
I believe you can use the classMethods object
var Foo = sequelize.define('foo', { /* attributes */}, {
classMethods: {
createRider: function(){ return 'smth' }
}
})
You can define both instance and class methods on your Model, during the define.
Change your sequelize.define as follows:
User = sequelize.define('user', {
authID: Sequelize.STRING,
name: Sequelize.STRING,
}, classMethods: {
createRider: function() {
console.log('test');
}
});
You can then use the classMethods in the expected way:
User.createRider();
See the Sequelize documentation: Model Definition.
Related
I currently have a User model and a Team model that I want to associate.
User.js Model
"use strict";
module.exports = sequelize => {
const User = sequelize.define(
"User",
{
id: {
type: Sequelize.UUID,
defaultValue: Sequelize.UUIDV1,
primaryKey: true,
allowNull: false
},
...more fields
}
{ tableName: "Users", timestamps: true }
);
User.associate = function(models) {
// 1 to many with skill
User.hasMany(models.Skill, {
foreignKey: "userId"
});
// 1 to many with team
User.hasMany(models.Team, {
foreignKey: "userId"
});
};
return User;
};
Team.js Model
"use strict";
module.exports = (sequelize, DataTypes) => {
const Team = sequelize.define(
"Team",
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: DataTypes.INTEGER
},
...more fields
},
{ tableName: "Teams", timestamps: true }
);
Team.associate = function(models) {
Team.belongsTo(models.User, {
foreignKey: "userId"
});
};
return Team;
};
The column userId is automatically added to the team's table with the correct type (uuid) but no matter what I try it's always null.
I have tried just defining without options, and again the column was added, but set to null when a team was created.
1). Define the associations with no options.
User.associate = function(models) {
User.hasMany(models.Team);
}
Also, I have another table "Skills" that uses the same code but it works.
"use strict";
module.exports = (sequelize, DataTypes) => {
const Skill = sequelize.define(
"Skill",
{
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
},
{ tableName: "Skills", timestamps: true }
);
Skill.associate = models => {
Skill.belongsTo(models.User, {
foreignKey: "userId"
});
};
return Skill;
};
This is the method i'm using to create the team.
// Create a new team
exports.createTeam = async (req, res) => {
// Get the user id from the session
const userId = req.session.passport.user.id;
const { title } = req.body;
// Make sure the user hasn't already created a team with that title.
const existingTeam = await models.Team.findOne({
where: { title: title, userId: userId }
});
if (existingTeam !== null) {
// Response and let the user know.
...
return;
}
const defaultTeamValues = {
title: title,
...
};
// No existing team was found with that title and created by that user.
// Create it.
const team = await models.Team.create(defaultTeamValues);
// Done
res.status(200).json(team);
};
New record in Team's table showing null value in foreign key (userId) column.
I'm using pgAdmin 4 to view the tables and each table has the correct constraints fkeys too.
I feel like I'm missing something simple here, but I've read the docs and searched for similar issues but haven't found what I needed. What am I missing?
Ok, so I sort of figured out the problem. Instead of using
// Look for existing team
const existingTeam = await models.Team.findOne({
where: { title: title, userId: userId }
});
if (existingTeam !== null) {
...
return;
}
// No existing team was found with that title and created by that user.
// Create it.
const team = await models.Team.create(defaultTeamValues);
I switch to this method.
const team = await models.Team.findOrCreate({
where: {
title: title,
userId: userId
},
// title and userId will be appended to defaults on create.
defaults: defaultTeamValues
});
The difference being that findOrCreate will append, the values in the where query, to the default values you provide.
This is what I did previously when adding new skills and that's why my Skills records had the userId and Teams did not.
So at this point, I don't know if my associations are even working because I'm actually just adding the userId value into the new record.
Back to reading the docs and testing.
I want to understand how sequelize instance methods works and if its possible to use returned object as instance for further usage. Basically I'm just selecting user by its user name, later I want to compare if password matches and if so - update data. But the error says
Unhandled rejection TypeError: user_data.validPassword is not a function
and I'm not even close to instance update..
my User model:
const bcrypt = require('bcryptjs');
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_name: DataTypes.STRING,
user_password: DataTypes.STRING,
user_token: DataTypes.STRING,
user_alias_name: DataTypes.STRING,
}, {
tableName: 'oc_users',
instanceMethods: {
generateHash(password) {
return bcrypt.hash(password, bcrypt.genSaltSync(8));
},
validPassword(password) {
return bcrypt.compare(password, this.password);
}
}
});
return User;
};
my method:
...
loginAttempt(cookie) {
return models.User.findOne({
attributes: ['id', 'user_password', 'user_alias_name'],
where: {user_name: this.user}
}).then(user_data => {
if (!user_data) return 'No matching results for such a user';
return user_data.validPassword(this.password).then(result => {
if (result !== true) return 'Invalid password for selected user';
return this.updateAfterLogin(user_data, cookie);
})
})
}
updateAfterLogin(user, cookie) {
return user.update({
user_token: cookie
}).then(()=> {
return {data: 'updated'};
})
}
...
It depends on which version of sequelize you're using and probabily you're using Sequelize v4. On Sequelize v4 classMethods and instanceMethods were removed from sequelize.define.
You may check it at oficial docs for more informations:
http://docs.sequelizejs.com/manual/tutorial/upgrade-to-v4.html#config-options
Removed classMethods and instanceMethods options from sequelize.define. Sequelize models are now ES6 classes. You can set class / instance level methods like this
Old
const Model = sequelize.define('Model', {
...
}, {
classMethods: {
associate: function (model) {...}
},
instanceMethods: {
someMethod: function () { ...}
}
});
New
const Model = sequelize.define('Model', {
...
});
// Class Method
Model.associate = function (models) {
...associate the models
};
// Instance Method
Model.prototype.someMethod = function () {..}
I'm working on app but I having this issue using Node.js and Sequelize for Postgresql :
throw new Error(this.name + '.' + Utils.lowercaseFirst(Type.toString()) + ' called with something that\'s not a subclass of Sequelize.Model');
^
Error: Expense.class BelongsTo extends Association {
constructor(source, target, options) {
super(source, target, options);
<....LOT OF CODE FROM SEQUELIZE ....>
if ((fieldsOrOptions || {}).transaction instanceof Transaction) {
options.transaction = fieldsOrOptions.transaction;
}
options.logging = (fieldsOrOptions || {}).logging;
return association.target.create(values, fieldsOrOptions).then(newAssociatedObject =>
sourceInstance[association.accessors.set](newAssociatedObject, options)
);
}
} called with something that's not a subclass of Sequelize.Model
I don't understand this error, especially the last line "called with something that's not a subclass of Sequelize.Model".
Here is the models :
User model
const models = require('./index');
const Expense = models.User;
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('User', {
firstname: DataTypes.STRING,
lastname: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Expense);
}
}
});
console.log(models);
User.hasMany(Expense, {as: 'Expenses', foreignKey: 'userId'});
return User;
};
And Expense model
const models = require('./index');
const User = models.User;
module.exports = (sequelize, DataTypes) => {
var Expense = sequelize.define('Expense', {
name: DataTypes.STRING,
amount: DataTypes.INTEGER,
date: DataTypes.INTEGER
}, {
classMethods: {
}
});
Expense.belongsTo(User, { foreignKey: 'userId' });
return Expense;
};
And the controller for creating an expense :
createExpense: function(req, res) {
const user = User.findOne({ where: { id: req.params.id } });
Expense.create({
name: req.body.name,
amount: req.body.amount,
date: req.body.date,
User: user
},{
include: [
{
model: User
}
]
}).then((created) => {
res.status(200).send({ success: true, message: 'Dépense ajoutée !' });
});
}
Does someone have already see an error that look like that ? I search for few days without any issue, if someone could help I'll really appreciate,
thank !
I have a same problem before, but I found a reason. Key point is that your models must under a same sequelize class.
You can look my project mini-shop on github.
Though being 3 years late... I think, it's caused by a bad import. The import in in "User Model" for the "Expense Model" uses this line and looks kind of fishy:
const Expense = models.User;
But from a rather general point of view, this kind of error message might be a hint to an error while creating an association in a line such as User.hasMany(Expense, {as: 'Expenses', foreignKey: 'userId'});
Here is another answer regarding a similar question. For example in my case, the error came from not using the definition name to access my model (<- depends on your setup!)
I'm trying to create a method User.createRider() that adds normalized data to to the UserAttribute column.
var sequelize = require('../database.js').sequelize,
Sequelize = require('../database.js').Sequelize,
UserAttribute = sequelize.define('userAttributes', {
key: Sequelize.STRING,
value: Sequelize.STRING,
}),
User;
UserAttribute.belongsTo(User);
User = sequelize.define('user', {},{
classMethods: {
createRider: function(params) {
newUser = User.create();
//this should be related to the user.
UserAttribute.create({key: 'somekey', value: 'somevalue'});
return newUser;
}
}
});
module.exports = User;
My problem is that I can't create the UserAttribute-model before the user is done, because I need the User-model to associate the userAttributes to and I can't create the usermodel first, because I can't create the class method before the userAttribute is defined. This seems like a common scenario. How can this be solved.
Sounds to me like you could create the user first and then add its id to the userAttribute when you create the latter. Like so:
User = sequelize.define('user', {}, {
classMethods: {
// Returns a new user instance as a promise.
createRider: function(params) {
return User.create().then(function(user) {
return UserAttribute.create({
key: 'somekey',
value: 'somevalue',
userId: user.id
}).then(function() {
// Make sure this promise chain returns the user object, not the userAttribute object
return user;
});
});
}
}
});
I'm using this way to keep my Sequelize models in separate files and everything works pretty well but now I came up with the idea to have scopes with includes in it.
Something like this doesn't work:
var User = sequelize.define("User", {...}, {
scopes: {
complete: {
include: [{
model: Task
}]
}
}
});
... Since Task is (of course) not defined. Even using require('.').Task instead doesn't help at this point because User gets loaded before Task and by the time User is loaded, Task is not yet defined.
So, is there a simple and easy way without a dozen workarounds to have
associations
scopes with includes
... All of this in a separate file per model?
and if the model (Task) is not yet loaded? (in this case T comes before U and the model is loaded).
Object.keys(db).forEach(function(modelName) {
if ("associate" in db[modelName]) {
db[modelName].associate(db);
}
});
becomes
Object.keys(db).forEach(function(modelName) {
if ("associate" in db[modelName]) {
db[modelName].associate(db);
}
});
Object.keys(db).forEach(function(modelName) {
if ("loadScopes" in db[modelName]) {
db[modelName].loadScopes(db);
}
});
and the model
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
username: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Task)
}
}
});
return User;
};
becomes
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("User", {
username: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Task)
}
loadScopes: function(models) {
User.addScope('complete', {
include: [{
model: models.Task
}]
})
}
}
});
return User;
};
Using the addScope() method is better for include models in scope. This is because Sequelize models are defined alphabetically, Model A comes first before Model B. When you include Model B in Model A scope, by the time Model A runs Model B is undefined. addScope() method solves this issue.
module.exports = function(sequelize, DataTypes) {
const User = sequelize.define('User', {
username: DataTypes.STRING
}, { });
User.associate = function(models) {
// associations can be defined here
User.hasMany(models.Task);
// scopes should be defined here
User.addScope('defaultScope', {
include: [{ model: models.Task }],
});
// scopes with parameter
User.addScope('byProfile', profileId => ({
where: { profileId },
}));
};
return User;
};
using default scope:
User.findAll();
This will use the default scope by default.
using byProfile scope:
User.scope({ method: ['byProfile', profile.id] }).findAll();
Try to use Sequelize.models.Task
module.export = function(Sequelize, DataTypes){
var User = sequelize.define("User", {...}, {
scopes: {
complete: {
include: [{
model: Sequelize.models.Task
}]
}
}
});
}
Or use addScope() to add them without having to worry about order of loading each model