I'm using sequelize as my nodejs web app ORM.
I have 2 models, Offer and Game. There is a 1:1 association between them, and I am considering how to implement it.
First, I tried using Offer.belongsTo(Game) so that the GameId resides in the Offer model. When I create an Offer, I want that the Game to be created as well.
I have set the association as follows:
Offer.belongsTo(models.Game, {
foreignKey: {
allowNull: false
}
})
So that the GameId will never be null. In sequelize docs, it is noted that I could create the Game together with the Offer like this:
models.Offer.create({Game:{"name":"The Last of Us", "platform":"PC"}},{include:[models.Game]});
But I'm getting:
Unhandled rejection SequelizeDatabaseError: null value in column "GameId" violates not-null constraint
How can I create an Offer and a Game in 1 action using the not null constraint?
EDIT:
module.exports = function(sequelize, DataTypes) {
var Game = sequelize.define("Game", {
name: {
type: DataTypes.STRING,
allowNull: false
},
platform: {
type: DataTypes.ENUM('PC', 'PS4', 'PS3', 'XB1', 'XB360'),
allowNull: false
},
cover: {
type: DataTypes.STRING,
allowNull: false,
validate: {
notEmpty: true
}
}
}, {
updatedAt: false,
createdAt: false
});
return Game;
};
module.exports = function(sequelize, DataTypes) {
var Offer = sequelize.define("Offer", {
price: {
type: DataTypes.INTEGER,
validate: {
max: 999,
min: 0
}
},
exchange: {
type: DataTypes.BOOLEAN,
defaultValue: true,
allowNull: false
}
}, {
updatedAt: false,
classMethods: {
associate: function(models) {
Offer.belongsTo(models.Game, {
foreignKey: {
allowNull: false
}
});
}
}
});
return Offer;
};
You didn't put anything for Offer object. You're just passing Game object.
models.Offer.create({
//set fields for 'Offer'
Game:{"name":"The Last of Us", "platform":"PC"}
},
{include:[models.Game]}
);
Also, you just have to define foreignKey while setting associations. Constraint definition resides in model:
Offer.belongsTo(models.Game, {
foreignKey: 'GameId'
});
Related
I am working on an electron app and for the database, I am using sqlite3 along with sequelize. I want to establish a one-to-many relationship between two of the following models.
Item
Metric
Metrics can be liters/kilograms/units and an item can be measured in any of these metrics. So following is how I have declared the Item model.
const { Model, DataTypes } = require("sequelize");
const sequelize = require("../database/db");
const Metric = require("./metricModel");
class Item extends Model {}
Item.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
metricId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: "metrics",
key: "id",
},
},
available: {
type: DataTypes.FLOAT,
defaultValue: 0,
},
incoming: {
type: DataTypes.FLOAT,
defaultValue: 0,
},
},
{
sequelize,
tableName: "items",
freezeTableName: true,
}
);
Item.associate = (models) => {
Item.belongsTo(models.Metric, { foreignKey: "metricId" });
};
module.exports = Item;
And following is how I have declared the Metric
const { Model, DataTypes } = require("sequelize");
const sequelize = require("../database/db");
const Item = require("./itemModel");
class Metric extends Model {}
Metric.init(
{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING(10000),
},
},
{
sequelize,
tableName: 'metrics', freezeTableName: true
}
);
Metric.associate = function (models) {
Metric.hasMany(models.Item, { foreignKey: "metricId" });
};
module.exports = Metric;
But in the logs, I can't see any association getting created.
Also on making a select query on items. like below.
const items = await Item.findAll({include: [Metric]});
I get below error
My bad, there was a duplicate column in my items model and since during debugging the table named item was present beforehand, it was working fine. If you encounter this issue, make sure all your tables are declared properly.
I'm trying to run the following code block, for some reason the query tries to insert it into a column labeled "users->user_group"."userUuid", despite the fact that I have not reference the string literal userUuid once in the project (through search not in the code base), also check columns in pg-admin (using PostgreSQL), both columns in the user_group table are user_uuid and group_uuid, both columns are also validated and populated properly.
const result = await group.findAll({
include: user,
});
Postman body returns the following error
"hint": "Perhaps you meant to reference the column "users->user_group.user_uuid".",
I have 3 models user, group and user_group. The relations have been defined per documentation and countless other articles and videos.
user model
module.exports = (sequelize, DataTypes) => {
const user = sequelize.define(
"user",
{
uuid: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
unique: true,
},
username: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
{
freezeTableName: true,
}
);
user.associate = (models) => {
user.belongsToMany(models.group, {
// as: "userUuid",
through: models.user_group,
foreignKey: "user_uuid",
});
};
return user;
};
group model
module.exports = (sequelize, DataTypes) => {
const group = sequelize.define(
"group",
{
uuid: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
unique: true,
},
title: {
type: DataTypes.STRING,
unique: true,
allowNull: false,
},
},
{
freezeTableName: true,
}
);
group.associate = (models) => {
group.belongsToMany(models.user, {
// as: "groupUuid",
through: models.user_group,
foreignKey: "group_uuid",
});
};
return group;
};
user_group model
module.exports = (sequelize, DataTypes) => {
const user_group = sequelize.define(
"user_group",
{
uuid: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
unique: true,
},
user_uuid: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: "user",
key: "uuid",
},
},
group_uuid: {
type: DataTypes.STRING,
allowNull: false,
references: {
model: "group",
key: "uuid",
},
},
author: {
type: DataTypes.STRING,
unique: true,
allowNull: true,
},
},
{
freezeTableName: true,
}
);
user_group.associate = (models) => {
user_group.belongsTo(models.user, {
foreignKey: "user_uuid",
});
user_group.belongsTo(models.group, {
foreignKey: "group_uuid",
});
};
return user_group;
};
Any help is much apprecaited, thanks!
You should indicate otherKey option along with foreignKey in belongsToMany in order to indicate a foreign key column on the other model otherwise you will end up with a default name of an other key, see below:
The name of the foreign key in the join table (representing the target model) or an object representing the type definition for the other column (see Sequelize.define for syntax). When using an object, you can add a name property to set the name of the column. Defaults to the name of target + primary key of target (your case: user+uuid)
group.belongsToMany(models.user, {
// as: "groupUuid",
through: models.user_group,
foreignKey: "group_uuid",
otherKey: "user_uuid"
});
const result = await group.findAll({
include: {user},
});
you should to create like this. baecause you missing this {}.
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 2 models users and tags, both are associated through another model called usersTags and all 3 models have paranoid set with custom timestamps. I understand that associating models will add additional methods to work on the associations to all associated models, so i am wanting to making a simple setTags call for users, the docs shows that if in the array in the method does not contain the element that is stored in the database it should be removed, otherwise it should be created/restored.
So i try to restore a previously removed tag but for some reason it fails. The models are defined as following:
Users
module.exports = function(sequelize, DataTypes) {
const Users = sequelize.define("users", {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(100),
allowNull: false,
validate: {
len: {
args: [3, 100],
msg: "String length is not in this range"
}
}
},
password: {
type: DataTypes.STRING(100),
allowNull: false,
field: "password_hash"
}
}, {
tableName: "users",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true
});
Users.associate = function(models) {
// Add this association to include tag records
this.belongsToMany(models.tags, {
through: {
model: models.usersTags,
unique: true
},
foreignKey: "users_id",
constraints: false
});
};
return Users;
};
Tags
module.exports = function(sequelize, DataTypes) {
const Tags = sequelize.define("tags", {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(45),
allowNull: false
}
}, {
tableName: "tags",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true
});
Tags.associate = function(models) {
this.belongsToMany(models.users, {
through: {
model: models.usersTags,
unique: true
},
foreignKey: "tags_id",
constraints: false
});
};
return Tags;
};
usersTags
module.exports = function(sequelize, DataTypes) {
const UsersTags = sequelize.define("usersTags", {
users_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: "users",
key: "id"
}
},
tags_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
references: {
model: "tags",
key: "id"
}
}
}, {
tableName: "users_tags",
createdAt: "create_time",
updatedAt: "update_time",
deletedAt: "delete_time",
paranoid: true,
indexes: [
{
unique: true,
fields: ["users_id", "tags_id"]
}
]
});
return UsersTags;
};
Test
let _user;
models.users.findOne({where: {id: 100}})
.then(user => {
_user = user;
return _user.setTags([1]); // Successfully create association tag with id 1
})
.then(() => _user.setTags([])) // Successfully remove all associated tags
.then(() => _user.setTags([1])); // Should restore association tag with id 1 but fails
Executed query
app:database Executing (default): SELECT `id`, `username`, `first_name`, `last_name`, `birthday`, `description`, `location`, `email`, `type`, `image_path` FROM `users` AS `users` WHERE ((`users`.`delete_time` > '2018-08-28 19:40:15' OR `users`.`delete_time` IS NULL) AND `users`.`id` = 100); +0ms
app:database Executing (default): SELECT `users_id`, `tags_id`, `create_time`, `update_time`, `delete_time` FROM `users_tags` AS `usersTags` WHERE ((`usersTags`.`delete_time` > '2018-08-28 19:40:15' OR `usersTags`.`delete_time` IS NULL) AND `usersTags`.`users_id` = 100); +6ms
app:database Executing (default): INSERT INTO `users_tags` (`users_id`,`tags_id`,`create_time`,`update_time`) VALUES (100,1,'2018-08-28 19:40:15','2018-08-28 19:40:15'); +7ms
For some reason the tag search query is failing to retrieve the tag that contains the delete_time set and therefore the last query is insert instead of update, i know the workaround would be to set paranoid to false but i have to keep track of all activities, i know another workaround would be to create a custom model method to handle this but i still want to know if there is a way to achieve this without having to create an additional custom method
your code in not in a correct async order so your _user global variable is not initiated,I think this is the correct order :
let _user;
models.users.findOne({where: {id: 100}})
.then(user => {
_user = user;
_user.setTags([]).then(()=>{
_user.setTags([1])
})
})
as you guys can see my issue is related to the title description, i created a User Model, and a Foto Model in sequelize, basicly a user can shoot many fotos, but each foto can be related to just 1 user.
My User model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var Foto = require('./Foto');
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { username: value } })
.then(function (user) {
// reject if a different user wants to use the same username
if (user && self.id !== user.id) {
return next('username already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isUnique: function (value, next) {
var self = this;
User.find({ where: { email: value } })
.then(function (user) {
// reject if a different user wants to use the same email
if (user && self.id !== user.id) {
return next('Email already in use!');
}
return next();
})
.catch(function (err) {
return next(err);
});
}
}
},
typeOfUser: {
type: DataTypes.INTEGER,
allowNull:true,
defaultValue:null
},
country: {
type: DataTypes.STRING,
allowNull:true,
defaultValue:null
},
birthDate:{
type: DataTypes.DATEONLY,
allowNull:true,
defaultValue:null
},
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
points: {
type: DataTypes.INTEGER,
defaultValue: 0
},
password: {
type: DataTypes.STRING,
allowNull:false
},
numberFotos: {
type: DataTypes.INTEGER,
defaultValue: 0
}
}, {
classMethods: {
generateHash: function (password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
},
instanceMethods: {
validPassword: function (password) {
return bcrypt.compareSync(password, this.password);
}
}
});
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
return Foto;
}
My foto model
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Foto = sequelize.define("Foto", {
reports: {
type: DataTypes.INTEGER,
defaultValue: 0
},
image: {
type: DataTypes.STRING,
allowNull: false
},
date: {
type: DataTypes.DATE,
allowNull:true
},
position: {
type: DataTypes.RANGE,
allowNull: true
}
});
Foto.belongsTo(User, {foreignKey: 'userId'});
return Foto;
}
You don't need to declare the association on the Photo Model:
Foto.belongsTo(User, {foreignKey: 'userId'});
When you have a 1:N relation between models you only need to refer the id from the "1" model, on our case the User model, on the "N" model, Photos. So doing:
User.hasMany(Foto,{as: 'fotos', foreignKey: 'userId'})
Will create a column on your Foto table with name "userId" that refer to user table. On this way both models are associate as you want.
You can define relations for both models in one file. It doesn't throw any errors that way.
In your Foto.js, you can try:
...
Foto.belongsTo(User);
User.hasMany(Foto);
return Foto;
I had a similar problem. Sometimes it can be caused because in your index.js or app.js the files are loaded in a specific order, so for example if you have a relationship between A and B, A loads first and references B, and B in turn references A, the error will be thrown inside the B file because A has not been fully defined/executed yet.
The solution to this would be to remove all associations from the model files, and inside your app or index.js require them all, and then define their relationships.
Example
const entities = {
A: require('./src/Entity/A'),
B: require('./src/Entity/B'),
};
entities.A.belongsToMany(entities.B, {through: 'AB'});
entities.B.belongsToMany(entities.A, {through: 'AB'});
So I was getting this error and it took me some time to deal with the bug. I realised I was getting the Error because I was referencing the model wrongly. Sequelize is case sensitive so if you created the model with UpperCase ensure to keep it uniform throughout your referencing.
I would also point out you could try this out instead
User.hasMany(models.Foto ,{as: 'fotos', foreignKey: 'userId'})
It seems you need to define both ends of the relationship in the file containing the 1 part of the 1:many association. That is, the "User" file in your case.
So:
User.hasMany(Foto);
Foto.belongsTo(User);
None of the above solutions worked for my scenario (could work for other setups). I stumbled upon this article which states you have to have the models defined and exported prior to applying the associations. Using a separate extra-setup.js file to define the associations, worked for me.
https://github.com/sequelize/express-example/tree/master/express-main-example
I had lots of issues, but I switched to using the sequelize CLI which generated models in this format, I then found creating associations a lot easier as the index file took care of everything and the static associate({ PersonalDetail }) that is in the model itself already requires your models in one place all you need to do is deconstruct them, so no need to require anything at the top of the file.
This youtube video really helped me out... https://www.youtube.com/watch?v=3qlnR9hK-lQ
'use strict'
const { Model } = require('sequelize')
module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
* Helper method for defining associations.
* This method is not a part of Sequelize lifecycle.
* The `models/index` file will call this method automatically.
*/
static associate({ PersonalDetail }) {
// define association here
this.hasMany(PersonalDetail, {
foreignKey: 'userId',
//as: 'personalDetails',
})
}
}
User.init(
{
uuid: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
},
moredata below: {
type: DataTypes.STRING,
allowNull: false,
},
//createdAt/updatedAt is defined in migration and updated automatically
},
{
sequelize,
tableName: 'users',
modelName: 'User',
}
)
return User
}
I got the same type issue. All mappings were done perfectly as explained in the document.
Yet, I received the issue regarding the association.
Reason is given by Dorian in this forum.
https://stackoverflow.com/a/60760296/16790144
My approach:
models/company.js
const company = sequelize.define("company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default company;
models/client.js
const Client = sequelize.define("client", {
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
}
});
export default Client;
models/clientCompany.js
const clientCompany = sequelize.define("client_company",{
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
companyId: {
type: DataTypes.INTEGER
},
clientId: {
type: DataTypes.INTEGER
}
});
export default clientCompany;
models/index.js
import Company from './company';
import Client from './client';
import ClientCompany from './clientCompany';
Company.belongsToMany(Client, { through : ClientCompany });
Client.belongsToMany(Company, { through : ClientCompany });
export {
Company,
Client,
ClientCompany,
};
handler.js
This file contains the business logic.
import { Client, Company } from '../../models';
const company = await Company.findOne({
where: { id: companyId },
include: Client,
});