How to include related models in Sequelize? - javascript

I am trying to retrieve all transactions and include all related stripePayments but get the error include.model.getTableName is not a function.
I have a transactions table that has many stripePayments. This can seen with the following sequelize models:
src/models/transaction.js
module.exports = (sequelize, DataTypes) => {
class Transaction extends Model {
static associate(models) {
this.hasOne(models.StripePayment, {
as: 'StripePayment',
foreignKey: 'stripePaymentId'
});
}
}
Transaction.init(
{
id:{
allowNull: false,
primaryKey: true,
type: DataTypes.UUID
},
stripePaymentId: {
type: DataTypes.UUID,
allowNull: false,
foreignKey: true,
references: {
model: stripePayment,
key: 'id',
},
}
},
{
sequelize,
modelName: 'Transaction',
}
);
return Transaction;
};
src/models/stripePayment.js
module.exports = (sequelize, DataTypes) => {
class StripePayment extends Model {
static associate(models) {
this.belongsTo(models.Transaction, {
as: 'Transaction',
foreignKey: 'stripePaymentId'
});
}
}
StripePayment.init(
{
id:{
allowNull: false,
primaryKey: true,
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4
}
},
{
sequelize,
modelName: 'StripePayment',
}
);
return StripePayment;
};
I then have an index file that imports all the models and calls the association method like so:
src/models/index.js
const db = fs
.readdirSync(__dirname)
.filter(isModelFile)
.reduce((acc, file) => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
acc[model.name] = model;
return acc;
}, {});
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
console.log(db[modelName], db[modelName].association())
});
The console log outputs the following:
StripePayment, { Transaction: Transaction }
Transaction, { StripePayment: StripePayment}
which I assume means the association has worked?
However, when running a query like:
await models.Transaction.findAll({
where: { id: "my_id" },
include: [
{
model: 'StripePayment',
as: 'stripePayments'
}
]
});
is when I get the error include.model.getTableName is not a function.
What is causing this to fail?
Have I defined the relationships correctly in the models?

Turns out the issue was with my query. I needed to specify the model instance, not the name of the model as a string:
I.e. I needed to do this:
await models.Transaction.findAll({
where: { id: "my_id" },
include: [
{
model: models.StripePayment,
as: "StripePayment", // match the alias specified in the model
attributes: ['id', 'status'], // need to select attributes
}
]
});

Related

How to solve EagerLoadingError [SequelizeEagerLoadingError]: Table_B is not associated to Table_A?

I am trying to fetch data from Table_A and Table_B using node and sequelize
Table Structure
Table_A:
id PK
name Text
Table_B:
id PK
a_id FK_tableA_id
name Text
Model
TableA.js
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = (sequelize) => {
const Table_A = sequelize.define('Table_A', {
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.TEXT,
allowNull: true
}
});
Table_A.associate = models => {
Table_A.belongsTo(models.Table_B, { as: 'tb' });
}
return Table_A;
};
TableB.js
'use strict';
const DataTypes = require('sequelize').DataTypes;
module.exports = (sequelize) => {
const Table_B = sequelize.define('Table_B', {
id: {
type: DataTypes.UUID,
allowNull: false,
primaryKey: true
},
a_id: {
type: DataTypes.UUID,
allowNull: false,
defaultValue: null
},
name: {
type: DataTypes.TEXT,
allowNull: true
}
});
return Table_B;
};
I am getting below error while I am trying to run the query using sequelize, Can you please guide me where I am making the mistake?
Error
EagerLoadingError [SequelizeEagerLoadingError]: Table_B is not associated to Table_A!
at Function._getIncludedAssociation (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:545:13)
at Function._validateIncludedElement (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:482:53)
at C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:401:37
at Array.map (<anonymous>)
at Function._validateIncludedElements (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:397:39)
at Function.aggregate (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1204:12)
at Function.count (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1252:31)
at async Promise.all (index 0)
at async Function.findAndCountAll (C:\Project\test\FilterTest\node_modules\sequelize\dist\lib\model.js:1268:27)
index.js
'use strict';
const { Op } = require('sequelize');
const {sequelize, connect } = require('./db');
const uninitModels = require('./models');
let initModels = uninitModels(sequelize);
initModels = { connection: sequelize, ...initModels }
const {
Table_A, Table_B
} = initModels;
function dbCall(final) {
Table_A.findAndCountAll(final).then((result)=>{
console.log(result)
}).catch((err)=>{
console.log(err)
})
}
function data() {
let final = {
include: [
{
model: Table_B,
attributes: ['id', 'name', 'a_id'],
as: 'tb'
}
]
}
dbCall(final);
}
data();
I suppose you didn't register associations that should be registered by calling associate methods of models.
Also you confused how models are linked. If a model1 has a foreign key field pointing to a model2 then an association should be model1.belongsTo(model2).
In your case it should be:
Table_A.associate = models => {
Table_A.hasMany(models.Table_B, { as: 'tb', foreginKey: 'a_id' });
}
and in the model Table_B:
Table_B.associate = models => {
Table_B.belongsTo(models.Table_A, { as: 'ta', foreginKey: 'a_id' });
}
Pay attention to foreignKey option, you need to indicate it explicitly because your foreign key field is named other than Table_A+id.
I got stuck on this same error (for what seemed like an eternity) and finally realized that there was a big flaw in the way I was declaring my associations.
Incorrect:
Account.associate = function (models) {
Account.hasMany(models.History, {
onDelete: "cascade"
});
};
Account.associate = function (models) {
Account.hasMany(models.User, {
onDelete: "cascade"
});
};
In hindsight, this was a really silly oversight doing two declarations here. tl;dr the 2nd declaration was canceling out the 1st one.
Correct:
Account.associate = function (models) {
Account.hasMany(models.History, {
onDelete: "cascade"
});
Account.hasMany(models.User, {
onDelete: "cascade"
});
};
One declaration with multiple function calls for the win.

