First of all, I'm rather new to Node.JS and even newer to Sequelize, and this has been bothering me for a while. I have the following Model entities:
Match.js
module.exports = function(sequelize, DataTypes) {
var Match = sequelize.define('Match', {
matchId: {
type: DataTypes.BIGINT,
field: 'match_id'
},
server: {
type: DataTypes.STRING
},
(...)
rankedBoo: {
type: DataTypes.BOOLEAN,
field: 'ranked_boo'
}
}, {
classMethods: {
associate: function(models) {
Match.belongsToMany(models.Summoner, {as: 'Participants', through: 'SummonerMatch'});
}
},
freezeTableName: true,
underscored: true
});
return Match;
};
Summoner.js
module.exports = function(sequelize, DataTypes) {
var Summoner = sequelize.define('Summoner', {
summonerId: {
type: DataTypes.INTEGER,
field: 'summoner_id'
},
server: {
type: DataTypes.STRING
},
summonerName: {
type: DataTypes.STRING,
field: 'summoner_name'
},
mainChampion: {
type: DataTypes.INTEGER,
field: 'main_champion'
}
}, {
classMethods: {
associate: function(models) {
Summoner.belongsToMany(models.Match, {as: 'SummonerMatches', through: 'SummonerMatch'});
Summoner.hasOne(models.RankedStats);
Summoner.hasMany(models.RankedHistory, { as: { singular: 'RankedHistory', plural: 'RankedHistory' }});
}
},
freezeTableName: true,
underscored: true
});
return Summoner;
};
SummonerMatch.js
module.exports = function(sequelize, DataTypes) {
var SummonerMatch = sequelize.define('SummonerMatch', {
championId: {
type: DataTypes.INTEGER,
field: 'champion_id'
},
role: {
type: DataTypes.STRING
},
(...)
sightWardsBought: {
type: DataTypes.INTEGER,
field: 'sight_wards_bought'
}
}, {
freezeTableName: true,
underscored: true
});
return SummonerMatch;
};
Now I'm trying to create a new match, and associate it with a summoner, and I'm doing the following:
summoner.createSummonerMatch(matchInfo, matchDetails).then(
function () {
callback();
return null;
});
Where matchInfo contains the attributes of the "Match" entity, and matchDetails contains the attributes of the "SummonerMatch" entity.
This is fine and all, but it doesn't check if the match already exists, so I'm trying to use findOrCreate here.
models.Match.findOrCreate({
include: [ {model: models.Summoner, as: 'Participants'}],
where: { matchId: matchInfo.matchId, server: matchInfo.server },
defaults: {
matchMode: queueType,
matchDate: new Date(matchCreation),
matchDuration: matchDurationInSeconds,
rankedBoo: rankedBoo
}
}).spread(function(match, created) {
console.log(created)
});
This almost does the trick (creates a new row in the Match table, but not in SummonerMatch). How would I proceed to insert information into SummonerMatch as well? Tried a few things (adding attributes to defaults, switching it to an array, tweaking around with the include, but no success so far.
I'm prolly missing out on something, but I can't figure out what. Any help is much appreciated :)
[EDIT] In case someone comes here looking for answers on how to do it, this works, but I'm not sure if it is the best approach:
models.Match.findOrCreate({
include: [ {model: models.Summoner, as: 'Participants'}],
where: { matchId: matchInfo.matchId, server: matchInfo.server },
defaults: {
matchMode: queueType,
matchDate: new Date(matchCreation),
matchDuration: matchDurationInSeconds,
rankedBoo: rankedBoo
}
}).spread(function(match, created) {
match.addParticipant(summoner, matchDetails).then(
function () {
console.log('Done')
}
)
});
It is creating a match if it doesn't exist, and adding a participant afterwards. Isn't there a way to do it all at once?
Related
I have defined two table with many-to-many association between them.
create-image-migration.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Images', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Images');
}
};
create-category-migration.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Categories', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
name: {
type: Sequelize.STRING
},
...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Categories');
}
};
Now JOIN table is defined as follows
create-image-category-migration.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('ImageCategories', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
imageId: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'Images', key: 'id' }
},
categoryId: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'Categories', key: 'id' }
},
...
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('ImageCategories');
}
};
image-category-model.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const ImageCategory = sequelize.define('ImageCategory', {
imageId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'Image', key: 'id' },
},
categoryId: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'Category', key: 'id' },
},
...
}, {});
ImageCategory.associate = function(models) {
models.Image.belongsToMany(models.Category, { through: ImageCategory });
models.Category.belongsToMany(models.Image, { through: ImageCategory });
};
return ImageCategory;
};
Now when I run the migration the join table is created with respective column name as specified in migration file i.e. in camel case.
But when I run the following bulkCreate command in sequelize to insert data
await db.ImageCategory.bulkCreate([
{ imageId: 'someId', categoryId: topicId, categoryType: 'topic' },
{ imageId: 'someId', categoryId: styleId, categoryType: 'style' },
]);
I am get the following error:
sqlMessage: "Column 'imageId' specified twice",
sql: "INSERT INTO `ImageCategories` (`imageId`,`categoryId`,`categoryType`,`createdAt`,`updatedAt`,`ImageId`) VALUES (5,'22','topic','2022-11-26 08:11:41','2022-11-26 08:11:41',NULL),(5,'27','style','2022-11-26 08:11:41','2022-11-26 08:11:41',NULL);"
},
As we can see here "ImageId" is automatically added by sequelize. So my question is if there is a convention followed by sequelize to name the column name while creating join table since it is not mention anywhere on its documentation.
By default Sequelize generates foreign key names in the pascal case. You do have foreign keys in the junction table that differ with the letter case.
So you just need to indicate foreign keys explicitly in both associations:
ImageCategory.associate = function(models) {
models.Image.belongsToMany(models.Category, { through: ImageCategory, foreignKey: 'imageId' });
models.Category.belongsToMany(models.Image, { through: ImageCategory, foreignKey: 'categoryId' });
};
I am trying to associate two tables in Sequelize but I am getting the SequelizeEagerLoadingError that one table is not associated to another despite trying all the available fixes on this platform.
I have two tables, User and Item.
User (user.js)
const User = dbconnection.sequelize.define('users', {
id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true},
name: {
type: Sequelize.STRING(80),
allowNull: false
},
email: {
type: Sequelize.STRING(120),
allowNull: false,
unique: true
},
dob: {
type: Sequelize.DATEONLY,
allowNull: false
},
password: {
type: Sequelize.STRING(256),
allowNull: false
}
});
User.associate = models => {
User.hasMany(models.Item, { as: 'items',foreignKey: 'user_id' })
}
dbconnection.sequelize.sync({ force: false })
.then(() => {
//console.log('Table created!')
});
module.exports = {
User
};
Item (item.js)
const Item = dbconnection.sequelize.define('items', {
id: { type: Sequelize.INTEGER, unique: true, autoIncrement: true, primaryKey: true},
item: {
type: Sequelize.STRING(80),
allowNull: true
},
item_type: {
type: Sequelize.STRING(10),
allowNull: false
},
comment: {
type: Sequelize.STRING(1000),
allowNull: true
},
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
references: { model: 'users', key: 'id' }
},
});
Item.associate = models => {
Item.belongsTo(models.User, { as: 'users',foreignKey: 'user_id' })
}
dbconnection.sequelize.sync({ force: false })
.then(() => {
// console.log('Table created!')
})
});
module.exports = {
Item
};
User hasMany(Item) while Item belongsTo(User) as shown above.
However, when I make a query to the Item table (as below),
const usersdb = require('./userdb')
const itemsdb = require('./itemdb')
class ItemsController {
static async getAllItems(req, res, next) {
try{
let allitems = await itemsdb.Item.findAll({
include: [{
model: usersdb.User
}]
})
return {items: allitems, status: true}
}
catch (e) {
return {items: e, status: false}
}
}
}
module.exports = ItemsController;
I get the SequelizeEagerLoadingError that "users is not associated to items!"
I have tried all the available fixes including this and this among others but to no success.
I have finally found a workaround. First, I dropped the tables and discarded the model definitions. Second, I generated migrations and models using the sequelize model:create --name ModelName --attributes columnName:columnType command. I then used the generated models to associate the two tables just as I had done earlier. Lastly, I ran the sequelize db:migrate command to create the tables and on running the query, it worked!
Earlier, I was creating the models manually. I was also creating the tables using the sequelize.sync({force: false/true}) command after loading the models.
User Model (user.js)
'use strict';
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define('User', {
email: {
type: DataTypes(120),
allowNull: false,
unique: true
},
dob: {
type: DataTypes.DATEONLY,
allowNull: false
},
password: {
type: DataTypes.STRING(256),
allowNull: false
}
}, {});
User.associate = function(models) {
User.hasMany(models.Item, {as: 'Item', foreignKey: 'user_id'})
};
return User;
};
Item model (item.js)
'use strict';
module.exports = (sequelize, DataTypes) => {
const Item = sequelize.define('Item', {
item: {
type: DataTypes.STRING(80),
allowNull: true
},
item_type: {
type: DataTypes.STRING(10),
allowNull: false
},
comment: {
type: DataTypes.STRING(1000),
allowNull: true
},
user_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: { model: 'User', key: 'id' }
}
}, {});
Item.associate = function(models) {
Item.belongsTo(models.User, { as: 'User',foreignKey: 'user_id' })
};
return Item;
};
Query (queryitem.js)
const Item = require('../models').Item
const User = require('../models').User
class ItemsController {
static async getAllItems() {
try{
let allitems = await Item.findAll({
include: [{
model: User,
as: 'User'
}]
})
return {items: allitems, status: true}
}
catch (e) {
return {items: e, status: false}
}
}
}
module.exports = ItemsController;
I have 3 models that work like a tree: Plants, Genre and family.
Each family can have a lot of genres each genre is associated to 1 family.
Same for Genre, each 1 can have a lot of plants and 1 plant can have 1 genre.
So based on that, i have this models:
Plant
"use strict";
var sequelize = require('./index');
var bcrypt = require('bcrypt-nodejs');
var User = require('./User');
module.exports = function (sequelize, DataTypes) {
var Plant = sequelize.define("Plant", {
specie: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: "No description for this plant yet"
},
directory: {
type: DataTypes.STRING,
allowNull: false
},
genreId: {
type: DataTypes.INTEGER,
allowNull: true
}
},
{
associate: function (models) {
Plant.hasMany(models.Foto, { foreignKey: "plantId", as: 'fotos' });
}
}
);
Genre
module.exports = function (sequelize, DataTypes) {
var Genre = sequelize.define("Genre", {
name: {
type: DataTypes.STRING,
allowNull: false
},
familyId: {
type: DataTypes.INTEGER,
allowNull: true
},
directory: {
type: DataTypes.STRING,
allowNull: false
}
},
{
associate: function (models) {
Genre.hasMany(models.Plant, { foreignKey: "genreId", as: 'plant' });
}
}
);
Family
module.exports = function (sequelize, DataTypes) {
var Family = sequelize.define("Family", {
name: {
type: DataTypes.STRING,
allowNull: false
},
directory: {
type: DataTypes.STRING,
allowNull: false
}
},
{
associate: function (models) {
Family.hasMany(models.Genre, { foreignKey: "familyId", as: 'genre' });
}
}
);
now, i do a query where i want to get all data related to the plant(genre and family) so i pass the id for the plant in the routing, via req.params.id.
after that i try to do a include so i can get the data with eager loading, because i need to get a json with all the data related to the plant.
But i can't get any data related to the other models, just with the specific plant table, any help?
Here is the controller code on the server:
specificPlant: function (req, res, next) {
Plant.findAll({
where: {
id: req.params.id,
},
include: [{ all: true }]
}).then(function (plant) {
console.log(plant);
return res.send(plant);
}).catch(function (err) {
return res.status(400).send({ message: err.stack }); //
})
}
First, define associations that will allow you to get data Plant->Genre->Family
Plant.hasMany(models.Genre, {foreignKey: "genreId", as: 'genre' });
Genre.hasMany(models.Family, { foreignKey: "familyId", as: 'family' });
Then you can query
Plant.findAll({
where: {
id: req.params.id,
},
include: [{
model: Genre,
as: 'genre',
include: [{
model: Family,
as: 'family'
}]
}]
}).then(function (plant) {
//plant
//plant.genre
//plant.genre.family
});
I am relatively new to NodeJS and SequelizeJS and am facing a hasOne issue with a query I am building and I'd like to know your thoughts about this issue to find out where I gone wrong and the correct way to implement this query.
Association Here
The models where generated using sequelize-auto (pg-hstore).
Bloco Model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('bloco_condominio', {
id_bloco: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
id_condominio: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'condominio',
key: 'id_condominio'
}
},
nm_bloco: {
type: DataTypes.STRING,
allowNull: true
},
ic_status: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "A"
}
}, {
tableName: 'bloco_condominio'
});
};
Apartamento Model:
module.exports = function(sequelize, DataTypes) {
return sequelize.define('apartamento', {
id_apartamento: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
id_condominio: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: 'condominio',
key: 'id_condominio'
}
},
nu_apto: {
type: DataTypes.STRING,
allowNull: true
},
id_bloco: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: 'bloco_condominio',
key: 'id_bloco'
}
},
ic_status: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: "A"
},
dt_incl: {
type: DataTypes.TIME,
allowNull: false,
defaultValue: sequelize.fn('now')
},
dt_ult_alt: {
type: DataTypes.TIME,
allowNull: false,
defaultValue: sequelize.fn('now')
}
}, {
tableName: 'apartamento'
});
};
Apartamento Service:
"use strict";
var model = require('../models');
var Utils = require('../utils/utils');
var service = {};
var Apartamento = model.apartamento;
var Bloco = model.bloco_condominio;
var Morador = model.morador;
var Pessoa = model.pessoa;
//Incluir relação OneToMany
Apartamento.hasMany(Morador, { as: "Moradores", foreignKey: 'id_apartamento' });
Morador.belongsTo(Apartamento, { foreignKey: 'id_apartamento' });
Morador.hasMany(Pessoa, { as: "Pessoa", foreignKey: 'id_pessoa' });
Pessoa.belongsTo(Morador, { foreignKey: 'id_pessoa' });
Bloco.hasMany(Apartamento, { as: "Bloco", foreignKey: 'id_bloco' });
Apartamento.hasMany(Bloco, { foreignKey: 'id_bloco' });
service.getApartamentoById = function(idApartamento) {
return Apartamento.findById(idApartamento, {
include: [
{ model: Morador, as: 'Moradores', include: [
{ model: Pessoa, as: 'Pessoa'}
]},
{ model: Bloco, as: 'Bloco' }
]
})
.then(function(data) {
return data;
})
.catch(function(err) {
throw 'Erro ao consultar apartamento por ID: ' + err.message + ' - Request: '+JSON.stringify(idApartamento);
});
};
I can perfectly retrieve the other hasMany associations, but still hasn't found a way to do so in the reverse way.
Do you guys have any idea of how I should approach this issue in the correct manner?
Thanks in advance for your help!
Best regards,
Enrico Bergamo
To make it simpler for me (only knowing English), I've grabbed the following from Google translate:
Pessoa: Person
Morador: Dweller
Bloco: Block
Apartmento: Apartment
So, Dweller can have many People, an Apartment can have many Dwellers and a Block can have many Apartments.
Your definition on the other models indicates they're all 1:m, so I followed that assumption for Apartments and Blocks.
With that in mind, the following should work.
Bloco.hasMany(Apartamento, { as: "Apartmento", foreignKey: 'id_bloco' });
Apartamento.belongsTo(Bloco, { foreignKey: 'id_bloco' });
Note: I've changed the as: "Bloco" to as: "Apartmento" and the second hasMany to belongsTo. This might be where your issues were coming from.
Edit: The method to access the Apartments that belong to a Block is:
bloco.getApartmento(options)
I have this working with this promise chain:
Bloco.create()
.then(block => {
return Promise.all([
block,
Apartamento.bulkCreate([{
id_bloco: block.id_bloco
}, {
id_bloco: block.id_bloco
}, {
id_bloco: block.id_bloco
}, {
id_bloco: block.id_bloco
}, {}])
])
})
.spread((bloco, apartment) => {
return bloco.getApartamento()
})
.then(apartments => {
console.log(apartments.length); --> Logs 4 which matches the bulk create.
})
If I've misinterpreted, and it should be an n:m relationship (Apartments/Blocks), then you should use belongsToMany on each model and identify the through option.
Bloco.belongsToMany(Apartamento, {
as: "Apartmentos",
foreignKey: 'id_bloco',
through: "BlocoAparmento"
});
Apartamento.belongsToMany(Bloco, {
as: "Blocos",
foreignKey: 'id_apartmento',
through: "BlocoAparmento"
});
This will create an n:m joining table called "BlockApartmento". If you define that model, and use the model instead of the string, you'll have complete control over the models settings.
This will give you the Bloco.getApartmentos( methods as well as opposite (Apartmento.getBlocos() along with setAssociation, addAssoc... etc
http://docs.sequelizejs.com/manual/tutorial/associations.html#belongs-to-many-associations
I have two tables: users and alerts. Users should be able to upvote and downvote on alerts.
My models are defined like that:
Alert
module.exports = function(sequelize, DataTypes) {
var Alert = sequelize.define("alert", {
ID: { type: DataTypes.BIGINT(11).UNSIGNED, primaryKey: true, autoIncrement: true },
title: DataTypes.STRING,
alert_type: DataTypes.INTEGER
},
{
classMethods: {
associate: function(models) {
Alert.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
}
}
});
return Alert;
};
User
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define("user", {
ID: { type: DataTypes.BIGINT(11).UNSIGNED, primaryKey: true, autoIncrement: true },
user_login: DataTypes.STRING(50),
user_pass: DataTypes.STRING(50),
user_email: DataTypes.STRING(50),
},
{
classMethods: {
associate: function(models) {
User.hasMany(models.alert);
}
}
});
return User;
};
What's the way of doing it using sequelize.js and MySQL(InnoDB)?
Should I use belongToMany() ?
Something like :
module.exports = function(sequelize, DataTypes) {
var AlertVote = sequelize.define("user_alert_vote", {
ID: { type: DataTypes.BIGINT(11).UNSIGNED, primaryKey: true, autoIncrement: true },
vote_up: DataTypes.INTEGER,
vote_down: DataTypes.INTEGER
},
{
classMethods: {
// belongToMany(models.alert, ...);
// belongToMany(models.user, ...);
}
});
return AlertVote ;
};
You have here a 'many-to-many' relationship between a User and an Alert:
A User can vote zero or many Alerts
An Alert can be voted by zero or many Users
In addition, the relationship has properties that are upVote and downVote (I will not discussion your design to have these two properties as I'm not aware about the details of the use case).
As such, you need to use .belongToMany() on User and Alert.
A abbreviated code would be as follows:
var User = sequelize.define('User', {})
var Alert = sequelize.define('Alert', {})
var AlertVote = sequelize.define('AlertVote', {
upVote: DataTypes.INTEGER,
downVote: DataTypes.INTEGER,
})
Alert.belongsToMany(User, {through: 'AlertVote'});
User.belongsToMany(Alert, {through: 'AlertVote'});
For more details, please refer to Belongs-To-Many associations from the official documentation.