Sequelize - how to include a custom column details - javascript

I have 2 tables: User and Follower
The basic schema looks like this:
User Table
var User = sequelize.define(
"User",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
username: DataTypes.STRING,
},
{
timestamps: true,
tableName: "users",
underscored: true
);
//Associations;
User.associate = function(models) {
User.hasMany(models.Follower, {
constraints: false,
onDelete: "cascade",
hooks: true
})
};
Follower Table
var Follower = sequelize.define(
"Follower",
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
follower_id: {
type: DataTypes.INTEGER,
foreignKey: true
},
following_id: {
type: DataTypes.INTEGER,
foreignKey: true
}
},
{
timestamps: true,
tableName: "followers",
underscored: true
}
);
//Associations;
Follower.associate = function(models) {
Follower.belongsTo(models.User, {
foreignKey: "following_id",
constraints: false
}),
Follower.belongsTo(models.User, {
foreignKey: "follower_id",
constraints: false
});
};
I want get a list of followers for a user but I want to show the user they are following and now their own data in.
I am currently running this procedure:
const following = await models.Follower.findAndCountAll({
attributes: models.Follower.fields,
where: { follower_id: user_id },
include:[
{
model: models.User,
attributes: models.User.fieldsPublic,
required: yes
}
]
})
The issue is that the include is returning the current user and not the user details of the person they are following.
Essentially the join seems to be between User.id = Follower.follower_id
How can I get it to include and join with User.id = Follower.following_id

You can add as both in your Model definition and include to be able to define which foreign key should be used (docs)
So do:
Follower.associate = function(models) {
Follower.belongsTo(models.User, {
foreignKey: "following_id",
as: 'following',
constraints: false
}),
Follower.belongsTo(models.User, {
foreignKey: "follower_id",,
as: 'follower',
constraints: false
});
};
and
...
include:[
{
model: models.User,
attributes: models.User.fieldsPublic,
as: 'follower'
required: yes
}
]
...
Alternatively, you can remove required: yes and use the where attribute to define the join condition
Furthermore, include seems to have an on attribute that is said to be similar to where (according to the docs), but I was unable to find an example.

Related

how to relational data one-to-one and many-to-many in sequalize in model same?

I have a problem in findById data with relational sequelize.
Where I have the condition of the One-to-One and Many-to-Many relations
TB_User is related on-to-one with tb_corporate
TB_User is related to many-to-many with TB Corporate and this data relations are saved in TB_corporate_Group
My problem, when I want to get corporate and corporate group. The data I get is data from the user table where the relationship is one-to-one.
this is code in model users
module.exports = (sequelize :any ) => {
class users extends Model{
static associate(models : any) {
// define association here
users.belongsToMany(models.corporate, {
through: 'corporateGroup',
as: 'dataCorporateGroup'
})
users.hasOne(models.corporate, {foreignKey: 'userId', as: 'corporate'})
}
}
users.init({
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
field: 'name',
type: DataTypes.STRING,
allowNull: false,
},
}, {
sequelize,
modelName: 'users',
});
return users;
};
code for model corporate
module.exports = (sequelize :any ) => {
class corporate extends Model{
static associate(models : any) {
// define association here
corporate.belongsTo(models.users, {foreignKey: 'userId', as: 'userCorporate'})
corporate.belongsToMany(models.users, {
through: 'corporateGroup',
as: 'userCorporate'
})
}
}
corporate.init({
userId: {
field: 'user_id',
type: DataTypes.INTEGER,
allowNull: false,
unique: true,
},
corporateName: {
field: 'name',
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
}, {
sequelize,
modelName: 'corporate',
});
return corporate;
};
code for model corporate group
module.exports = (sequelize :any ) => {
class corporateGroup extends Model{
static associate(models : any) {
// define association here
}
}
corporateGroup.init({
corporateId: {
field: 'corporate_id',
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'corporate',
key: 'id'
}
},
userId: {
field: 'user_id',
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'users',
key: 'id'
}
}
}, {
sequelize,
tableName: 'corporate_group',
modelName: 'corporateGroup',
});
return corporateGroup;
};
and this code for find by id
await corporate.findById(id, {
include: [{
model: db.users,
as: 'userCorporate',
}]
})
You need to indicate the unique aliases for associations
corporate.belongsTo(models.users, {foreignKey: 'userId', as: 'userCorporate'})
corporate.belongsToMany(models.users, {
through: 'corporateGroup',
as: 'corporateGroupUsers'
})
and then you can indicate included models with these aliases to get both of them along with corporate record:
await corporate.findById(id, {
include: [{
model: db.users,
as: 'userCorporate',
}, {
model: db.users,
as: 'corporateGroupUsers',
}]
})