How do I set a foreign key for a Sequelize model?

This is very basic, and should work.. but doesn't. So first my models:
const Conversation = sequelize.define('Conversation', {
name: {
type: DataTypes.STRING,
allowNull: false
},
...
})
Conversation.associate = (models, options) => {
Conversation.hasOne(models.Audio, options)
}
and:
module.exports = (sequelize /*: sequelize */ , DataTypes /*: DataTypes */ ) => {
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
})
Audio.associate = (models, options) => {
Audio.belongsTo(models.Conversation, options)
}
I have a model loader that does:
fs.readdirSync(`${__dirname}`)
.filter((modelFile) => {
return path.extname(modelFile) === '.js' && modelFile !== 'index.js'
})
.map((modelFile) => {
let model = sequelize.import(`./${modelFile}`)
models[model.name] = model
return model
})
.filter((model) => models[model.name].associate)
.forEach((model) => {
models[model.name].associate(models, {
hooks: true,
onDelete: 'CASCADE'
});
})
So it calls the associate method for all models that have it defined. This works, in that, when I .sync it, it creates a ConversationId field in my Conversations table.
When I try to execute:
let updAudio = {
ConversationId,
name: 'myname'
}
await global.db.models.Audio.create(updAudio, { logging: console.log })
ConversationId is not null, but when it saves in the DB, it's null. I've inspected it a hundred times. The raw query looks like:
INSERT INTO "Audios" ("id","name","createdAt","updatedAt") VALUES (DEFAULT,'myname','2019-10-20 19:59:18.139 +00:00','2019-10-20 19:59:18.139 +00:00') RETURNING *;
So what happened to ConversationId?
You might need to add the foreign key in the model definition:
(One of the two may work)
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
conversationId : {
type: Sequelize.INTEGER,
references: 'Conversations' // or "conversations"? This is a table name
referencesKey: 'id' // the PK column name
}
})
or
const Audio = sequelize.define("Audio", {
name: {
type: DataTypes.STRING,
unique: true,
allowNull: true
},
conversationId : {
type: Sequelize.INTEGER,
references: {
model: Conversation
key: 'id'
}
}
})
You might also need to define your belongsTo association this way:
Audio.belongsTo(Conversation, foreignKey: 'conversationId');
Try to look for the generated raw query on creation of the tables and start from there.

Sequelize Association throws an error in Create

