Sequelize M:N association not creating through table record - mysql

When I create a recipe with associated tags using the through option, no record is created in the joining table in the mysql database I'm connected to.
Here are my models definitions:
export const Recipe = sequelize.define('Recipe', {
// Model attributes are defined here
title: {
type: DataTypes.STRING,
allowNull: false
},
image: {
type: DataTypes.STRING,
allowNull: true
},
prepTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
cookTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
totalTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
servings: {
type: DataTypes.INTEGER,
allowNull: false
},
rating: {
type: DataTypes.INTEGER,
allowNull: false
},
notes: {
type: DataTypes.STRING, allowNull: true
},
}, {
// Other model options go here
tableName: 'Recipes'
});
export const Tag = sequelize.define('Tag', {
// Model attributes are defined here
name: {
type: DataTypes.STRING,
allowNull: false
},
}, {
// Other model options go here
tableName: 'Tags'
});
export const RecipeTag = sequelize.define('RecipeTag', {
// Model attributes are defined here
}, {
// Other model options go here
timestamps: false,
tableName: 'RecipeTags'
});
Here are my associations:
Recipe.belongsToMany(Tag, {
through: RecipeTag,
foreignKey: 'recipeId',
as: 'tags'
})
Tag.belongsToMany(Recipe, {
through: RecipeTag,
foreignKey: 'tagId',
as: 'recipes'
})
Here is the create call:
Recipe.create(args, {
model: Tag,
through: RecipeTag,
as: 'tags'
});
And here is the data:
{
"title": "Test Recipe",
"image": "test",
"prepTime": 20,
"cookTime": 40,
"totalTime": 60,
"servings": 2,
"rating": 5,
"categoryId": 1,
"tags": [
{
"name": "New tag",
"id": 1
}
],
}
With this set up the create method only creates a new recipe. How can I use the create method to add a record to the joining RecipeTags table at the same time as creating a new recipe? I've managed to get it working by doing something like this:
args.tags.map(async (tag: { tagId: number }) => {
await RecipeTag.create({tagId: tag.tagId, recipeId: recipe.id})
});
But I'd rather have it done in the create if it's possible.

You need to wrap the association options with include.
Recipe.create(args, {
include: {
model: Tag,
through: RecipeTag,
as: 'tags'
}
});
UPDATE:
In order to prevent the duplicates, you can add ignoreDuplicates option and data must include the primary key value.
{
"title": "Test Recipe",
...
"tags": [
{
"name": "New tag",
"id": 1 # this is important
}
]
}
Then
Recipe.create(args, {
include: {
model: Tag,
through: RecipeTag,
as: 'tags',
ignoreDuplicates: true // Add this
}
});
There were some bugs for this option, I suggest you to use the newer version of Sequelize, if you haven't updated lately.

Related

Sequelize M:N association not updating through table record

When I try to update a recipe with associated tags using the through option, no record is updated in the joining table in the mysql database I'm connected to.
Here are my models definitions:
export const Recipe = sequelize.define('Recipe', {
// Model attributes are defined here
title: {
type: DataTypes.STRING,
allowNull: false
},
image: {
type: DataTypes.STRING,
allowNull: true
},
prepTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
cookTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
totalTime: {
type: DataTypes.DOUBLE,
allowNull: false
},
servings: {
type: DataTypes.INTEGER,
allowNull: false
},
rating: {
type: DataTypes.INTEGER,
allowNull: false
},
notes: {
type: DataTypes.STRING, allowNull: true
},
}, {
// Other model options go here
tableName: 'Recipes'
});
export const Tag = sequelize.define('Tag', {
// Model attributes are defined here
name: {
type: DataTypes.STRING,
allowNull: false
},
}, {
// Other model options go here
tableName: 'Tags'
});
export const RecipeTag = sequelize.define('RecipeTag', {
// Model attributes are defined here
}, {
// Other model options go here
timestamps: false,
tableName: 'RecipeTags'
});
Here are my associations:
Recipe.belongsToMany(Tag, {
through: RecipeTag,
foreignKey: 'recipeId',
as: 'tags'
})
Tag.belongsToMany(Recipe, {
through: RecipeTag,
foreignKey: 'tagId',
as: 'recipes'
})
Here is the update call:
Recipe.update(args, {
include: [{
model: Tag,
through: RecipeTag,
as: 'tags',
where: {
recipeId: args.id
}
}],
where: {
id: args.id
},
});
Only one update mysql call is executed on the Recipes table. Is it possible to update the RecipeTags through table record in the same update call?

