Sequelize findAll with association and foreign key - mysql

I'm using sequelize to model a mySql-database schema in my node-application. Let's say I have 3 table: Project, User and Role.
It's a "Many to Many" association between Project and User through "Project_User" where is defined the role of a user for a project.
Project Model :
var Project = sequelize.define('Project', {
name:{type: DataTypes.STRING, unique: true}
},
classMethods: {
associate: function(models) {
Project.belongsToMany(models.User, { through: 'Project_User', as: 'users'});
}
}
// Methods...
);
User Model :
var User= sequelize.define('User', {
name:{type: DataTypes.STRING, unique: true}
},
classMethods: {
associate: function(models) {
User.belongsToMany(models.Project, { through: 'Project_User', as: 'projects'});
}
}
// Methods...
);
And here is the association table Project_User Model :
var Project_User = sequelize.define('Project_User', {
role:
{
type: DataTypes.INTEGER,
allowNull: false,
references: 'Role',
referencesKey: 'id'
}
},{
classMethods: {
associate: function(models) {
Project_User.belongsTo(models.Role, {foreignKey: 'role'});
}
}
});
Now, I want to find all project, with their users and their role. So I've used findAll with the "include" parameters like below:
models.Projects.findAll({
include:[
{
model: models.User,
as:'users',
through: {attributes: ['role'], as: 'role'}
}]
}).then(function(result) {
// ...
});
This works great but I only have the roleId associated to the user. I wasn't be able to link this "roleId" with the role table to get the other attributes like role name...
Here is the JSON I've got :
[
{
"id": 1,
"name": "Project name",
"users": [
{
"id": 1,
"name": "User name",
"role": {
"role": 1
}
}
]
}
]
But I would like to have something like that :
[
{
"id": 1,
"name": "Project name",
"users": [
{
"id": 1,
"name": "User name",
"role": {
"id": 1,
"name": "Role name",
"description": "Some info...",
}
}
]
}
]
I've tried many things to realize this association, even successive includes but it was unsuccessful. What is needed in the findAll options to get this JSON result ?
Thanks