Sequelize polymorphic many to many - `add` mixin not working

I have a many-to-many polymorphic association setup for customer surveys. The issue that I have run into is when using the add mixin on the model instance of Survey. If the joining table already has an item with the surveyed field equal to the id of the new surveyable, it gets overwritten.
survey table:
id
name
1
'Customer Survey'
scheduled_sessions table:
id
appointment_data
10
{ "someData" : [] }
service_provider table:
id
name
10
Joe Doe
survey_surveyable table:
survey
surveyable
surveyed
1
serviceProvider
10
When I add a scheduled session that happens to have the same id as a service provider, the join table row is overwritten:
const surveyInstance = await DB.Survey.findByPk(1);
const scheduledSessionInstance = await DB.ScheduledSession.findByPk(10);
surveyInstance.addScheduledSession(
scheduledSessionInstance,
{ through: { surveyable: "scheduledSession" } }
);
return surveyInstance.save();
This is the SQL queries that sequelize runs:
SELECT "id", "name"
FROM "surveys" AS "Survey"
WHERE "Survey"."id" = 1;
SELECT "id", "appointment_data" AS "appointmentData"
FROM "scheduled_sessions" AS "ScheduledSession"
WHERE "ScheduledSession"."id" = 10;
SELECT "survey", "surveyable", "surveyed"
FROM "survey_surveyable" AS "SurveySurveyable"
WHERE
"SurveySurveyable"."survey" = 1 AND
"SurveySurveyable"."surveyed" IN (10);
UPDATE "survey_surveyable"
SET "surveyable"=$1
WHERE
"survey" = $2 AND
"surveyed" = $3
Since both the scheduled session and the service provider have id=10, the service provider row in the join table is overwritten resulting in:
survey_surveyable table:
survey
surveyable
surveyed
1
scheduledSession
10
where it should have been:
survey_surveyable table:
survey
surveyable
surveyed
1
serviceProvider
10
1
scheduledSession
10
Is this a sequelize issue, or am I using the add mixin incorrectly?
My models:
Survey.js:
module.exports = (sequelize, DataTypes) => {
class Survey extends sequelize.Sequelize.Model {};
Survey.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}
},
{
timestamps: false,
tableName: "surveys",
sequelize
}
);
Survey.associate = (models) => {
Survey.belongsToMany(models.ScheduledSession, {
through: {
model: models.SurveySurveyable,
unique: false
},
foreignKey: "survey",
constraints: false
});
};
return Survey;
};
ScheduledSession.js:
module.exports = (sequelize, DataTypes) => {
class ScheduledSession extends sequelize.Sequelize.Model {};
ScheduledSession.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
}
appointmentData: {
type: DataTypes.JSONB,
allowNull: false,
field: "appointment_data"
}
},
{
paranoid: true,
tableName: "scheduled_sessions",
sequelize
}
);
ScheduledSession.associate = (models) => {
ScheduledSession.belongsToMany(models.Survey, {
through: {
model: models.SurveySurveyable,
unique: false,
scope: {
surveyable: "scheduledSession"
}
},
foreignKey: "surveyed",
constraints: false
});
};
return ScheduledSession;
};
ServiceProvider.js:
module.exports = (sequelize, DataTypes) => {
class ServiceProvider extends sequelize.Sequelize.Model {};
ServiceProvider.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
}
name: {
type: DataTypes.STRING,
allowNull: false
}
},
{
paranoid: true,
tableName: "service_provider",
sequelize
}
);
ServiceProvider.associate = (models) => {
ServiceProvider.belongsToMany(models.Survey, {
through: {
model: models.SurveySurveyable,
unique: false,
scope: {
surveyable: "serviceProvider"
}
},
foreignKey: "surveyed",
constraints: false
});
};
return ServiceProvider;
SurveySurveyable.js:
module.exports = (sequelize, DataTypes) => {
class SurveySurveyable extends sequelize.Sequelize.Model {};
SurveySurveyable.init(
{
survey: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
surveyable: {
type: DataTypes.STRING,
allowNull: false,
primaryKey: true
},
surveyed: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
constraints: false
}
},
{
timestamps: false,
tableName: "survey_surveyable",
sequelize,
freezeTableName: true
}
);
return SurveySurveyable;
};
You are using Survey's mixin but missing scope in Survey's association.
Survey.associate = (models) => {
Survey.belongsToMany(models.ScheduledSession, {
through: {
model: models.SurveySurveyable,
unique: false,
scope: { // This is missing
surveyable: "scheduledSession"
}
},
foreignKey: "survey",
constraints: false
});
};

