Problem setting up Sequelize association - query with 'include' is failing - node.js

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

Related

Sequelize model field that I did not add

I have a user, role and their relation model, when I want to insert into the relation model I get this error:
error: column "userUserId" of relation "roles_users_relationships" does not exist.
Can you help with this error?
(sorry if I wrote something wrong, this is my first question on )
This is how my model looks
Role model:
const Schema = (sequelize, DataTypes) => {
const table = sequelize.define(
"roles", {
role_id: {
type: DataTypes.UUID,
defaultValue: sequelize.literal("uuid_generate_v4()"),
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
}
}, {
timestamps: false
}
);
table.associate = function (models) {
table.belongsToMany(models.users, {
through: "roles_users_relationship",
foreignKey: "role_id",
});
};
return table;
};
Users model:
const Schema = (sequelize, DataTypes) => {
const table = sequelize.define(
"users", {
user_id: {
type: DataTypes.UUID,
defaultValue: sequelize.literal("uuid_generate_v4()"),
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: true,
}
}, {
timestamps: false
}
);
table.associate = function (models) {
table.belongsTo(models.roles, {
through: "roles_users_relationship",
foreignKey: "user_id",
});
};
return table;
};
Roles Users relationship model:
const Schema = (sequelize, DataTypes) => {
const table = sequelize.define(
"roles_users_relationship", {
user_id: {
type: DataTypes.UUID,
allowNull: false,
},
role_id: {
type: DataTypes.UUID,
allowNull: false,
},
}, {
timestamps: false
}
);
return table;
};
In your through table you should add options in related table field:
references: {
model: User,
key: 'user_id'
}
Otherwise sequelize will do it automatically, like adding foreign key column in this way tableNamePrimaryKeyColumn in your case its 'userUserId'

Change Sequelize default foreign key with through tables

