hasMany called with something that's not an instance of Sequelize.Model - node.js

as you guys can see my issue is related to the title description, i created a User Model, and a Foto Model in sequelize, basicly a user can shoot many fotos, but each foto can be related to just 1 user.
My User model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var Foto = require('./Foto');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { username: value } })
.then(function (user) {
// reject if a different user wants to use the same username
if (user && self.id !== user.id) {
return next('username already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { email: value } })
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
typeOfUser: {
type: DataTypes.INTEGER,
allowNull:true,
defaultValue:null
},
country: {
type: DataTypes.STRING,
allowNull:true,
defaultValue:null
},
birthDate:{
type: DataTypes.DATEONLY,
allowNull:true,
defaultValue:null
},
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
points: {
type: DataTypes.INTEGER,
defaultValue: 0
},
password: {
type: DataTypes.STRING,
allowNull:false
},
numberFotos: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
classMethods: {
generateHash: function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
return Foto;
}
My foto model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Foto = sequelize.define("Foto", {
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
image: {
type: DataTypes.STRING,
allowNull: false
},
date: {
type: DataTypes.DATE,
allowNull:true
},
position: {
type: DataTypes.RANGE,
allowNull: true
}
});
Foto.belongsTo(User, {foreignKey: 'userId'});
return Foto;
}

You don't need to declare the association on the Photo Model:
Foto.belongsTo(User, {foreignKey: 'userId'});
When you have a 1:N relation between models you only need to refer the id from the "1" model, on our case the User model, on the "N" model, Photos. So doing:
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
Will create a column on your Foto table with name "userId" that refer to user table. On this way both models are associate as you want.

You can define relations for both models in one file. It doesn't throw any errors that way.
In your Foto.js, you can try:
...
Foto.belongsTo(User);
User.hasMany(Foto);
return Foto;

I had a similar problem. Sometimes it can be caused because in your index.js or app.js the files are loaded in a specific order, so for example if you have a relationship between A and B, A loads first and references B, and B in turn references A, the error will be thrown inside the B file because A has not been fully defined/executed yet.
The solution to this would be to remove all associations from the model files, and inside your app or index.js require them all, and then define their relationships.
Example
const entities = {
A: require('./src/Entity/A'),
B: require('./src/Entity/B'),
};
entities.A.belongsToMany(entities.B, {through: 'AB'});
entities.B.belongsToMany(entities.A, {through: 'AB'});

So I was getting this error and it took me some time to deal with the bug. I realised I was getting the Error because I was referencing the model wrongly. Sequelize is case sensitive so if you created the model with UpperCase ensure to keep it uniform throughout your referencing.
I would also point out you could try this out instead
User.hasMany(models.Foto ,{as: 'fotos', foreignKey: 'userId'})

It seems you need to define both ends of the relationship in the file containing the 1 part of the 1:many association. That is, the "User" file in your case.
So:
User.hasMany(Foto);
Foto.belongsTo(User);

None of the above solutions worked for my scenario (could work for other setups). I stumbled upon this article which states you have to have the models defined and exported prior to applying the associations. Using a separate extra-setup.js file to define the associations, worked for me.
https://github.com/sequelize/express-example/tree/master/express-main-example

I had lots of issues, but I switched to using the sequelize CLI which generated models in this format, I then found creating associations a lot easier as the index file took care of everything and the static associate({ PersonalDetail }) that is in the model itself already requires your models in one place all you need to do is deconstruct them, so no need to require anything at the top of the file.
This youtube video really helped me out... https://www.youtube.com/watch?v=3qlnR9hK-lQ
'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({ PersonalDetail }) {
// define association here
this.hasMany(PersonalDetail, {
foreignKey: 'userId',
//as: 'personalDetails',
})
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
moredata below: {
type: DataTypes.STRING,
allowNull: false,
},
//createdAt/updatedAt is defined in migration and updated automatically
},
{
sequelize,
tableName: 'users',
modelName: 'User',
}
)
return User
}