How to include model in belongstToMany relation in Sequelize

I want to add in my sequelize query model, but i don't know how to do this correctly.
I have two tables: Regions and Countries. There is a relation between them called belongsToMany that is going through the table region_countries, there i have foreign keys region_id and country_id. For Region and Countries i have table with translation, for example: table translate_trgions has region and language_code name (en, fr, ru, etc.). And i am getting list of regions with countries with their translations.
My Region model
class Region extends Model {}
Region.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
},
{ sequelize, timestamps: false, tableName: 'regions', modelName: 'Region' }
);
class RegionTranslate extends Model {}
RegionTranslate.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
type: DataTypes.STRING,
allowNull: true,
},
region_id: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: Region,
key: 'id',
},
},
language_code: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: Language,
key: 'code',
},
},
},
{
sequelize,
timestamps: false,
tableName: 'regions_description',
modelName: 'RegionTranslate',
}
);
class RegionsCountries extends Model {}
RegionsCountries.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
region_id: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: Region,
key: 'id',
},
},
country_id: {
type: DataTypes.INTEGER.UNSIGNED,
references: {
model: Country,
key: 'id',
},
},
},
{
sequelize,
timestamps: false,
tableName: 'region_countries',
modelName: 'RegionsCountries',
}
);
Region.hasMany(RegionTranslate, {
foreignKey: 'region_id',
as: 'translate_regions',
});
RegionTranslate.belongsTo(Region, { foreignKey: 'region_id' });
Region.belongsToMany(Country, {
through: RegionsCountries,
foreignKey: 'region_id',
});
Country.belongsToMany(Region, {
through: RegionsCountries,
foreignKey: 'country_id',
});
Region.belongsToMany(CountryTranslate, {
through: RegionsCountries,
as: 'translate_countries',
foreignKey: 'country_id',
otherKey: 'id',
});
My Country Model
class Country extends Model {}
Country.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
code: {
type: DataTypes.STRING,
allowNull: false,
},
unicode: {
type: DataTypes.INTEGER,
allowNull: false,
},
},
{ sequelize, timestamps: false, tableName: 'countries', modelName: 'Country' }
);
class CountryTranslate extends Model {}
CountryTranslate.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
language_code: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Language,
key: 'code',
},
},
country_id: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Country,
key: 'id',
},
},
name: {
type: DataTypes.BOOLEAN,
allowNull: true,
},
description: {
type: DataTypes.BOOLEAN,
allowNull: true,
},
},
{
sequelize,
timestamps: false,
tableName: 'countries_description',
modelName: 'CountryTranslate',
}
);
Country.hasMany(CountryTranslate, {
foreignKey: 'country_id',
as: 'translate_countries',
});
CountryTranslate.belongsTo(Country, { foreignKey: 'country_id' });
Country.belongsToMany(Region, {
through: RegionsCountries,
foreignKey: 'country_id',
});
My query in RegionController
const regions = await Region.findAll({
attributes: {
include: [[Sequelize.literal('translate_regions.name'), 'name']],
},
include: [
{
model: Country,
attributes: {
include: [[Sequelize.literal('translate_countries.name'), 'name']],
},
through: {
attributes: [],
},
},
{
model: RegionTranslate,
as: 'translate_regions',
where: { language_code: language },
attributes: [],
},
{
model: CountryTranslate,
as: 'translate_countries',
},
],
});
The output i'm getting
"regions": [
{
"id": 4,
"name": "Europe",
"Countries": [
{
"id": 3,
"code": "dza",
"unicode": "12",
"name": "Ангола"
}
],
"translate_countries": [
{
"id": 10,
"language_code": "ru",
"country_id": 5,
"name": "Ангола",
"description": null,
"RegionsCountries": {
"id": 10,
"region_id": 4,
"country_id": 4
}
}
]
}
]
The output that i want
"regions": [
{
"id": 4,
"name": "Europe",
"Countries": [
{
"id": 4,
"code": "and",
"unicode": "20",
"name": "Andorra"
}
]
}
]
When setting up your Model associations for many-to-many through a relational table, you need to specify the columns in the relational table that is the foreign key for your source table primary key, as well as the column that is the "other" foreign key for the related table primary key. It's helpful to specify an as property as well to identify the relationship.
This is an example, check other relationships as well.
// use otherKey to indicate the relationship
// country.id -> country_id, region_id -> region.id
Country.belongsToMany(Region, {
as: 'regions',
through: RegionsCountries,
foreignKey: 'country_id',
otherKey: 'region_id'
});
When you query for related records we can no specify the through and as property on the query.
Country.findByPk(countryId, {
include: [{
model: Region,
through: RegionsCountries,
as: 'regions',
}],
});