Assuming that your User model is linked to the Role model, something like this should work:
models.Projects.findAll({
include:[
{
model: models.User,
as:'users',
through: {attributes: []},
include: [models.Role]
}] }).then(function(result) {
// ... });

Related

ExtJS model associations with jsonapi specification

We are creating a new version our API (v2) adopting the JSON:API specification (https://jsonapi.org/). I'm not being able to port the ExtJS model associations (belongs_to) to the new pattern.
The ExtJS documentation only shows how to use a nested relation in the same root node (https://docs.sencha.com/extjs/4.2.2/#!/api/Ext.data.association.Association).
v1 data (sample):
{
"data": [
{
"id": 1,
"description": "Software Development",
"area_id": 1,
"area": {
"id": 1,
"code": "01",
"description": "Headquarters"
}
},
],
"meta": {
"success": true,
"count": 1
}
}
v2 data (sample):
{
"data": [
{
"id": "1",
"type": "maint_service_nature",
"attributes": {
"id": 1,
"description": "Software Development",
"area_id": 1
},
"relationships": {
"area": {
"data": {
"id": "1",
"type": "area"
}
}
}
}
],
"included": [
{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"code": "01",
"description": "Headquarters"
}
}
],
"meta": {
"success": true,
"count": 1
}
}
My model:
Ext.define('Suite.model.MaintServiceNature', {
extend: 'Ext.data.Model',
fields: [
{ desc: "Id", name: 'id', type: 'int', useNull: true },
{ desc: "Area", name: 'area_id', type: 'int', useNull: true },
{ desc: "Description", name: 'description', type: 'string', useNull: true, tableIdentification: true }
],
associations: [
{
type: 'belongsTo',
model: 'Suite.model.Area',
foreignKey: 'area_id',
associationKey: 'area',
instanceName: 'Area',
getterName: 'getArea',
setterName: 'setArea',
reader: {
type: 'json',
root: false
}
}
],
proxy: {
type: 'rest',
url: App.getConf('restBaseUrlV2') + '/maint_service_natures',
reader: {
type: 'json',
root: 'data',
record: 'attributes',
totalProperty: 'meta.count',
successProperty: 'meta.success',
messageProperty: 'meta.errors'
}
}
});
Any ideias on how to setup the association to work with the v2 data?
I'm honestly taking a stab at this one... I haven't used Ext JS 4 in years, and I wouldn't structure my JSON like JSON:API does, but I think the only way you can accomplish this is by rolling your own reader class. Given that you have generic properties for your data structure, this reader should work for all scenarios... although, I'm not too familiar with JSON:API, so I could be totally wrong. Either way, this is what I've come up with.
Ext.application({
name: 'Fiddle',
launch: function () {
Ext.define('MyReader', {
extend: 'Ext.data.reader.Json',
alias: 'reader.myReader',
root: 'data',
totalProperty: 'meta.count',
successProperty: 'meta.success',
messageProperty: 'meta.errors',
/**
* #override
*/
extractData: function (root) {
var me = this,
ModelClass = me.model,
length = root.length,
records = new Array(length),
dataConverter,
convertedValues, node, record, i;
for (i = 0; i < length; i++) {
node = root[i];
var attrs = node.attributes;
if (node.isModel) {
// If we're given a model instance in the data, just push it on
// without doing any conversion
records[i] = node;
} else {
// Create a record with an empty data object.
// Populate that data object by extracting and converting field values from raw data.
// Must pass the ID to use because we pass no data for the constructor to pluck an ID from
records[i] = record = new ModelClass(undefined, me.getId(attrs), attrs, convertedValues = {});
// If the server did not include an id in the response data, the Model constructor will mark the record as phantom.
// We need to set phantom to false here because records created from a server response using a reader by definition are not phantom records.
record.phantom = false;
// Use generated function to extract all fields at once
me.convertRecordData(convertedValues, attrs, record, me.applyDefaults);
if (me.implicitIncludes && record.associations.length) {
me.readAssociated(record, node);
}
}
}
return records;
}
});
Ext.define('Suite.model.Area', {
extend: 'Ext.data.Model',
fields: [{
name: 'type',
type: 'string'
}]
});
Ext.define('Suite.model.MaintServiceNature', {
extend: 'Ext.data.Model',
fields: [{
desc: "Id",
name: 'id',
type: 'int',
useNull: true
}, {
desc: "Area",
name: 'area_id',
type: 'int',
useNull: true
}, {
desc: "Description",
name: 'description',
type: 'string',
useNull: true,
tableIdentification: true
}],
associations: [{
type: 'belongsTo',
model: 'Suite.model.Area',
associatedName: 'Area',
foreignKey: 'area_id',
associationKey: 'relationships.area.data',
instanceName: 'Area',
getterName: 'getArea',
setterName: 'setArea'
}],
proxy: {
type: 'rest',
url: 'data1.json',
reader: {
type: 'myReader'
}
}
});
Suite.model.MaintServiceNature.load(null, {
callback: function (record) {
console.log(record.getData(true));
}
});
}
});

One to many relationship in sequelize with MYSQL

I have two tables:
const attr = {
name: {
type: DataTypes.STRING,
},
};
const Tags = createModel('Tags', attr, {});
and:
const attr = {
tagId: {
type: DataTypes.INTEGER,
references: { model: 'Tags', key: 'id' },
}
}
const Client = createModel('Client', attr, {})
Client.belongsTo(Tag, { foreignKey: 'tagId', as: 'tags' });
and my query is this:
const clientCount = await Client.findAll({
include: [ { model: Tags, as: 'tags' } ],
attributes: { exclude: 'tagId' }
});
and this is my response:
{
"id": 1,
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-22T00:00:00.000Z",
"tags": {
"id": 1,
"name": "New tag",
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-20T00:00:00.000Z"
}
}
but I want my tags to be an array, so I guest I have to define a one to many association, but everything I tried so far failed.
What I want is tags to be an array, where I can add multiple tag objects:
{
"id": 1,
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-22T00:00:00.000Z",
"tags": [
{
"id": 1,
"name": "New tag",
"createdAt": "2020-01-20T00:00:00.000Z",
"updatedAt": "2020-01-20T00:00:00.000Z"
}
]
}
Method1
We need new model as Client_Tag
const attr = {
clientId: {
type: DataTypes.INTEGER,
},
tagId: {
type: DataTypes.INTEGER,
},
};
const Client_Tag = createModel('Client_Tag', attr, {});
Client.belongsToMany(Tag, {
foreignKey: 'clientId',
otherKey: 'tagId',
through: models.Client_Tag,
as: 'tags'
});
const clientCount = await Client.findAll({
include: [ { model: Tags, as: 'tags' } ],
attributes: { exclude: 'tagId' }
});
Method2
const attr = {
name: {
type: DataTypes.STRING,
},
clientId: { // need clientId in tag model, and remove 'tagId' from client model
type: DataTypes.INTEGER,
}
};
const Tags = createModel('Tags', attr, {});
Client.belongsToMany(Tag, { foreignKey: 'tagId', as: 'tags' });

Sequelize populate joined table attributes

I have a few tables and I want to do some includes on a joined table, but I can't seem to figure it out. Here are my models:
/* Staff model */
const model = {
fisrName: {
type: DataTypes.INTEGER,
references: { model: 'Roles', key: 'id' },
},
lastName: {
type: DataTypes.INTEGER,
references: { model: 'Profiles', key: 'id' },
}
};
const Staff = createModel('Staff', model, { paranoid: true });
export default Staff
/* Service model */
const model = {
name: {
type: DataTypes.STRING,
},
category: {
type: DataTypes.STRING,
},
description: {
type: DataTypes.STRING,
}
};
const Service = createModel('Service', model, {});
export default Service;
/* Appointment model */
const model = {
endDate: {
type: DataTypes.DATE,
},
startDate: {
type: DataTypes.DATE,
},
day: {
type: DataTypes.DATE,
},
};
const Appointment = createModel('Appointment', model, {})
Appointment.belongsToMany(Service, { through: 'Products', as: 'products' });
export default Appointment;
/* Products model */
const model = {
serviceId: {
type: DataTypes.INTEGER,
},
appointmentId: {
type: DataTypes.INTEGER,
},
staffId: {
type: DataTypes.INTEGER,
references: { model: 'Staff', key: 'id' },
}
};
const Product = createModel('Product', model, {});
Product.belongsTo(Staff, { foreignKey: 'staffId', as: 'staff' });
export default Product;
This is my appointment query, where I include the services array, and on this services array, I have a Products object, and in this object I have a staffId that I want to populate, and I'm not sure how. I have tried different ways, but nothing worked.
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: Service,
as: 'services',
through: { attributes: { include: ['id', 'staffId', 'serviceId', 'appointmentId'], exclude: ['createdAt', 'updatedAt', 'AppointmentId', 'ServiceId'] },
},
],
});
And this is my response:
{
"startDate": "date",
"endDate": "date",
"day": "date",
"services": [{
"id": 1,
"name": "Service name",
"category": "service category",
"description": "service description",
"Products": {
"id": 1,
"staffId": 2,
"serviceId": 1,
"appointmentId": 1
}
}]
}
What I want is to do is to populate the staffId from Products with the model from the collection, something like this:
{
"startDate": "date",
"endDate": "date",
"day": "date",
"services": [{
"id": 1,
"name": "Service name",
"category": "service category",
"description": "service description",
"Products": {
"id": 1,
"staffId": {
"firstName": "First Name",
"lastName": "Last Name"
},
"serviceId": 1,
"appointmentId": 1
}
}]
}
const appointment = await Appointment.findByPk(req.params.id, {
include: [
{
model: Service,
as: 'services',
through: {
attributes: { include: ['id', 'staffId', 'serviceId', 'appointmentId'], exclude: ['createdAt', 'updatedAt', 'AppointmentId', 'ServiceId'] },
},
include: [{
as: 'Products', model: Product,
include: 'staff'
}]
}
]
});