I got the same type issue. All mappings were done perfectly as explained in the document.
Yet, I received the issue regarding the association.
Reason is given by Dorian in this forum.
https://stackoverflow.com/a/60760296/16790144
My approach:
models/company.js
const company = sequelize.define("company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default company;
models/client.js
const Client = sequelize.define("client", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default Client;
models/clientCompany.js
const clientCompany = sequelize.define("client_company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyId: {
type: DataTypes.INTEGER
},
clientId: {
type: DataTypes.INTEGER
}
});
export default clientCompany;
models/index.js
import Company from './company';
import Client from './client';
import ClientCompany from './clientCompany';
Company.belongsToMany(Client, { through : ClientCompany });
Client.belongsToMany(Company, { through : ClientCompany });
export {
Company,
Client,
ClientCompany,
};
handler.js
This file contains the business logic.
import { Client, Company } from '../../models';
const company = await Company.findOne({
where: { id: companyId },
include: Client,
});

Related

how to write migrations to add foreignkey to already existing tables in sequelize

I have this already created two tables called User and Profile.
This is how my model for User looks like..
const Sequelize = require("sequelize");
const db = require("../db");
const User = db.define("User", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: Sequelize.STRING,
allowNull: true,
},
email: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
validator: {
isEmail: true,
},
},
});
module.exports = User;
and model for Profile looks like..
const Sequelize = require("sequelize");
const User = require("./User");
const db = require("../db");
const Profile = db.define("Profile", {
image: {
type: Sequelize.STRING,
},
description: {
type: Sequelize.TEXT,
},
});
module.exports = Profile;
Now I want to define a one-to-one relationship between User and Profile such that user will recieve a profileId column.
so i am defining it like this
Profile.hasOne(User, {
foreignKey: {
allowNull: false,
},
});
User.belongsTo(Profile);
Now i am not able to figure out how to write migrations for the newly added foreign key
can anyone help me please..
Thanks.
I got the answer. for someone who is confused like me here is the answer
since the User table already exists, migrations for the foreignkey will look like this
module.exports = {
async up(queryInterface, Sequelize) {
return await queryInterface.addColumn("Users", "ProfileId", {
type: Sequelize.INTEGER,
references: {
model: "Profiles",
key: "id",
},
});
},
async down(queryInterface, Sequelize) {
return await queryInterface.removeColumn("Users", "ProfileId", {
type: Sequelize.INTEGER,
references: {
model: "Profiles",
key: "id",
},
});
},
};
the Users in addColumn and removeColumn is the name of the table in which foreignkey was added.
the ProfileId is the name for foreignkey which you would have specified in hasOne.
hope this helps..

Sequelize bulk insert with associations

I'm trying to bulk insert with associations,
I have this 'Song' model which have one to many relationships with 'Genre' and 'Language' defined with the migrations CLI.
Song:
module.exports = (sequelize, DataTypes) => {
class Song extends Model {
static associate(models) {
// define association here
Song.hasMany(models["Language"])
Song.hasMany(models["Genre"])
}
};
Song.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING,
energy: {type: DataTypes.FLOAT, allowNull: false},
valence: {type: DataTypes.FLOAT, allowNull: false}
}, {
sequelize,
modelName: 'Song',
timestamps: true
});
return Song;
};
Language:
module.exports = (sequelize, DataTypes) => {
class Language extends Model {
static associate(models) {
// define association here
models["Language"].belongsTo(models["Song"])
}
};
Language.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING
}, {
sequelize,
modelName: 'Language',
indexes: [{unique: true, fields: ['name']}]
});
return Language;
};
Genre:
module.exports = (sequelize, DataTypes) => {
class Genre 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
models["Genre"].belongsTo(models["Song"])
}
};
Genre.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: DataTypes.STRING
}, {
sequelize,
modelName: 'Genre',
indexes: [{unique: true, fields: ['name']}]
});
return Genre;
};
I'm trying to bulk insert songs with languages and genres like this:
Song.bulkCreate(songs, {
include: [Genre,Language]
}).then(() => {
const result = {
status: "ok",
message: "Upload Successfully!",
}
res.json(result);
});
each song in the songs array is structured like this:
{
name: "abc",
genres: [{name: "abc"}],
languages: [{name: "English"}],
energy: 1,
valence: 1
}
I'm ending up with a full songs table but genres and languages are empty
What am I doing wrong?
Thanks.
Just in case anyone else got here from a search, starting from version 5.14
Sequelize added the option to use include option in bulkCreate as follows:
await Song.bulkCreate(songsRecordsToCreate, {
include: [Genre,Language]
})
Edit 2nd Feb 2023
As none answered above, as of v5.14.0 the include option is now available on bulkInsert.
Unfortunately bulkCreate does not support include option like create do.
You should use create in a cycle inside a transaction.
const transaction = ...
for (const song of songs) {
await Song.create(song, {
include: [Genre,Language]
}, { transaction })
}
await transaction.commit()
or you can use Promise.all to avoid using for.

