I'm implementing some tests to make sure my sequelize objects are saved correctly.
I have a very simple schema: Article <--> Users
An Article is published by ONE User
A User can publish MANY Articles
Here is my Article model definition:
module.exports = function(sequelize){
"use strict";
var Sequelize = require('sequelize');
...
var Article = sequelize.define("Article", {
slug: {
type: Sequelize.STRING,
unique: true,
comment: "Unique URL slug to access the article"
},
title: {
type: Sequelize.STRING,
unique: true,
allowNull: false,
validate: {
notEmpty: true
}
},
summary: {
type: Sequelize.TEXT,
allowNull: true
},
body: {
type: Sequelize.TEXT,
allowNull: true
},
published: {type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true},
allowComment: {type: Sequelize.BOOLEAN, allowNull: false, defaultValue: true}
}, {
freezeTableName: true,
classMethods: {
associate: function (models)
{
Article.belongsTo(models.User, {as: "Author", foreignKey: 'author_id'});
Article.hasMany(models.Comment, {as: "Comments", foreignKey: 'article_id'});
},
articlesForIndex: function()
{
return this.findAll({
where: {published: true},
order: 'createdAt DESC',
limit: 10
});
}
},
setterMethods : {
title : function(v) {
this.setDataValue('title', v.toString());
this.setDataValue('slug', slugify(v));
}
}
});
return Article;
};
What I want to do is forcing the Article to have an Author (User). With the current definition I can create Article without Author.
Here is my test that is failing:
module.exports = function (sequelize, models) {
'use strict';
var Q = require('q');
var should = require('chai').should();
describe('Article Model', function () {
describe('Validation', function () {
...
it('should not be valid without an author', function (done) {
models.Article.create({title: 'title5', summary: 'summary', body: 'body'})
.should.be.rejected.notify(done);
});
});
});
};
On fairly recent (2.0-rc2 i believe) versions of sequelize you can change the foreign key to an object:
Article.belongsTo(User, {
as: "Author",
onDelete: 'CASCADE',
foreignKey: { name:'author_id', allowNull: false }
});
You also need to add onDelete: 'CASCADE' because we can no longer set null on delete
Related
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 have 2 tables: Countries and Spots. A country can have many spots and a spot belongs to one country.
I have generated the migrations necessary with sequelize for the 2 tables:
Countries.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Countries', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
unique: true,
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Countries');
}
};
Spots.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Spots', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
allowNull: false,
type: Sequelize.STRING
},
wind: {
type: Sequelize.FLOAT
},
country_id: {
type: Sequelize.INTEGER,
references: {
model: 'Countries', // name of Target table
key: 'id', // key in Target table that we're referencing
},
onDelete: 'CASCADE',
},
createdAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
defaultValue: Sequelize.literal('NOW()'),
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Spots');
}
};
Everything works ok. I define some routes and some controllers and I try to do Spots.findAll() in my controller
const models = require('../models/index')
const Spot = models.Spot
exports.index = async (req, res, next) => {
const spots = await Spot.findAll()
res.status(200).json(spots)
}
However the query Spot.findAll() tries to ask for CountryId which is a key that obviously doesn't exist and I do not wish for it to exist.
Executing (default): SELECT `id`, `name`, `wind`, `country_id`, `createdAt`, `updatedAt`, `CountryId` FROM `Spots` AS `Spot`;
(node:13027) UnhandledPromiseRejectionWarning: SequelizeDatabaseError: Unknown column 'CountryId' in 'field list'
These are the spot and countries models:
Country.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Country = sequelize.define('Country', {
name: DataTypes.STRING
}, {});
Country.associate = function(models) {
// associations can be defined here
Country.hasMany(models.Spot)
};
return Country;
};
Spot.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Spot = sequelize.define('Spot', {
name: DataTypes.STRING,
wind: DataTypes.FLOAT,
country_id: DataTypes.INTEGER
}, {});
Spot.associate = function(models) {
// associations can be defined here
Spot.belongsTo(models.Country, {
foreignKey: 'country_id'
});
Spot.hasMany(models.Favorite)
};
return Spot;
};
I added the foreign_key attribute to belongs_to as I thought that the error surely comes from the associations(I still think it does).
Why does it happen and how to fix it?
The problem is because you are mixing everything here please follow one convention either camelCase or snack_case.
Write country_id as countryId and change your table names to lowercase and you will good to go.
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 list of many to many of the people tables and lists. I need to make a query that includes the relation table, something like:
SELECT p.id, pl.id
FROM people p inner join peopletolists pl
WHERE p.id == pl.id
My models:
Lists model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const lists = sequelize.define('lists', {
listId: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
nome: DataTypes.STRING(50),
status: DataTypes.STRING(1),
parametros: DataTypes.STRING(500)
}, {
timestamps: false,
});
lists.associate = function(models) {
lists.belongsToMany(models.people, {
through: models.peopletolists,
foreignKey: 'listId',
});
}
return lists;
};
People model:
'use strict';
module.exports = (sequelize, DataTypes) => {
const people = sequelize.define('people', {
peopleId: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
cpf: DataTypes.STRING(11),
name: DataTypes.STRING(50),
city: DataTypes.STRING(50),
}, {
timestamps: false
});
people.associate = function (models) {
people.belongsToMany(models.lists, {
through: models.peopletolists,
foreignKey: 'peopleId'
});
}
return people;
};
N:M model:
'use strict';
module.exports = function (sequelize, DataTypes) {
const peopletolists = sequelize.define("peopletolists", {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
autoIncrement: true,
unique: true,
},
listId: {
type: DataTypes.INTEGER,
references: {
model: 'lists',
key: 'listId'
},
allowNull: false
},
peopleId: {
type: DataTypes.INTEGER,
references: {
model: 'people',
key: 'peopleId'
},
allowNull: false
}
}, {
timestamps: false
});
return peopletolists;
}
Query:
router.get('/', function (req, res, next) {
model.lists.findAll({
include: [{
model: model.peopletolists,
}]
})
.then(lists => res.json({
data: lists,
}))
.catch(err => res.json({
error: err,
}))
});
Error: SequelizeEagerLoadingError
A person has many lists and lists have many people. I have not found many things in the Sequelize documentation and not many people complaining about this error, it's generally on ClassMethods, but I'm not even using it.
What's wrong?
This is how I am using it.
ArtistModel.findAll({
where: {
slug: req.params.slug
}, attributes: ['id']
, include: [{
model: Genres,
as: 'ArtistGenre',
required: false,
// attributes: ['id'],
through: {attributes: []},
}],
subQuery: false,
limit: req.query.limit,
offset: req.skip
})
Where Genre is the table that is linked with the Artist table with a many-to-many relationship through ArtistGenre
ArtistGenre Model:
const db = require('../utils/connection');
const Sequelize = require('sequelize');
let ArtistGenre = db.define('artist_genre', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
}
}, {
timestamps: false,
underscored: true
});
ArtistGenre.removeAttribute('id');
module.exports = ArtistGenre;
Artist:
const db = require('../utils/connection');
const Sequelize = require('sequelize');
module.exports = db.define('artist', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING
},
status: {
type: Sequelize.ENUM,
values: ['active', 'inactive'],
defaultValue: 'active'
},
created_at: {
type: 'TIMESTAMP',
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
allowNull: false
},
updated_at: {
type: 'TIMESTAMP',
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
allowNull: false
}
}, {
underscored: true,
timestamps: false
});
Genre Model:
const db = require('../utils/connection');
const Sequelize = require('sequelize');
module.exports = db.define('genre', {
id: {
type: Sequelize.BIGINT(20),
primaryKey: true,
autoIncrement: true
},
name: {
type: Sequelize.STRING
}
}, {
timestamps: false
});
Associations are like the following:
Artist.belongsToMany(models.genre, {as: 'ArtistGenre', through: 'artist_genre', foreignKey: 'artist_id', otherKey: 'genre_id'});
Genre.belongsToMany(models.artist, {as: 'ArtistGenre', through: 'artist_genre', foreignKey: 'genre_id', otherKey: 'artist_id'});
I've been through several questions on the site but I still can't see what I'm doing wrong here, so any help would be greatly appreciated.
I'm getting the error:
Organization (organizations) is not associated to User!
Org Model:
module.exports = function (sequelize, DataTypes) {
return sequelize.define('Organization', {
organizationID: {
primaryKey: true,
type: DataTypes.INTEGER(11),
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.STRING,
allowNull: true,
}
},
{
tableName: "spa_vOrganization",
freezeTableName: true,
classMethods: {
associate: function (models) {
Organization.hasMany(models.User, {
as: 'users',
through: models.User_Tenant_Organization,
foreignKey: 'organizationID'
});
}
},
});
};
User Model:
module.exports = function (sequelize, DataTypes) {
return sequelize.define('User', {
userID: {
primaryKey: true,
type: DataTypes.INTEGER(11),
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
}
},
{
tableName: "spa_User",
freezeTableName: true,
classMethods: {
associate: function(models) {
User.hasMany(models.Organization, { as: "organizations", through: models.User_Tenant_Organization, foreignKey: 'userID'});
}
}
}
);
};
Matrix table model:
module.exports = function (sequelize, DataTypes) {
return sequelize.define('User_Tenant_Organization', {
userTenantOrganizationID: {
primaryKey: true,
type: DataTypes.INTEGER(11),
allowNull: false,
},
userID: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
organizationID: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
tenantID: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
},
{
tableName: "spa_User_Tenant_Organization",
freezeTableName: true,
});
};
What I'm trying to do is just pull back a user with their organizations eagerly loaded. Here's what I'm using:
models.User.findOne({where: {
email: body.email
}, include: [ {model:models.Organization, as: 'organizations'}]}).complete( function (err, user) {
// do something with the user
}
I've tried with and without foreignKey definitions on both User and Organization, nothing makes any difference. I'm obviously misunderstanding something about the associations. Can anyone tell me where I'm going wrong please?
I found the problem. The associations in the above code are actually correct - what was failing was my models/index.js, which had been automatically generated by the yeoman generator-angular-express-sequelize
index.js was looping through the model files, importing them into the sequelize object and storing a copy in an array db[], then trying to run the classMethod associate(), but it was calling models.options.associate() instead of models.associate():
Object.keys(db).forEach(function (modelName) {
if (db[modelName].options.hasOwnProperty('associate')) {
db[modelName].options.associate(db);
}
});
I've fixed that by removing the ".options" and everything works fine.
Pull request to fix the problem is here for reference: https://github.com/rayokota/generator-angular-express-sequelize/pull/7