Problem adding allowNull in the foreignKey association 1:1 in the models with Sequelize

i am doing a simple project with node.js using Sequelize with MySQL database. I was modeling my models associations. One got the hasMany and the other the belongsTo, I set the informations in both(like, as:...,foreignKey:...,onDelete:...), and set in foreignKey the propertie allowNull. When I save de model with the belongsTo method passing the in the request body the foreignKey it saves properly. But when I do this with model that uses in association the method hasOne it doesn't work. It passes that I need to specify the model ID that uses the belongsTo method.
const connection = require('../../config/database');
const DataTypes = require('sequelize').DataTypes;
const Course = connection.define('course', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
courseName: {
type: DataTypes.STRING(50),
field: 'course_name',
allowNull: false
}
},
{ timestamps: false });
Course.associate = models => {
Course.belongsTo(models.Lecturer, {
foreignKey: {
field: 'lecturer_id',
allowNull: false
},
as: 'lecturer'
});
Course.belongsToMany(models.Student, {
through: 'student_course',
as: 'students',
foreignKey: 'curse_id',
otherKey: 'student_id'
})
}
module.exports = Course;
const connection = require('../../config/database');
const DataTypes = require('sequelize').DataTypes;
const Lecturer = connection.define('lecturer', {
lecturerName: {
type: DataTypes.STRING(50),
field: 'lecturer_name',
allowNull: false
}
},
{ timestamps: false });
Lecturer.associate = models => {
Lecturer.hasOne(models.Course, {
foreignKey: {
field: 'lecturer_id',
allowNull: false
},
as: 'course'
})
}
module.exports = Lecturer;
Result:
[ ValidationErrorItem {
message: 'course.courseId cannot be null',
type: 'notNull Violation',
path: 'courseId',
value: null,
origin: 'CORE',
instance: [Object],
validatorKey: 'is_null',
validatorName: null,
validatorArgs: [] } ]
I can't comment yet, but it looks like the error shows a camel-case column name (courseId) as opposed to a snake-case column name (course_id).
Also, you have misspelled "course_id" in your Course.belongsToMany association.

sequelize - Cannot add foreign key constraint