How to get user information instead of just ID sequelize?

I am using sequelize with mysql,
I have 3 models
posts
Comments
users
posts model
module.exports = (sequelize, DataTypes) => {
const Post = sequelize.define('Post', {
title: DataTypes.STRING,
content: DataTypes.TEXT,
userId: DataTypes.INTEGER
}, {});
Post.associate = function(models) {
// associations can be defined here
Post.hasMany(models.Comment, {
foreignKey: 'postId',
as: 'comments',
onDelete: 'CASCADE',
})
Post.belongsTo(models.User, {
foreignKey: 'userId',
as: 'author',
onDelete: 'CASCADE',
})
};
return Post;
};
comments model
const user = require("./user");
module.exports = (sequelize, DataTypes) => {
const Comment = sequelize.define(
"Comment",
{
postId: DataTypes.INTEGER,
comment: DataTypes.TEXT,
userId: DataTypes.INTEGER,
},
{}
);
Comment.associate = function (models) {
// associations can be defined here
Comment.belongsTo(
models.User,
{
foreignKey: "userId",
as: "author",
me: "name",
},
{ name: user.name }
);
Comment.belongsTo(models.Post, {
foreignKey: "postId",
as: "post",
});
};
return Comment;
};
users model
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
name: DataTypes.STRING,
email: DataTypes.STRING,
},
{}
);
User.associate = function (models) {
// associations can be defined here
User.hasMany(models.Post, {
foreignKey: "userId",
as: "posts",
onDelete: "CASCADE",
});
User.hasMany(models.Comment, {
foreignKey: "userId",
as: "comments",
onDelete: "CASCADE",
});
};
return User;
};
and following is my response i am getting when i execute the following query
const getAllPosts = async (req, res) => {
try {
const posts = await models.Post.findAll({
include: [
{
model: models.Comment,
as: "comments"
},
{
model: models.User,
as: "author"
}
]
});
return res.status(200).json({ posts });
} catch (error) {
return res.status(500).send(error.message);
}
};
RESPONSE
"posts": [
{
"id": 1,
"title": "1st post ever on this server",
"content": "This is the content of the first post published on this type or architecture",
"userId": 1,
"createdAt": "2021-01-31T10:00:45.000Z",
"updatedAt": "2021-01-31T10:00:45.000Z",
"comments": [
{
"id": 1,
"postId": 1,
"comment": "this is the comment on first post",
"userId": 1, // Also need a key val pair of username and his email ID just instead of UserID
"createdAt": null,
"updatedAt": null
},
{
"id": 2,
"postId": 1,
"comment": "comment second",
"userId": 1,
"createdAt": "2021-01-31T15:34:27.000Z",
"updatedAt": "2021-01-31T15:34:27.000Z"
}
],
"author": {
"id": 1,
"name": "test user",
"email": "testuser#gmail.com",
"createdAt": null,
"updatedAt": null
}
}
]
}
I need the user name of commented user name and email for which i have fields in the table
but i am just getting user ID
how can i go about it,
I am very much new in sequelize, I tried but i am getting get same hasMany and benlongsTo results.
From what I see you doing, you need to run a nested include when getting the comment.
Try this modified code.
const getAllPosts = async (req, res) => {
try {
const posts = await models.Post.findAll({
include: [
{
model: models.Comment,
as: "comments",
include: [
{
model: models.User,
as: "author"
}
]
},
{
model: models.User,
as: "author"
}
]
});
return res.status(200).json({ posts });
} catch (error) {
return res.status(500).send(error.message);
}
};