I have two tables, apps and services. On services the PRIMARY KEY is set to field called orn.
I created a through table between these two tables called apps_services with appId and serviceId
appId: {
type: Sequelize.UUID,
references: {
model: 'apps',
key: 'id'
}
},
serviceId: {
type: Sequelize.STRING,
references: {
model: 'services',
key: 'orn'
}
},
But here is a problem, when i use app.addService(foundService); (this creates a relationship between that app instance and foundService). When i used node debugger, i found out that sequelize tried inserting an id into serviceId instead of an orn
Executing (default): INSERT INTO "apps_services" ("id","appId","serviceId","createdAt","updatedAt") VALUES ('8d85f9b9-b472-4165-a345-657a0fe0d8ad','49f3daa4-1df4-4890-92f8-9a06f751ffb7','8cab340d-d11b-4e3b-842c-aeea9a28c368','2021-02-16 00:22:17.919 +00:00','2021-02-16 00:22:17.919 +00:00') RETURNING "id","appId","serviceId","userId","selfGranted","createdAt","updatedAt";
the third value in the bracket, the problem is id is not the foreignKey but orn is
below is the service table, we can see the orn column, which is the PRIMARY KEY and foreign key linking to the apps_services table.
Is there a way to force sequelize to use the orn field?
module.exports = (sequelize, DataTypes) => {
const apps_services = sequelize.define('apps_services', {
id: {
type: DataTypes.UUID,
defaultValue: new DataTypes.UUIDV4(),
unique: true,
primaryKey: true
},
appId: {
type: DataTypes.UUID,
},
serviceId: {
type: DataTypes.STRING,
},
userId: {
type: DataTypes.UUID
},
selfGranted: DataTypes.BOOLEAN
}, {
timestamps: true
});
apps_services.associate = function (models) {
models.apps.belongsToMany(models.services, {
through: apps_services
});
models.services.belongsToMany(models.apps, {
through: apps_services
});
};
//apps_services.sync({alter: true});
return apps_services;
};
// this is apps_services migration
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('apps_services', {
id:{
type: Sequelize.UUID,
defaultValue: new Sequelize.UUIDV4(),
unique: true,
primaryKey: true
},
appId: {
type: Sequelize.UUID,
references: {
model: 'apps',
key: 'id'
}
},
serviceId: {
type: Sequelize.STRING,
references: {
model: 'services',
key: 'orn'
}
},
userId: {
type: Sequelize.UUID,
references: {
model: 'Users',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
selfGranted: Sequelize.BOOLEAN,
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('apps_services');
}
};
Below is the services table
Yes you can mention which field to use from the opposite table.
apps_services.associate = function (models) {
// you can move this association to apps model file
models.apps.belongsToMany(models.services, {
// Reference: https://sequelize.org/master/class/lib/model.js~Model.html#static-method-belongsToMany
through: 'apps_services',
foreignKey: 'appId', // <-- add this
});
// you can move this association to services model file
models.services.belongsToMany(models.apps, {
through: 'apps_services',
foreignKey: 'serviceId', // <-- add this - name of field in through table
otherKey: 'orn', // <-- add this - name of field in source table
});
/**
* new relation below just to complete the Many to Many relation
*/
// you can add this association in app_services model file
models.apps_services.belongsTo(models.services, {
// Reference: https://sequelize.org/master/class/lib/model.js~Model.html#static-method-belongsTo
foreignKey: 'serviceId', // <--- name of field in source table
targetKey: 'orn', // <--- name of field in target table
});
// you can add this association in app_services model file
models.apps_services.belongsTo(models.apps, {
foreignKey: 'appId', // <--- name of field in source table
// targetKey: 'id', //(not needed as already primary key by default) <--- name of field in target table
});
};

Saving Sequelize Record/Referencing it in Another Model

I have a postrgresql/Sequelize model called Segment, which belongs to many models:
module.exports = (sequelize, DataTypes) => {
const Segment = sequelize.define(
'segment',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
provider_id: {
type: DataTypes.INTEGER,
references: {
model: 'provider',
key: 'id'
}
},
summary_id: {
type: DataTypes.INTEGER,
references: {
model: 'summary',
key: 'id'
}
},
audience_id: {
type: DataTypes.INTEGER,
references: {
model: 'audience',
key: 'id'
}
},
onboarding_id: {
type: DataTypes.INTEGER,
references: {
model: 'onboarding',
key: 'id'
}
}
},
{
// disable the modification of table names; By default, sequelize will automatically
// transform all passed model names (first parameter of define) into plural.
// if you don't want that, set the following
freezeTableName: true,
tableName: 'segment'
}
);
Segment.associate = models => {
Segment.belongsTo(models.Provider, { foreignKey: 'id' });
Segment.belongsTo(models.Summary, { foreignKey: 'id' });
Segment.belongsTo(models.Audience, { foreignKey: 'id' });
Segment.belongsTo(models.Onboarding, { foreignKey: 'id' });
};
return Segment;
};
The models that segment has associations to (ie provider_id, summary_id, audience_id, onboarding_id) look like this:
Provider:
module.exports = (sequelize, DataTypes) => {
const Provider = sequelize.define(
'provider',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
providerName: {
type: DataTypes.STRING
},
email: {
type: DataTypes.STRING
},
privacyPolicy: {
type: DataTypes.STRING
}
},
{
freezeTableName: true,
tableName: 'provider'
}
);
Provider.associate = models => {
Provider.hasMany(models.Segment, { foreignKey: 'provider_id' });
};
return Provider;
};
Summary:
module.exports = (sequelize, DataTypes) => {
const Summary = sequelize.define(
'summary',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
summaryName: DataTypes.STRING,
standardIdName: DataTypes.STRING,
description: DataTypes.STRING,
},
{
freezeTableName: true,
tableName: 'summary'
}
);
Summary.associate = models => {
Summary.hasMany(models.Segment, { foreignKey: 'summary_id' });
};
return Summary;
};
Audience:
module.exports = (sequelize, DataTypes) => {
const Audience = sequelize.define(
'audience',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
refreshCadence: DataTypes.STRING,
sourceLookbackWindow: DataTypes.STRING
},
{
freezeTableName: true,
tableName: 'audience'
}
);
Audience.associate = models => {
Audience.hasMany(models.Segment, { foreignKey: 'audience_id' });
};
return Audience;
};
Onboarding:
module.exports = (sequelize, DataTypes) => {
const Onboarding = sequelize.define(
'onboarding',
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
onboardingExpansions: DataTypes.STRING,
onboardingAudiencePrecision: DataTypes.STRING
},
{
freezeTableName: true,
tableName: 'onboarding'
}
);
Onboarding.associate = models => {
Onboarding.hasMany(models.Segment, { foreignKey: 'onboarding_id' });
};
return Onboarding;
};
My question is: what should come first when creating and saving a Segment record? Do I create and save each one of the other models first (provider, summary, audience, onboarding), and then create/save a Segment with references to those ids? I don't really know what the order of events should be in this situation. Any help is much appreciated! Thanks!
TLDR:
In order to create an instance of Segment, you must have all 4 foreign keys reference exist records on the referenced tables(provider, summary, audience and onboarding).
Explanation:
provider, summary, audience and onboarding tables are independent.
However, Segment model is not independent.
Segment model has 4 columns which are foreign keys.
From PostgresSql Tutorial:
A foreign key is a field or group of fields in a table that uniquely
identifies a row in another table. In other words, a foreign key is
defined in a table that references to the primary key of the other
table.
The table that contains the foreign key is called referencing table or
child table. And the table to which the foreign key references is
called referenced table or parent table.
It means that a foreign key is a constraint that the column should reference the primary key of the referenced table.
So, you must create all the resources of a created row of Segment.