I'm trying to do a simple association with sequelize in my NodeJS API, the idea is simple, I want to create a person and his type in the same moment. I try to follow the docs, but, when the create function is called Sequelize throws the error
"TypeError: Cannot read property 'getTableName' of undefined"
Bellow is the code used:
Models
Person.js
import Sequelize from 'sequelize';
export default (sequelize) => {
const Person = sequelize.define('person', {
email: { type: Sequelize.STRING },
nickname: { type: Sequelize.STRING(60) },
fullname: { type: Sequelize.STRING(60) },
observation: { type: Sequelize.TEXT },
}, { underscored: true, freezeTableName: true });
Person.associate = (models) => {
Person.hasOne(models.PersonType, {
foreignKey: 'person_id',
});
};
return Person;
};
PersonType.js
import Sequelize from 'sequelize';
export default (sequelize, DataTypes) => {
const PersonType = sequelize.define('person_type', {
type: { type: Sequelize.STRING(14), unique: true },
}, { underscored: true, freezeTableName: true });
PersonType.associate = (models) => {
PersonType.belongsTo(models.Person, {
foreignKey: 'person_id',
});
};
return PersonType;
};
index.js
import Sequelize from 'sequelize';
import console from 'console';
const database = process.env.DB_URL;
const sequelize = new Sequelize(database, {
dialect: 'postgres',
underscored: true,
});
const models = {
Person: sequelize.import('./person'),
PersonType: sequelize.import('./personType'),
};
Object.keys(models).forEach((modelName) => {
if ('associate' in models[modelName]) {
models[modelName].associate(models);
}
});
models.sequelize = sequelize;
models.Sequelize = Sequelize;
export default models;
The file where I try to execute the operation:
import models from '../models';
export default class PersonOperations {
constructor(db) {
this.db = db; // db here is the sequelize model of Person
}
create(person) {
return this.db.create({
email: person.email,
nickname: person.nickname,
observation: person.observation,
personType: {
type: person.personType.type,
},
}, {
include: [{
model: models.PersonType,
include: [person.personType],
}],
});
}
The error:
TypeError: Cannot read property 'getTableName' of undefined
at Function._validateIncludedElement (/home/~/node_modules/sequelize/lib/model.js:465:30)
at options.include.options.include.map.include (/home/~/node_modules/sequelize/lib/model.js:395:37)
at Array.map (<anonymous>)
at Function._validateIncludedElements (/home/~/node_modules/sequelize/lib/model.js:390:39)
I have already tried a lot of different things that I have found, but none of them helped me.
I am using freezeTableName, I have already tried to remove that, drop the database and create again, but the problem still persists.
Just Comment out the below code block from Person.js and run the code
Person.associate = (models) => {
Person.hasOne(models.PersonType, {
foreignKey: 'person_id',
});
};
Reason of error :
Here you are creating the circular dependecies , using
models.PersonType inside models.Person and then after using
models.Person inside models.PersonType
hasOne and belongsTo both are the same thing only diff is (READ) :
Player.belongsTo(Team) // `teamId` will be added on Player / Source model
Coach.hasOne(Team) // `coachId` will be added on Team / Target model

Sequelize — how to get associated model?

I have two associated models (abbreviated for clarity)
const model = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
fullName: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
len: [1,255]
}
}
},
{
classMethods: {
associate: function(models) {
User.hasMany(models.Meeting, {as: 'chairedMeetings', foreignKey: 'chairmanId'})
}
}
}
)
return User
}
and
const model = (sequelize, DataTypes) => {
const Meeting = sequelize.define('Meeting', {
title: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true,
len: [1,255]
}
}
}, {
classMethods: {
associate: function(models) {
Meeting.belongsTo(models.User, {as: 'chairman'})
}
}
}
)
return Meeting
}
module.exports = model
I create a new meeting, given an previously created user with id = 1, as follows:
const data = {
title: 'Some amazing meeting title',
chairmanId: 1
}
Meeting.create(data, {include: [{all: true, nested: true}]}).then(meeting => {
const chairmanId = meeting.get('chairmanId') // ===> this is 1 as expected
const chairman = meeting.get('chairman') // ===> but this is undefined
}
How do I get sequelize to return the associated record without manually having to go through the associations and findOne them by id?
Okay figured it out:
The solution is to do a reload after creation.
Meeting.create(data, {include: [{all: true, nested: true}]}).then(meeting => {
const chairmanId = meeting.get('chairmanId') // ===> this is 1 as expected
meeting.reload({include: [{all: true, nested: true}]}).then(reloadedMeeting => {
const chairman = reloadedMeeting.get('chairman') // ===> is a user!
})
}
I guess sequelize doesn't automatically reload after creation in case you never actually care about the model you just created, and you just want to know it got saved.

Sequelize Access parent table's field on where clause

I have Ad, Banner, Redirect and Script models. Ad is the parent table.
Ad model
import UID from '../util/uid';
export default (sequelize, DataTypes) => {
const Ad = sequelize.define('Ad',
{
uid: {
type: DataTypes.STRING(40),
allowNull: false,
unique: true
},
name: {
type: DataTypes.STRING(15),
allowNull: false
},
type: {
type: DataTypes.STRING(15),
allowNull: false
}
});
Ad.hook('beforeValidate', (ad, options) => ad.uid = UID.generate());
return Ad;
};
Banner model
export default (sequelize, DataTypes) => {
const Banner = sequelize.define('Banner',
{
outcome: {
type: DataTypes.STRING(120),
allowNull: false
},
image: {
type: DataTypes.STRING(120),
allowNull: false
}
},
{
classMethods: {
associate: (models) => {
Banner.belongsTo(models.Ad);
Banner.hasMany(models.Format);
}
}
});
return Banner;
};
So, I when I search a Banner I need to access to Ad.uid in order to retrieve all data: https://myapp.doamin.com/ads/uid/dh49mmx02?type=banner.
Ad API
router.get('/:uid', (request, response) => {
const adType = request.query.type;
let service = null;
switch(adType) {
case 'banner':
service = bannerService; break;
...
}
service.findOne(request.params.uid, (ad) => {
if(!ad) {
response.json({eror: true, message: 'Ad not found'});
} else {
response.json(ad);
}
});
});
Banner service
export default class BannerService {
find(qty, cb) {
Banner.findAll({offset: qty, limit: 10})
.then((banners) => cb(banners));
}
findOne(uid, cb) {
Banner.find({where: {'Ad.uid': uid}, include: [Ad, Format]})
.then((banner) => cb(banner));
}
...
}
When I try to get a banner, this error is thrown: Banner.Ad.uid column not exist. I also tried with $Ad.uid$ but it throws An entry for table Ad is missing in from clause.
Which is the right way to do this?
Firstly i believe you should also specify the association from Ad to Banner by adding an associate function with Ad.hasMany(models.Banner);.
As for the query, Sequelize actually allows you to add a where statement inside your include
findOne(uid, cb) {
Banner.find({include: [
{model: Ad, where: {uid: uid}},
Format
]}).then((banner) => cb(banner));
}

Categories