I am running into an issue where I am trying to associate two models that have a one-to-many relationship. For some reason this query is throwing an error despite referencing the relationship.
Here is my error message:
Unhandled rejection TypeError: Cannot read property 'getTableName' of undefined
at generateJoinQueries (/Users/user/Desktop/Projects/node/project/node_modules/sequelize/lib/dialects/abstract/query-generator.js:1181:43)
This is the route:
appRoutes.route('/settings')
.get(function(req, res, organization){
models.DiscoverySource.findAll({
where: {
organizationId: req.user.organizationId
},
include: [{
model: models.Organization, through: { attributes: ['organizationName', 'admin', 'discoverySource']}
}]
}).then(function(organization, discoverySource){
res.render('pages/app/settings.hbs',{
organization: organization,
discoverySource: discoverySource
});
})
});
DiscoverySource:
module.exports = function(sequelize, DataTypes) {
var DiscoverySource = sequelize.define('discovery_source', {
discoverySourceId: {
type: DataTypes.INTEGER,
field: 'discovery_source_id',
autoIncrement: true,
primaryKey: true
},
discoverySource: {
type: DataTypes.STRING,
field: 'discovery_source_name'
},
organizationId: {
type: DataTypes.TEXT,
field: 'organization_id'
},
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
DiscoverySource.belongsTo(db.Organization, {foreignKey: 'organization_id'});
},
},
});
return DiscoverySource;
}
Organization:
module.exports = function(sequelize, DataTypes) {
var Organization = sequelize.define('organization', {
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
autoIncrement: true,
primaryKey: true
},
organizationName: {
type: DataTypes.STRING,
field: 'organization_name'
},
admin: DataTypes.STRING
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.hasMany(db.DiscoverySource, {foreignKey: 'organization_id'});
},
}
});
return Organization;
}
It seems, that's a duplicate of Sequelize Association Error Cannot read property 'getTableName' of undefined.
However, you need to rewrite your query to look like:
models.DiscoverySource.findAll({
attributes: ['discoverySource'],
where: {
organizationId: req.user.organizationId
},
include: [{
model: models.Organization,
attributes: ['organizationName', 'admin']
}]
})
According to the Sequelize documentation:
[options.attributes] - A list of the attributes that you want to select, or an object with include and exclude keys.
[options.include[].attributes] - A list of attributes to select from the child model.
[options.include[].through.where] - Filter on the join model for belongsToMany relations.
[options.include[].through.attributes] - A list of attributes to select from the join model for belongsToMany relations.
So, [options.include[].through] can only be used in case of Belongs-To-Many association rather than Belong-To used by you for DiscoverySource and Organization models.
Related
I only found following in Sequelize documentation, but it's not possible to understand how to fetch associations properly.
Person.belongsToMany(Person, { as: 'Children', through: 'PersonChildren' })
// This will create the table PersonChildren which stores the ids of the objects.
This is the implementation.
const sequelizePsqlConfig = new Sequelize(
"postgres://Kavinda Vindika#localhost:5432/hrm_users"
);
export const User = sequelizePsqlConfig.define(
"users",
{
userId: {
field: "user_id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
userName: {
field: "user_name",
type: DataTypes.STRING,
},
email: { type: DataTypes.STRING, key: "email" },
department: { type: DataTypes.STRING, key: "department" },
designation: { type: DataTypes.STRING, key: "designation" },
},
{
timestamps: false,
}
);
export const User_Associations = sequelizePsqlConfig.define(
"user_associations",
{
id: {
field: "id",
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
managerId: {
field: "manager_id",
type: DataTypes.INTEGER,
},
subordinateId: {
field: "subordinate_id",
type: DataTypes.INTEGER,
},
},
{
modelName: "user_associations",
timestamps: false,
tableName: "user_associations",
}
);
User.belongsToMany(User, {
as: "manager",
through: "user_associations",
foreignKey: "manager_id",
otherKey: "subordinate_id",
});
User.belongsToMany(User, {
as: "subordinate",
through: "user_associations",
foreignKey: "subordinate_id",
otherKey: "manager_id",
});
Then I tried to fetch the data with manager and subordinate details.
public async getAllUsers() {
return User.findAll({
include: ["manager", "subordinate"],
});
}
But it failed with following logs. Please assist me on this issue.
[1] Executing (default): SELECT "users"."user_id" AS "userId", "users"."user_name" AS "userName", "users"."email", "users"."department", "users"."designation", "manager"."user_id" AS "manager.userId", "manager"."user_name" AS "manager.userName", "manager"."email" AS "manager.email", "manager"."department" AS "manager.department", "manager"."designation" AS "manager.designation", "manager->user_associations"."id" AS "manager.user_associations.id", "manager->user_associations"."manager_id" AS "manager.user_associations.managerId", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinateId", "manager->user_associations"."manager_id" AS "manager.user_associations.manager_id", "manager->user_associations"."subordinate_id" AS "manager.user_associations.subordinate_id", "subordinate"."user_id" AS "subordinate.userId", "subordinate"."user_name" AS "subordinate.userName", "subordinate"."email" AS "subordinate.email", "subordinate"."department" AS "subordinate.department", "subordinate"."designation" AS "subordinate.designation", "subordinate->user_associations"."id" AS "subordinate.user_associations.id", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.managerId", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinateId", "subordinate->user_associations"."manager_id" AS "subordinate.user_associations.manager_id", "subordinate->user_associations"."subordinate_id" AS "subordinate.user_associations.subordinate_id" FROM "users" AS "users" LEFT OUTER JOIN ( "user_associations" AS "manager->user_associations" INNER JOIN "users" AS "manager" ON "manager"."user_id" = "manager->user_associations"."subordinate_id") ON "users"."user_id" = "manager->user_associations"."manager_id" LEFT OUTER JOIN ( "user_associations" AS "subordinate->user_associations" INNER JOIN "users" AS "subordinate" ON "subordinate"."user_id" = "subordinate->user_associations"."manager_id") ON "users"."user_id" = "subordinate->user_associations"."subordinate_id";
[1] TypeError: Class constructor model cannot be invoked without 'new'
Basically I have a user model and appointment model. The two models are linked with a one-to-many relationship. The Appointment table has two columns that are associated with the user model. When ever I try to include the properties of the user table in appointment, I get the above error.
These are my model designs
Appointment Model
export default ({
sequelize
}:{
sequelize: Sequelize
}) => {
const Appointments: ModelDefined<AppointmentsAttribute, AppointmentsCreationAttributes> = sequelize.define('Appointments', {
appointmentId: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
client: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: 'Users',
key: 'uid'
}
},
serviceProvider: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: 'Users',
key: 'uid'
}
},
})
return Appointments
}
User Model
export default ({
sequelize
}: {
sequelize: Sequelize
}) => {
const User: ModelDefined<UserAttribute, UserCreationAttributes> = sequelize.define('User', {
uid: {
type: DataTypes.STRING,
allowNull: false,
unique:true,
primaryKey: true
},
firstname: {
type: DataTypes.STRING,
allowNull: false,
},
lastname: {
type: DataTypes.STRING,
allowNull: false,
},
}
})
return User
}
I have associated the user and appointment models with
Users.hasMany(Appointments);
The code that I am trying to use to fetch the appointment data and include the corresponding user value is
db.appointment.findAll({
where: {
client: this.uid
},
include: 'Users'
})
Sequelize always use associations for models whose association method was called. i.e. if you call Model1.hasMany(Model2) then you can execute queries like:
Model1.findAll({
include: [{
model: Model2
}]
}
and NOT vice versa like this:
Model2.findAll({
include: [{
model: Model1
}]
}
If you wish to request appointment with users as an associated model then you need to add a reversed association from Appointments to Users like this:
Users.hasMany(Appointments);
Appointments.belongsTo(Users);
I have a User Model with a hasOne relation on Role Model
User.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
},
//email, password, and other fields, ...
roleId: {
type:DataTypes.INTEGER,
allowNull: false
}},
{
sequelize,
tableName: "Users"
});
User.hasOne(Role)
and a Role Model
Role.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}},
{
sequelize,
tableName: "Roles"
});
When I try to create a new Role with
await Role.create(req.body)
And the request is
POST http://localhost:3000/api/role
Content-Type: application/json
Authorization: Bearer <token>
{
"name": "test role"
}
I get the error column "UserId" does not exist
And the log says is:
routine: 'errorMissingColumn',
sql: 'INSERT INTO "Roles" ("id","name") VALUES (DEFAULT,$1) RETURNING "id","name","UserId";',
parameters: [
'test role'
]
What did I do wrong here? My table only has roleId in the Users table, where did the UserId in Roles table come from?
Migrations
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Roles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING,
allowNull: false
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Roles');
}
};
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
// other fields
roleId: {
type: Sequelize.INTEGER,
references: {
model: "Roles",
key: "id"
}
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};
If I add Role.belongsTo(User) in the Role Model, I get the error:
models init error: TypeError: Cannot read property 'name' of undefined
For the role to be stored in the user table as your schema suggests: User.belongsTo(Role) will setup the mapping for you as RoleId.
The Model sets up the foreign key the opposite way to the migrations, so results in a missing UserID column.
The foreign keys don't need to be defined in the schema unless you want to customise the fields. The belongsTo/hasOne options are then defined on association call.
const { Sequelize, Model, DataTypes } = require('sequelize')
const sequelize = new Sequelize('sqlite::memory:')
class User extends Model {}
class Role extends Model {}
User.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}},
{
sequelize,
tableName: "Users"
});
Role.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
autoIncrement: true,
primaryKey: true
},
name: {
type: DataTypes.STRING,
allowNull: false
}},
{
sequelize,
tableName: "Roles"
});
User.belongsTo(Role, { foreignKey: 'roleId' })
Then you can do things with the association
async function go(){
await sequelize.sync()
const role = await Role.create({ name: 'atester' })
const user = await User.create({ name: 'test' })
await user.setRole(role)
console.log("%j", await User.findAll({ include: Role }))
}
go().catch(console.error)
Results in a document like:
{
"id": 1,
"name": "test",
"createdAt": "2020-12-04T09:44:05.762Z",
"updatedAt": "2020-12-04T09:44:05.763Z",
"roleId": 1,
"Role": {
"id": 1,
"name": "atester",
"createdAt": "2020-12-04T09:44:05.758Z",
"updatedAt": "2020-12-04T09:44:05.758Z"
}
}
From there you can match your migration to the database.
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
}]
});
I have a case where I am querying information from two tables that have a many-to-many relationship with a "through" table. When I make my query it appears that I am querying correctly by not using the "through" table as the table join reference and receiving the outputted records with both table attributes, but I am unable to access the field properties of the joined table. Here is the outputted values.
{"fullNameSlug":"Tester Test","email":"test#test.com","firstName":"Tester","lastName":"Test","teams":[{"teamName":"Sales","member":{"memberId":1,"memberEmail":"test#test.com","organizationId":1,"teamId":1,"userId":1,"created_at":"2016-08-21T21:15:19.000Z","updated_at":"2016-08-21T22:00:32.000Z","organization_id":1,"team_id":1,"user_id":1}}]}
Here is my query and how I am setting the data:
.get(function(req, res){
models.User.find({
where: {
organizationId: organization.organizationId
}, attributes: ['email', 'firstName', 'lastName'],
include: [{
model: models.Team,
attributes: ['teamName']
}]
});
}).then(function(currentUsers){
res.jsonp(currentUsers);
console.log(currentUsers);
});
Here is how I was trying to access the teamName in my view: {{currentUsers.teams.teamName}}, which is not returning a value, but {{currentUsers.email}} returns the right user email.
User Table:
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('user', {
userId: {
type: DataTypes.INTEGER,
field:'user_id',
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
},
email: {
type: DataTypes.STRING,
isEmail: true,
unique: true,
set: function(val) {
this.setDataValue('email', val.toLowerCase());
}
},
password: DataTypes.STRING,
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
}
}, {
underscored: true,
freezeTableName: true,
},
classMethods: {
associate: function(db) {
User.belongsToMany(db.Organization, { through: 'member', foreignKey: 'user_id'}),
User.belongsToMany(db.Team, { through: 'member', foreignKey: 'user_id'})
}
});
return User;
}
Team table:
module.exports = function(sequelize, DataTypes) {
var Team = sequelize.define('team', {
teamId: {
type: DataTypes.INTEGER,
field: 'team_id',
autoIncrement: true,
primaryKey: true,
notNull: true
},
teamName: {
type: DataTypes.STRING,
field: 'team_name'
},
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id'
},
},{
underscored: true,
freezeTableName: true,
classMethods: {
associate: function(db) {
Team.belongsToMany(db.User, { through: 'member', foreignKey: 'team_id' });
},
}
});
return Team;
}
Member Table:
module.exports = function(sequelize, DataTypes) {
var Member = sequelize.define('member', {
memberId: {
type: DataTypes.INTEGER,
field: 'member_id',
autoIncrement: true,
primaryKey: true
},
memberEmail: {
type: DataTypes.STRING,
field: 'member_email',
isEmail: true,
unique: true
},
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
},
teamId: {
type: DataTypes.INTEGER,
field: 'team_id',
allowNull: true
},
userId: {
type: DataTypes.INTEGER,
field: 'user_id',
allowNull: true
}
},{
underscored: true,
freezeTableName: true,
});
return Member;
}
Outputted SQL:
SELECT `user`.*, `teams`.`team_id` AS `teams.teamId`, `teams`.`team_name` AS `teams.teamName`, `teams.member`.`member_id` AS `teams.member.memberId`, `teams.member`.`member_email` AS `teams.member.memberEmail`, `teams.member`.`organization_id` AS `teams.member.organizationId`, `teams.member`.`team_id` AS `teams.member.teamId`, `teams.member`.`user_id` AS `teams.member.userId`, `teams.member`.`created_at` AS `teams.member.created_at`, `teams.member`.`updated_at` AS `teams.member.updated_at`, `teams.member`.`organization_id` AS `teams.member.organization_id`, `teams.member`.`team_id` AS `teams.member.team_id`, `teams.member`.`user_id` AS `teams.member.user_id` FROM (SELECT `user`.`user_id` AS `userId`, `user`.`email`, `user`.`first_name` AS `firstName`, `user`.`last_name` AS `lastName` FROM `user` AS `user` WHERE `user`.`organization_id` = 1 LIMIT 1) AS `user` LEFT OUTER JOIN (`member` AS `teams.member` INNER JOIN `team` AS `teams` ON `teams`.`team_id` = `teams.member`.`team_id`) ON `user`.`userId` = `teams.member`.`user_id`;
Consider your relations, User has many Teams trough table Member and your query returns user with many teams(array of team objects) as expected. You should use user.teams[0].teamName to get specific team by key, or loop objects in this array