How to define foreign key constraint in postgres using sequelize ORM?

I am trying to create one to many foreign key constraint between two tables. A client can have many environments.
Here is my snippet in the model.
Client.associate = function(models) {
Client.hasMany(models.Enviornment, {as: 'enviornments', foreignKey: 'clientId'})
};
The solution is here.
The client has many environments. (one to many association )
Here is a code snippet for a model client.
module.exports = (sequelize, DataTypes) => {
let client = sequelize.define ('client', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id' },
...
}, {
associate: models => {
client.hasMany (models.enviornment, {
foreignKey: { name: 'client_id', allowNull: false }
});
},
});
return client;
};
Here is a code snippet for a model enviornment.
module.exports = (sequelize, DataTypes) => {
let enviornment = sequelize.define ('enviornment', {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, field: 'id' },
...
}, {
associate: models => {
enviornment.belongsTo (models.client, {
foreignKey: { name: 'client_id', allowNull: false }
});
},
});
return enviornment;
};

Creating associations in Sequelize migration

Nodejs. Sequelize 4.41. Try to make 2 models with relation many-to-many through another table. Running with sequelize-cli, for example...
sequelize model:generate --name Camera --attributes name:string,sn:string
Here is models
// Camera model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Сamera = sequelize.define('Сamera', {
name: DataTypes.STRING,
sn: DataTypes.STRING
}, {});
Сamera.associate = function(models) {
// associations can be defined here
Camera.belongsToMany(models.Relay, {through: 'CameraRelay'});
};
return Сamera;
};
And
// Relay model
'use strict';
module.exports = (sequelize, DataTypes) => {
const Relay = sequelize.define('Relay', {
name: DataTypes.STRING,
sn: DataTypes.STRING,
channel: DataTypes.INTEGER
}, {});
Relay.associate = function(models) {
// associations can be defined here
Relay.belongsToMany(models.Camera, {through: 'CameraRelay'});
};
return Relay;
};
In documentation there are phrase
Belongs-To-Many associations are used to connect sources with multiple targets. Furthermore the targets can also have connections to multiple sources.
Project.belongsToMany(User, {through: 'UserProject'});
User.belongsToMany(Project, {through: 'UserProject'});
This will
create a new model called UserProject with the equivalent foreign keys
projectId and userId.
Migrations is
// create-relay
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Relays', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
sn: {
type: Sequelize.STRING
},
channel: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Relays');
}
};
And
//create camera
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Сameras', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
sn: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Сameras');
}
};
Why it doesn't create model CameraRelay and doesn't create migration for same model when I running migrate?
I guess the misunderstanding is about sync vs migration: great part of documentation you are referring to, is using the sync method to create all tables and associations starting from models.
When you are using migrations, you are creating db all of your table/columns/associations using migration files (and in my hopinion, this is a better way for something that is going to production).
To understand the difference, just look at your camera model vs your camera migration file:
the model has only name and sn properties defined
the migration file has of course name and sn, but it has id, createdAt and updatedAt too.
Migrations are file with the aim of change your db in a safe way, allowing you to rollback to any point in the past.
So, back to your problem, you have to:
create a new migration file to create your new CameraRelay table, with foreign keys to both Camera and Relay tables
update your current Camera migration file with one-to-many relation to CameraRelay table
update your current Relay migration file with one-to-many relation to CameraRelay table
CameraRelay migration example:
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('CameraRelays', {
cameraId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Relay',
key: 'id'
}
},
relayId: {
primaryKey: true,
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Camera',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('CameraRelays');
}
};

Resources