SEQUELIZE: "Cannot read property 'length' of undefined"

Does anyone know how to solve this? There is something I am not seeing. To see if the controller was working, I returned the response receiving the request in json, it worked, there was no error.
The problem seems to be with the controller, but the controller...
Edit
Controller
import Post from '../models/Post';
import * as Yup from 'yup';
class PostController {
async store(req, res) {
try {
//Checks if the fields have been filled correctly
const schema = Yup.object().shape({
title: Yup.string()
.required(),
content: Yup.string()
.required()
});
if(!(await schema.isValid(req.body))) {
return res.status(400).json({ error: 'Error 1' });
}
//Abstraction of fields
const { title, content } = req.body;
const createPost = await Post.create(title, content);
if(!createPost) {
return res.json({error: 'Error 2'})
}
//If everything is correct, the information will be registered and returned.
return res.json({
title,
content,
});
}
catch (err) {
console.log("Error: " + err);
return res.status(400).json({error: 'Error 3'});
}
}
}
export default new PostController();
Model:
import Sequelize, { Model } from 'sequelize';
class Post extends Model {
static init(sequelize) {
//Fields registered by the user
super.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true
},
title: Sequelize.STRING,
content: Sequelize.TEXT,
created_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
},
updated_at: {
type: Sequelize.DATE,
defaultValue: Sequelize.NOW,
},
},
{
sequelize,
tableName: 'posts'
});
return this;
}
}
export default Post;
Migration:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('posts', {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
content: {
type: Sequelize.TEXT,
allowNull: false,
},
created_at: {
type: Sequelize.DATE,
allowNull: false,
},
updated_at: {
type: Sequelize.DATE,
allowNull: false,
}
});
},
down: (queryInterface) => {
return queryInterface.dropTable('posts');
}
};
The terminal error:
TypeError: Cannot read property 'length' of undefined
You need to define all columns that you put on migration inside your model. As you put allowNull: false, the database needs the column info.
I believe that you can solve adding the columns that you declared on your migration inside your Model and, if you don't wanna to declare those columns on your Controller, add the defaultValue property on those columns.
This will allow to sequelize to insert the appropriate data on those columns. As example: defaultValue: Sequelize.NOW on created_at column.
Another thing that you need to put is the table name, like this (this is one Model that I use inside one of my projects:
static init(sequelize) {
super.init(
{
categoryId: {
type: Sequelize.INTEGER,
primaryKey: true,
field: 'id',
},
dateUpdated: {
type: Sequelize.DATE,
field: 'updated_at',
defaultValue: Sequelize.NOW,
},
// ... other fields here,
},
{
sequelize,
tableName: 'categories',
}
);
}
// ...
}
export default Category;
And, therefore, try to import as Instance, not as a class.
EDIT 1:
Other thing. The error that are in your answer is on line 140 of file lib/model.js (see here on github on the master repo of sequelize).
Looking at this, try to declare your primary key on model if if you didn't yet.
EDIT 2:
In your controller, try this (according to docs):
await Post.create({ title, content });
Try to pass an json object with info that you want to store and not as parameters.
EDIT 3:
You need to import database.js before call the controllers, I had problems in this point (follow a database.js that is working):
// Imports here
import Category from '../app/models/Category';
//...
const models = [
// Models that I want to use
Category,
//...
];
class Database {
constructor() {
this.init();
}
// Load on database init
init() {
this.connection = new Sequelize(databaseConfig);
models.forEach(model => model.init(this.connection));
}
}
export default new Database();
I was having the same problem
The problem with my code was that I was forgetting to add the module name to my database's index.js file inside my models array
eg: const **models** = [User,**Task**]
I found the answer on this post:
https://github.com/sequelize/sequelize/issues/11111#issuecomment-697078832
I had the same problem that Naruto Uzumaki. I forgot to put the connection with the model on the file that initialize the models.

Foreign field reference in Sequelize model files