Sequelize: Using include for a joined table

I have 4 models: User, Skill, SkillToLearn, and SkillToTeach. The SkillToLearn and SkillToTeach contain two columns userId and skillId to keep track of a skill (given by skillId) that a user (given by userId) wants to learn or teach.
My goal is to use Sequelize's include statement such that I can return all users' data, including a list of skills they are learning and a list of skills they are teaching. I want a response similar to the following:
[
{
"id": 1,
"username": "janedoe",
"skillsLearning": [
{
"skillId": 1,
"name": "arts"
}
],
"skillsTeaching": [
{
"skillId": 2,
"name": "cooking"
}
]
}
]
However, instead, I'm getting the following response:
[
{
"id": 1,
"username": "janedoe",
"skillsLearning": [
{
"skillId": 1,
"Skill": {
"name": "arts"
}
}
],
"skillsTeaching": [
{
"skillId": 2,
"Skill": {
"name": "cooking"
}
}
]
}
]
I have tried to include Skill instead of SkillToLearn and SkillToTeach but I got an error saying that Skill is not associated to User. I am uncertain if my schemas and associations are incorrect.
User.js
const User = sequelize.define("User", {
username: {
type: DataTypes.STRING,
allowNull: false,
unique: true
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
description: {
type: DataTypes.TEXT
}
}, {
freezeTableName: true
});
User.associate = function (models) {
User.hasMany(models.SkillToLearn, {
as: "skillsLearning",
onDelete: "cascade",
foreignKey: "userId",
sourceKey: "id"
});
User.hasMany(models.SkillToTeach, {
as: "skillsTeaching",
onDelete: "cascade",
foreignKey: "userId",
sourceKey: "id"
});
};
Skill.js
const Skill = sequelize.define("Skill", {
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true
}
}, {
freezeTableName: true
});
Skill.associate = function (models) {
Skill.hasMany(models.SkillToLearn, {
as: "usersLearning",
onDelete: "cascade",
foreignKey: "skillId",
sourceKey: "id"
});
Skill.hasMany(models.SkillToTeach, {
as: "usersTeaching",
onDelete: "cascade",
foreignKey: "skillId",
sourceKey: "id"
});
};
SkillToLearn.js
const SkillToLearn = sequelize.define("SkillToLearn", {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
skillId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
}
}, {
freezeTableName: true
});
SkillToLearn.associate = function (models) {
SkillToLearn.belongsTo(models.User, {
foreignKey: "userId",
targetKey: "id"
});
SkillToLearn.belongsTo(models.Skill, {
foreignKey: "skillId",
targetKey: "id"
});
};
SkillToTeach.js
const SkillToTeach = sequelize.define("SkillToTeach", {
userId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
},
skillId: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true
}
}, {
freezeTableName: true
});
SkillToTeach.associate = function (models) {
SkillToTeach.belongsTo(models.User, {
foreignKey: "userId",
targetKey: "id"
});
SkillToTeach.belongsTo(models.Skill, {
foreignKey: "skillId",
targetKey: "id"
});
};
dataRoutes.js
db.User
.findAll({
attributes: ["id", "username"],
include: [
{
model: db.SkillToLearn,
as: "skillsLearning",
attributes: ["skillId"],
include: [
{
model: db.Skill,
attributes: ["name"]
}
]
},
{
model: db.SkillToTeach,
as: "skillsTeaching",
attributes: ["skillId"],
include: [
{
model: db.Skill,
attributes: ["name"]
}
]
}
]
})
.then(results => res.json(results));
});
Is there a way for me to get the skill's name without having it in an object? Should I just do multiple queries and construct the response using my own object literal? Thank you!

Sequelize 4 - belongs-To-Many insert issue on eagger loading