Sequelize findAll and Join table

I want to do a simple chat app and my tables are like this (I don't know if it's the proper way to implement it):
var User = sequelize.define('User', {
userName: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
User.hasMany(models.Friend);
}
}
Friends:
var Friend = sequelize.define('Friend', {
realId: DataTypes.INTEGER,
invitStatus: DataTypes.STRING
}, {
classMethods: {
associate: function(models) {
Friend.hasMany(models.Message);
}
Messages:
var Message = sequelize.define('Message', {
text: DataTypes.TEXT,
sentBy: DataTypes.INTEGER
}, {
classMethods: {
associate: function(models) {
}
This is my current query:
models.User.findAll({
where: {
id: req.payload.userId
},
include: [{
model: models.Friend,
include: [{
model: models.Message,
order: [['createdAt', 'DESC']],
limit: 1
}]
}]
}).then(function(result) {
reply(result).code(200);
})
}
Everything is working fine, except that I want to tell sequelize to get the Friend data from the User table with the realId from the Friend table.
Should I use the keyword "through"? I didn't get exactly how it works even though I read the docs multiple times. I'm a beginner in SQL.
Thanks for your help.
Edit: My current output is:
{
"id": 2,
"userName": "user2",
"email": "user2#example.com",
"password": "123456",
"createdAt": "2016-05-26T16:28:02.000Z",
"updatedAt": "2016-05-26T16:28:13.000Z",
"Friends": [
{
"id": 1,
"realId": 1,
"invitStatus": "accepted",
"createdAt": "2016-05-26T16:30:15.000Z",
"updatedAt": "2016-05-26T16:30:15.000Z",
"UserId": 2,
"Messages": [
{
"id": 18,
"text": "ok",
"sentBy": 1,
"createdAt": "2016-05-26T16:59:36.000Z",
"updatedAt": "2016-05-26T16:59:36.000Z",
"FriendId": 1
}
]
}
What I want is to add the value "userName", "email" and "password" to the Friends array by getting it from the User table with the "Friend.realId = User.id.
I'm not sure to clearly understand your problem. I assume that Friends is linked to User by realId so you shouldn't have a Friends model but only one User model.
If this answer can help anyone, in this case you should probably use self-association.
You can achive self-association between User and User (as friend) with something like
User.belongsToMany(models.User, { as: 'friends', foreignKey: 'userId', through: 'User_Friends' });
And if your User_Friends model need additional attributes you can specified them like below, else this join table will be automatically created.
var User_Friends = sequelize.define('User_Friends', {
// ...
invitStatus: DataTypes.STRING
// ...
}