I am writing a lead capture app using nodejs, express and sequelize. I have a Lead object, but I want to introduce a new table LeadSource to the schema, with Lead having a FK to LeadSource
/path/to/model/Lead.js
'use strict';
module.exports = (sequelize, DataTypes) => {
var Lead = sequelize.define('Lead', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false,
primaryKey: true
},
first_name: {
type: DataTypes.STRING,
},
last_name: {
type: DataTypes.STRING,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
});
return Lead;
};
This is the schema for LeadSource
LeadSource = sequelize.define('LeadSource', {
id: { type: Sequelize.INTEGER },
name: { type: Sequelize.STRING }
});
My questions are:
How do I reference LeadSource as FK in the Lead.js file?
I like to keep each model in a seperate file - can I have this separation and still have LeadSource reference by a FK in Lead?
Yes, you can define the relationships between the models while keeping each one in a separate file.
Here is how you can define associations between Lead and LeadSource in Lead.js file
module.exports = (sequelize, DataTypes) => {
var Lead = sequelize.define('Lead', {
// ...
})
Lead.associate = function (models) {
// associations can be defined here
//One-To-One relationship with foreign key added to Lead
Lead.belongsTo(models.LeadSource, {
foreignKey: { name: 'leadSourceId' }
})
//One-To-Many relationship with foreign key added to Lead
models.LeadSource.hasMany(Lead, {
foreignKey: { name: 'leadSourceId' }
})
}
return Lead;
}

Problem setting up Sequelize association - query with 'include' is failing

I'm new to Sequelize and trying to test if an n:m association I set up between two models, User and Podcast, is working. When I try to run this query, I get some kind of DB error that isn't specific about what's wrong:
User.findOne({
where: { id: id },
include: [{ model: Podcast }]
});
Does anyone know what I'm messing up? I suspect there's something wrong in how I've set up the association, like I'm referencing the names of tables slightly incorrectly, but the migration to create the association worked.
Here's my User.js model file:
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
photo: {
type: DataTypes.STRING
}
});
User.associate = function(models) {
// associations can be defined here
User.belongsToMany(models.Podcast, {
through: 'user_podcast'
});
};
return User;
};
And here's my Podcast.js file:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Podcast = sequelize.define('Podcast', {
id: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false
},
title: {
type: DataTypes.STRING,
allowNull: false
},
thumbnail: {
type: DataTypes.STRING
},
website: {
type: DataTypes.STRING
}
});
Podcast.associate = function(models) {
// associations can be defined here
Podcast.belongsToMany(models.User, {
through: 'user_podcast'
});
};
return Podcast;
};
And here's the migration I ran to join the two tables:
'use strict';
module.exports = {
up: function(queryInterface, Sequelize) {
return queryInterface.createTable('user_podcast', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id'
}
},
podcastId: {
type: Sequelize.STRING,
references: {
model: 'Podcasts',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: function(queryInterface, Sequelize) {
return queryInterface.dropTable('user_podcast');
}
};
And here's the project on Github for further reference:
https://github.com/olliebeannn/chatterpod
You don't need to create a migration for the M:N table. Now you have something wrong on your user_podcast model. If you are setting a M:N relation between to tables your primary key will be the combination between the foreign key from these two models. If you still want a single id primary key for your table, then you won't use belongsToMany instead use hasMany on user and podcast models pointing to a new model user_podcast.
As far as I see on your first query, it seems that you really need a M:N relation so you can define the model as you do with user and podcast like this:
module.exports = (sequelize, DataTypes) => {
const UserPodcast = sequelize.define('user_podcast', {
userId: {
// field: 'user_id', #Use 'field' attribute is you have to match a different format name on the db
type: DataTypes.INTEGER
},
podcastId: {
// field: 'podcast_id',
type: DataTypes.INTEGER
},
});
UserPodcast.associate = function(models) {
models.User.belongsToMany(models.Podcast, {
as: 'podcasts', //this is very important
through: { model: UserPodcast },
// foreignKey: 'user_id'
});
models.Podcast.belongsToMany(models.User, {
as: 'users',
through: { model: UserPodcast },
// foreignKey: 'podcast_id'
});
};
return UserPodcast;
};
I do prefer to have the belongsToMany associations on the save function where I define the join model, and you have to notice that I used as: attribute on the association. This is very important because this will help sequelize to know which association are you referring on the query.
User.findOne({
where: { id: id },
include: [{
model: Podcast,
as: 'podcasts' //here I use the previous alias
}]
});

Resources