Sequelize — how to get associated model? - javascript

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.

Related

How to include related models in Sequelize?

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
}
]
});

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 to use leftJoin on sequelize findAll method

How would i be able to use sequelize findAll to check if each post has been liked by the current user.
the where query filters through the posts array objects. It needs to check if the current user liked a post, but still get all data.
Would i need to do a left join ? where userId === req.session.user.id
Get all posts and check if user liked each post
I'm using sequelize, and postgres. Its pretty much just sql .
How should i go about achieving this using sequelize
what the posts structure looks like
Take for example this code, this is laravel app i made, i want the same implementation in sequelize
php(Example)
public function getPosts( )
{
$posts = Post::with('user')
->with(['likes' => function ($query) {
$query->whereNull('deleted_at');
$query->where('user_id', auth()->user()->id);
}])
->with(['comments' => function($query) {
$query->with('user');
}])->orderBy('created_at', 'desc')
->get();
$data = $posts->map(function(Post $post)
{
$user = auth()->user();
if($user->can('delete', $post)) {
$post['deletable'] = true;
}
if($user->can('update', $post)) {
$post['update'] = true;
}
// $comment = new Comment();
$post['likedByMe'] = $post->likes->count() == 0 ? false : true;
$post['likesCount'] = Like::where('post_id', $post->id)->get()->count();
$post['createdAt'] = $post->created_at->diffForHumans();
$post['createdAt'] = $post->updated_at->diffForHumans();
return $post;
});
return response()->json($data);
}
post.controller.js
getPosts: async (req: any, res: Response) => {
// use async/await here
const posts = await models.Post.findAll({
include: [
{ model: models.User, as: "author", attributes: ["username"] },
// limit the likes based on the logged in user
{
model: models.Likes
}
],
order: [["createdAt", "DESC"]],
limit: 6
});
// i dont think this is the right approach.
posts.forEach(post => {
post.Likes.forEach(like => {
console.log(like.userId);
if (like.userId === req.session.user.id) {
post.setDataValue("likedByMe", true);
} else {
post.setDataValue("likedByMe", false);
}
});
});
return res.json(posts);
post.js
"use strict";
module.exports = (sequelize, DataTypes) => {
var Post = sequelize.define("Post", {
title: DataTypes.STRING,
postContent: DataTypes.STRING,
liked: {
type: DataTypes.VIRTUAL,
allowNull: false,
defaultValue: false,
get: function () {
return this.getDataValue('Likes').length ? true : false;
}
},
likeCounts: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
}
},
authorId: DataTypes.INTEGER
}, {});
Post.associate = function (models) {
Post.belongsTo(models.User, {
as: "author",
foreignKey: "authorId",
onDelete: "CASCADE"
});
Post.hasMany(models.Likes, {
foreignKey: "resourceId",
timestamps: false,
targetKey: "id",
onDelete: "CASCADE"
});
};
return Post;
};
i think this forEach statement helped identifying if the current user has liked the post or not.
getPosts: async (req: any, res: Response) => {
// use async/await here
const posts = await models.Post.findAll({
include: [
{ model: models.User, as: "author", attributes: ["username"] },
// limit the likes based on the logged in user
{
model: models.Likes
}
],
order: [["createdAt", "DESC"]],
limit: 6
});
posts.forEach(post => {
if (post.Likes.length === 0) {
post.setDataValue("likedByMe", false);
}
post.Likes.forEach(like => {
console.log(like.userId);
if (like.userId === req.session.user.id) {
post.setDataValue("likedByMe", true);
} else {
post.setDataValue("likedByMe", false);
}
});
});
return res.json(posts);
},

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 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