Hi Folks, I have these two models:
const media = sequelize.define('media', {
id: { type: Sequelize.UUID, primaryKey: true, defaultValue: Sequelize.UUIDV4 },
name: { type: Sequelize.STRING, allowNull: false, unique:'compositeIndex' },}, { freezeTableName: true });
const mediaGenre = sequelize.define('mediaGenre', {
id: { type: Sequelize.UUID, primaryKey: true, defaultValue: Sequelize.UUIDV4 }}, { freezeTableName: true, name: { plural: 'mediaGenre' } });
and these two relations:
media.hasMany(mediaGenre, as: 'mGenre');
mediaGenre.belongsTo(media);
media.belongsToMany(genre, { through: mediaGenre });
genre.belongsToMany(media, { through: mediaGenre });
when I want to create a media I do:
MediaModels.media.create(
{name: 'Hulk',
mediaGenre: [
{ genreId: '021baab5-7fc6-4b06-aca5-e4b1ed1f3ce4' },
{ genreId: '03f069a4-dc52-4ab5-82d3-6bcd67d2d29e' }]},
{
include: [
{model: MediaModels.mediaGenre, as: 'mGenre'}
]
}
);
These has been working with sequelize 3 and recently I updated to sequelize 4.4.2 and it is not throwing error but the table mediaGenre it's not been populated.
Any ideas of what could be the error?
Have you tried to sync after you referenced the association? I had this issue and when i called to sync (with force false, this is important if tou dont want to erase your schema and to be recreated), the junction table was created.
If you are using sequelize-cli,also notice that in this version, classMethods are not longer supported. Instead, you shold add a method called associate directly to your model, which will be called in your models/index.js.

Sequelize says instanceMethod is not defined

I'm using sequelize to connect to a mysql db for development. I have a model called Dealer:
'use strict';
module.exports = function(sequelize, DataTypes) {
var Dealer = sequelize.define('Dealer', {
id: { allowNull: false, autoIncrement: true,
primaryKey: true, type: DataTypes.INTEGER.UNSIGNED },
...
created_at: { allowNull: false, type: DataTypes.DATE },
updated_at: { allowNull: false, type: DataTypes.DATE }
},
{underscored: true},
{
classMethods: {
associate: function(models) {
Dealer.hasMany(models.Job);
}
},
instanceMethods: {
getAllClientData: function(){
leads = [];
...
return leads;
},
}
});
return Dealer;
};
When I try to call the instance method on an object returned by a sequelize query in my dealerController.js file:
dealer.getAllClientData()
I get the error:
Unhandled rejection TypeError: dealer.getAllClientData is not a function
When i print the returned JSON to the console it reads as such:
{ dataValues:
{ id: 1,
....
}
...
'$modelOptions':
{ timestamps: true,
instanceMethods: {},
classMethods: {},
validate: {},
freezeTableName: false,
underscored: true,
underscoredAll: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: { id: '1' },
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: [],
hooks: {},
indexes: [],
name: { plural: 'Dealers', singular: 'Dealer' },
omitNul: false,
...
}
...
}
Obviously my instanceMethod is not defined, and according to the sequelize docs I should have getters and setters available too.
I don't understand what step i'm missing here as I've read through much of the sequelize docs and even used their cli to generate the models and migrations.
Any thoughts?
Edit:
Here is what is output to log for dealer.prototype
{ _customGetters: {},
_customSetters: {},
validators: {},
_hasCustomGetters: 0,
_hasCustomSetters: 0,
rawAttributes:
{ id:
{ allowNull: false,
autoIncrement: true,
primaryKey: true,
type: [Object],
Model: Dealer,
fieldName: 'id',
_modelAttribute: true,
field: 'id' },
///Other Attributes
},
_isAttribute: { [Function] cache: MapCache { __data__: [Object] } },
Model: Dealer,
$Model': Dealer }
After reading the docs a little further and looking at some other model definitions I discovered that the issue was I had defined my model incorrectly.
In my definition above you'll notice I wrapped the underscored: true option in brackets, followed by my classMethods and instanceMethods wrapped in another set of brackets.
This is incorrect. The proper way to define a sequelize model is with two sets of brackets, the first containing your model attributes and the second containing all other options, including methods.
'use strict';
module.exports = function(sequelize, DataTypes) {
var Dealer = sequelize.define('Dealer', {
id: { allowNull: false, autoIncrement: true,
primaryKey: true, type: DataTypes.INTEGER.UNSIGNED },
...
created_at: { allowNull: false, type: DataTypes.DATE },
updated_at: { allowNull: false, type: DataTypes.DATE }
},
{
underscored: true,
classMethods: {
associate: function(models) {
Dealer.hasMany(models.Job);
}
},
instanceMethods: {
getAllClientData: function(){
leads = [];
...
return leads;
},
}
});
return Dealer;
};