I'm working on an express sequelize project.
I've got an issue with sequelize.
I've got a simply schema like the documentation says:
class Task extends Model {}
Task.init({ title: Sequelize.STRING }, { sequelize, modelName: 'task' });
class User extends Model {}
User.init({ username: Sequelize.STRING }, { sequelize, modelName: 'user' });
User.hasMany(Task); // Will add userId to Task model
Task.belongsTo(User); // Will also add userId to Task model
But when I'm querying with include
sequelize.models['task'].findAll({include:[{all:true}]});
I'm facing to some issues:
The first :
this._getIncludedAssociation is not a function
Here is the github source origin code : https://github.com/sequelize/sequelize/blob/master/lib/model.js#L615
I just change this to Model to check.
This error is gone away, but a second (linked with the first, because I changed the behaviour), appears
User is not associated to Model
But It should say User is not associated to Task
(github of error: https://github.com/sequelize/sequelize/blob/master/lib/model.js#L711)
It's like the class Model wasn't instanciate...
What could be bad in my code ? Maybe the fact to call the model by sequelize.models[modelName] ?? I'm struggling with this for a long time...
Thanks.
More Infos :
Sequelize Version : ^5.7.0
Architecture :
At the application launch, all my models are store in a custom Database class stored in my process.
Inside any controller, I call a repository class with for exemple a method like the following where the wanted model is accessible via this.model:
findAll(options, force = false) {
let data = this.performExtraOptions(options);
data.paranoid = force;
if (this.isDebug) {
this.req.debug.addDatabaseCall('Table ' + this.model.getTableName() + ' findAll', data);
}
return this.model.findAll(data);
}
When I inspect with Chrome Debugger, the model is show like this :
const model = class extends Model {};
Ok.
In reading the sequelize code, I found that the options.model is required inside the algo.
The problem was that I sent the model too in the query options, but as the model name (string).
The algo, transform the option.model as the instance object of Model to make the process.
So I just have to rename my variable passed as query options.
This line of code was the problem :
https://github.com/sequelize/sequelize/blob/master/lib/model.js#L493
if (!options.model) options.model = this;
Related
My goal with this question is to achieve, in a safe and sustainable way, working VS Code intellisense with Sequelize model definitions in my node.js (not typescript) project.
Sequelize models can be constructed
via hooks, or
by extending the Model class.
Models created with hooks won't be understood by intellisense, so I'm focused on the latter below.
Sequelize model constructors require a configured Sequelize connection instance. There are at least two ways of accomodating this.
A - Instantiate sequelize next to every model (ie: many times)
One documented example of model definition:
const { Sequelize, DataTypes, Model } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory');
class User extends Model {}
User.init({
// Model attributes are defined here
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING
// allowNull defaults to true
}
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
modelName: 'User' // We need to choose the model name
});
In this example, sequelize connection is instantiated in the same file alongside the model definition.
We can repeate exactly this in many Model definition files. Each file can then export a Model class which can be used directly by the caller.
Intellisense will understand this setup.
B - Instantiate sequelize once, and pass it around
Sequelize CLI facilitates the below project structure:
Each model definition file exports a function which builds the Model class. The function accepts the sequelize instance as an argument.
The models/index.js file instantiates sequelize -- the only place in the project where this occurs. Every model-definition-function is then called, to which this sequelize instance is passed.
The resulting models are all exported by models/index.js.
It seems intellisense does not understand this, and cannot trace the class methods from these created-by-function exports.
Below is the User.js file that Sequelize CLI would create for exactly the same model as the above example:
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate(models) {
// define association here
}
}
User.init(
{
firstName: DataTypes.STRING,
lastName: DataTypes.STRING
},
{
sequelize,
modelName: "User"
}
);
return User;
};
For the sake of intellisense, I would prefer to (more or less) use the structure evidenced by the first example: export classes, not functions. I might instantiate sequelize in some separate file, but then require() that file many times throughout my various model definitions.
Is this a bad idea? Will I unwittingly create some cacophony of competing connections? Do I need to be concerned of any side effects?
I am creating two models, the first one is User and the second one is Portfolio. When a user is creating a Portfolio (where a user can only have one Portfolio), I want it to have reference to the user who is creating it, and every time that user's data is fetched, I want it to also fetching their portfolio data if any.
I am trying to use hasOne to create portfolio_id inside User tables, with the skeleton generated using sequelize init command, but it is not working. I cannot find a column the name protfolio_id if I don't put it inside the user migration file. Is that how it is supposed to be?
How should I design the models? Should I include the portfolio_id in User tables and include user_id in Portfolio table, or is there a best way to do it?
And which associations method should I use, hasOne or belongsTo?
First of all make sure that you are calling Model.associate for each model. This will run queries for all the relationships.
You can define the relationships in the associate method as follows:
// user.js (User Model definition)
module.exports = (sequelize, dataTypes) => {
const { STRING } = dataTypes
const User = sequelize.define("user", {
username: { type: STRING }
})
User.associate = models => {
User.hasOne(models.Portfolio, { foreignKey: "userId" }) // If only one portfolio per user
User.hasMany(models.Portfolio) // if many portfolios per user
}
return User
}
// portfolio.js (Portfolio Model definition)
module.exports = (sequelize, dataTypes) => {
const { STRING } = dataTypes
const Portfolio = sequelize.define("portfolio", {
portfolioName: { type: STRING }
})
Portfolio.associate = models => {
Portfolio.belongsTo(models.User, { foreignKey: "userId" })
}
return Portfolio
}
hasOne stores the foreignKey in the target model. So this relationship will add a foreign key userId to the Portfolio model.
belongsTo stores the key in the current model and references the primary key of the target model. In this case the Portfolio.belongsTo will add userId in the Portfolio model which will reference the primary key of User model.
Notice how both these relationships do the same thing, they add userId to the Portfolio model. Its better to define this in both models for your last use case:
I want it to have reference to the user who is creating it, and every time that user's data is fetched, I want it to also fetching their portfolio data if any.
Accessing related models:
In sequelize fetching a related model together with the main model is called Eager Loading. Read more about it here.
Now for your use case if you want to fetch the portfolio if any, while fetching user, do the following:
var userWithPortfolio = await User.findAll({include: [models.Portfolio]};
// Or you may also use include: {all: true} to include all related models.
var userWithPortfolio = await User.findAll({include: {all: true}};
/*
Output:
userWithPortfolio = {
username: "xyz",
portfolio: {
portfolioName: "xyz"
}
}
*/
I have a sequelize model called Activity, that belongs to another model called User.
I want to be able to generate a dynamic property on the Activity model, so I did the following:
class Activity extends Sequelize.Model {
toJSON() {
return {
...this.dataValues,
my_property: 'its value', // Dynamic property
};
}
}
This works quite fine when I query the Activity itself (or get them all), but the property is not there when I query a User with its activities e.g.
const user = await User.findByPk('some-pk-here', { include: [Activity] });
Can this behavior be changed, and if yes then how?
I am using the Waterline ORM to interface with my database.
I am extend Waterline.Collections into a new objects and assign them to a module:
var Waterline = require('waterline');
var User = Waterline.Collection.extend({
...some fields...
attributes:{
col_a:{
type:'integer'
},
touch_user:function(){
// increment col_a and update in table
}
}
});
export = module.exports = User;
I query for the specific models I need to update, then manipulate them in a promise.
database.models.users.findAll().where({'id':1}).then(...model manipulation...)
There is a task I perform often, almost whenever I access the model. I would like to encapsulate this task in a function associated with the instance so I can simple call "touch_user" and the relevant fields will be updated for the model i call it on.
I am not able to access the Waterline query methods (such as .update()) from the model instance after a model is queried like so:
database.models.users.findAll().where({'id':1).then((user)=>{
user.touch_user();
//user.update // does not work
});
But it is not until I query it that I retrieve the data associated with the record.
I would like user.touch_user() to increment col_a in the users table similar to the following
database.models.users.findAll().where({'id':1).then((user)=>{
user.touch_user();
database.models.users.update(user.id, {'col_a':user.col_a+1});
});
The problem with the above is that the user object does not accurately reflect the update to the table.
What I ended up doing was creating a class method to update the model and the instance. Not exactly what I want, but it does in a pinch to keep the model and table synced
var User = Waterline.Collection.extend({
...some fields...
attributes:{
col_a:{
type:'integer'
},
},
// touch_user is a class method here
touch_user:function(user){
// update model instance
user.col_a += 1;
// update database table
database.models.users.update(user.id, {'col_a':user.col_a).exec(function(){});
}
});
Called like so
database.models.users.findAll().where({'id':1).then((user)=>{
...modify user...
database.models.users.touch_user(user);
...other changes to user that have to occur here...
database.models.users.touch_user(user);
});
While different from a previous question I asked here, it's related and so wanted to link it.
I have been trying hard to find out how I can get the model name (identity) or model "class" (exposed in sails.models) of a record. So, given a waterline record, how can I find out its model name or class?
Example (of course here I know the model is User but that is an example):
User.findOne(1).exec(function(err, record) {
// at this point think that we don't know it's a `user` record
// we just know it's some record of any kind
// and I want to make some helper so that:
getTheModelSomehow(record);
// which would return either a string 'user' or the `User` pseudo-class object
});
I have tried to access it with record.constructor but that is not User, and I couldn't find any property on record exposing either the model's pseudo-class object or the record's model name.
UPDATE:
To clarify, I want a function to which I'll give ANY record, and which would return the model of that record as either the model name or the model pseudo-class object as in sails.models namespace.
modelForRecord(record) // => 'user' (or whatever string being the name of the record's model)
or
modelForRecord(record) // => User (or whatever record's model)
WOW, ok after hours of research, here is how I am doing it for those who are interested (it's a very tricky hack, but for now unable to find another way of doing):
Let's say record is what you get from a findOne, create, ... in the callback, to find out what instance it is, and so find the name of the model owning the record, you have to loop over all your models (sails.models.*) and make an instanceof call this way:
function modelFor(record) {
var model;
for (var key in sails.models) {
model = sails.models[key];
if ( record instanceof model._model.__bindData__[0] ) {
break;
}
model = undefined;
}
return model;
}
Do not try to simply do instanceof model, that does not work
After if you need the model name simply modelFor(record).globalId to get it.
In your model definition why not just create a model attribute. Then the model will be returned with every record call. This will work even after the record become a JSON object.
module.exports = {
attributes : {
model : {type:'string',default:'User'}
}
}
Use this example for parsing model name
var parseModel = function(request) {
request = request.toLowerCase();
return sails.models[request];
};
and use in controller this code for using this model
parseModel(req.param('modelname')).find().then().catch();
Sails exposes everything in the request object. Try grabbing the name of the model this way:
var model = req.options.model || req.options.controller;
That will give you the raw name. To use it, you'll have to plug the model into the sails models array.
var Model = req._sails.models[model];
Check out the source code to see it in action. (https://github.com/balderdashy/sails/blob/master/lib/hooks/blueprints/actionUtil.js#L259)