I'm trying to set a 1:1 relation between two tables. RefreshToken table will have two foreignKey releated to Users table, as in this image:
I used sequelize-auto to generate my sequelize models.
Users model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('Users', {
idUsers: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
name: {
type: DataTypes.STRING(45),
allowNull: true
},
mail: {
type: DataTypes.STRING(45),
allowNull: false,
primaryKey: true
}
}, {
tableName: 'Users'
});
};
RefreshToken model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('RefreshToken', {
idRefreshToken: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true
},
token: {
type: DataTypes.TEXT,
allowNull: true
},
userId: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: 'Users',
key: 'idUsers'
}
},
userEmail: {
type: DataTypes.STRING(45),
allowNull: false,
primaryKey: true,
references: {
model: 'Users',
key: 'mail'
}
}
}, {
tableName: 'RefreshToken'
});
};
When I run the application, I receive this error:
Unhandled rejection Error: SequelizeDatabaseError:
ER_CANNOT_ADD_FOREIGN: Cannot add foreign key constraint
I tried to add explicit the relation, adding in Users table:
User.associate = (models) => {
User.hasOne(models.RefreshToken, {
foreignKey: 'userId'
});
User.hasOne(models.RefreshToken, {
foreignKey: 'userEmail'
});
};
and in RefreshToken:
RefreshToken.associate = (models) => {
RefreshToken.belongsTo(models.Users, {
foreignKey: 'userId'
});
RefreshToken.belongsTo(models.Users, {
foreignKey: 'userEmail'
});
};
But I receive again the same error. If I remove the references in the RefreshToken table I don't see any error, but when I check the database I don't see any foreign key relation constraint with email and id of the User
This is common type error mainly occurs because of
1. When the primary key data type and the foreign key data type did not matched
return sequelize.define('RefreshToken', {
userId: {
type: DataTypes.INTEGER(11), // The data type defined here and
references: {
model: 'Users',
key: 'idUsers'
}
},
return sequelize.define('Users', {
idUsers: {
type: DataTypes.INTEGER(11), // This data type should be the same
},
2. When the referenced key is not a primary or unique key.
You can not have two primary keys, so other referenced keys should be defined unique. unique:true
return sequelize.define('Users', {
idUsers: {
primaryKey: true
},
mail: {
type: DataTypes.STRING(45),
allowNull: false,
primaryKey: true // You should change this to 'unique:true'. you cant hv two primary keys in one table.
}
I see two issues:
No table should contain two primary keys and userId shouldn't be in integer it should be a UUID.
I had a foreign key set to INT and it gave me error:
Unhandled rejection SequelizeDatabaseError: foreign key constraint
"constraint_name_here" cannot be implemented
Try changing:
userId: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: 'Users',
key: 'idUsers'
}
},
To
userId: {
type: DataTypes.UUID,
allowNull: false,
foreignKey: true,
references: {
model: 'Users',
key: 'idUsers'
}
},

How can I achieve this SQL QUERY in sequelize (multiple joins and multiple AND/OR)

I've been struggling to achieve this (below SQL statement) in sequelize for a while now with no luck. I initially had to make separate sequelize queries to get the data but that just posed many limitations.
`SELECT "Documents".* FROM "Documents"
INNER JOIN "AccessTypes"
ON "AccessTypes"."id" = "Documents"."accessTypeId"
INNER JOIN "Users"
ON "Users"."id" = "Documents"."userId"
INNER JOIN "Departments"
ON "Departments"."id" = "Users"."departmentId"
WHERE
(("AccessTypes".name != 'private'
AND "Departments"."id" = ${req.decoded.departmentId})
OR "Users".id = ${req.decoded.id})
AND ("Documents"."title" ILIKE '%${searchQuery}%'
OR "Documents"."content" ILIKE '%${searchQuery}%'`
This is as far as I got
var dbQuery = {
where: {
$or: [
{
title: {
$iLike: `%${searchQuery}%`
}
},
{
content: {
$iLike: `%${searchQuery}%`
}
}
]
},
include: [{
model: db.Users,
where: { departmentId: req.decoded.departmentId }
},
{
model: db.AccessTypes,
where: { name: { $ne: 'private'}}
}]
};
db.Documents.findAll(dbQuery)
I still need to fetch another set of documents based on the userId supplied. I feel the way to go will be to perform an 'Include' within an '$or' statement. however my research so far leads me to believe that's not possible.
Here are my models
Access Types
export default (sequelize, DataTypes) => {
const AccessTypes = sequelize.define('AccessTypes', {
name: {
type: DataTypes.STRING,
allowNull: false,
isUnique: true
}
}, {
classMethods: {
associate: (models) => {
// associations can be defined here
AccessTypes.hasMany(models.Documents, {
foreignKey: 'accessTypeId',
onDelete: 'CASCADE'
});
}
}
});
return AccessTypes;
};
Users
export default (sequelize, DataTypes) => {
const Users = sequelize.define('Users', {
username: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
firstname: {
type: DataTypes.STRING,
allowNull: false
},
lastname: {
type: DataTypes.STRING,
allowNull: false
},
email: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
roleId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 3
},
departmentId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
classMethods: {
associate: (models) => {
// associations defined here
Users.belongsTo(models.Roles, {
onDelete: 'CASCADE',
foreignKey: 'roleId'
});
Users.belongsTo(models.Departments, {
onDelete: 'CASCADE',
foreignKey: 'departmentId'
});
Users.hasMany(models.Documents, {
as: 'documents',
foreignKey: 'userId',
onDelete: 'CASCADE'
});
}
}, ...
Departments
export default (sequelize, DataTypes) => {
const Departments = sequelize.define('Departments', {
name: {
type: DataTypes.STRING,
allowNull: false,
isUnique: true
}
}, {
classMethods: {
associate: (models) => {
// associations can be defined here
Departments.hasMany(models.Users, {
foreignKey: 'departmentId',
onDelete: 'CASCADE'
});
}
}
});
return Departments;
};
and Documents
export default (sequelize, DataTypes) => {
const Documents = sequelize.define('Documents', {
title: {
type: DataTypes.STRING,
allowNull: false
},
content: {
type: DataTypes.TEXT,
allowNull: false
},
userId: {
type: DataTypes.INTEGER,
allowNull: false
},
accessTypeId: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 1
},
docTypeId: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
classMethods: {
associate: (models) => {
// associations can be defined here
Documents.belongsTo(models.Users, {
foreignKey: 'userId',
as: 'user',
onDelete: 'CASCADE'
});
Documents.belongsTo(models.DocumentTypes, {
foreignKey: 'docTypeId',
onDelete: 'CASCADE'
});
Documents.belongsTo(models.AccessTypes, {
foreignKey: 'accessTypeId',
onDelete: 'CASCADE'
});
}
}
});
return Documents;
};
Any Pointers Will be greatly appreciated
Thanks in Advance
This is quite complex query (in Sequelize way of course), so you need to build it differently than you did. You should use functions like sequelize.col(), sequelize.where(), sequelize.or() and sequelize.and(). Moreover, in order to include the Departments model in the query, you need to use nested include statement in the options object of the findAll query. You can nest the includes as much as you want.
where: sequelize.and(
sequelize.or(
sequelize.and(
sequelize.where(sequelize.col('AccessTypes.name'), '!=', 'private'),
sequelize.where(sequelize.col('Departments.id'), '=', req.decoded.departmentId)
),
sequelize.where(sequelize.col('Users.id'), '=', req.decoded.id)
),
sequelize.or(
{ title: { $iLike: `%${searchQuery}%` } },
{ content: { $iLike: `%{searchQuery}%` } }
)
),
include: [
{
model: db.Users,
include: [ db.Departments ]
},
{
model: db.AccessTypes
}
]
You need to briefly get through documentation of above mentioned functions. In a short, col() creates a proper column selection basing on model name and field, where() creates WHERE statement with use of three attributes - column, condition (comparator) and logic, or() creates OR statement and and() creates AND statement. Both or() and and() can obtain multiple arguments that allows you to create complex statements, like yours.

Categories

Resources