Sequelize Self Relationship with junction table - javascript

I want to build a sequelize relationship that represents : An item is composed by a specific amount of others items.
Database tables
Item (itemId, name)
Ingredient (ingredientId, itemParentId, itemChildrenId, amount)
Sequelize models
// Item.js
class Item extends Sequelize.Model { }
Item.init({
itemId: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
name: Sequelize.STRING,
}, {
sequelize: db,
})
// Ingredient.js
class Ingredient extends Sequelize.Model { }
Ingredient.init({
ingredientId: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
amount: Sequelize.INTEGER,
}, {
sequelize: db,
})
And I am just trying to write the correct sequelize association to match my database logic, so I tried :
// Association.js
Item.belongsToMany(Item, { through: Ingredient, as: 'ingredients', foreignKey: 'itemParentId' })
But I'm having this error Unknown column 'ingredients->ingredient.ingredientItemId' in 'field list', which is true but I do not know how to specify the right keys/columns.
Any help, please!

I see few problems: First, you're performing Item.init within the Ingredient model. Probably a mistake on your part. Change it to Ingredient.init.(personally i never used this "init" api, i define models differently, so i'm not sure how it works)
Second, change the primary keys of both Ingredient and Item to be just "id", like:
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
}
Also, your association isn't correct, it needs to be through a junction table:
Item.belongsToMany(Ingredient, { through: "ItemIngredient"})
Note that "through" referes here to a table name, that Sequelize will create automatically if you're using model.sync(), and if not- you will need to create it yourself(or with a migration, which i recommend), with columns: itemId, ingredientId.
You also need to add the "reverse" association, like that:
Ingredient.belongsToMany(Item, { through: "ItemIngredient"})

Related

How to Include relationship on model via enum fields in sequelize

I have a table, table1, with a status field with type enum of values ['active', 'inactive'].
I have another table, table2, with a status field with type enum of values ['active', 'inactive'].
I tried adding a relationship between them as such:
table1.hasMany(models.table2, {
foreignKey: 'status', // enum field
sourceKey: 'status' // enum field
})
when I try to query the field including the relationship as such:
let result = await models.model1.findAll({
include: [{
model: models.model2
}]
})
I get the following error message.
"operator does not exist: \"enum_table1_status\" = \"enum_table2_status\""
What I want is for each element in result, there'd be an associated table property table2 which would be an array of the rows in table2 which have the same status with the status of the parent object from table1
I know it could probably work if both tables had the same enum type assigned to them, but I don't know how to do that.
I figured out how to go about it. When creating relationships in sequelize, the types have to be the same. string - string, integer to integer, etc. enums are a bit trickier apparently. Even if 2 enums have the same values, they are of different types like in the question above. So, to create a relationship, the the columns being related should have the same enum type.
Now, sequelize, to the best of my knowledge, doesn't have the functionality of creating enum types independent of a table/model. But, it is possible to assign type to an already created enum type. in PGadmin, the created types can be found under types, under schemas, under the db. Like this:
To assign the enum type of an already created table to another simply get the enum type name from the list of types, and assign it to the type variable when creating the migration file and model. Like this:
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('table2', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
loan_type: {
// Re-using the same ENUM than `status` so that we can create a relationship between them.
type: '"public"."enum_table1_status"',
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('table2');
}
};
With this, relationships can be created on the model level between both tables.

Sequelize 6, delete cascade with M:N association

I'm trying to define some specific M:N association using sequelize v6.20.1 and i'm facing and issue...
I have 2 models, an Account model and a Group model.
My rules about these models are the following:
An account can exists without any group
An account can have multiple groups
A group can exists with a least one account associated, so that mean a group cannot exists without an account associated
A group can be associated with multiple accounts
Here is the code definition of all models and association :
const Sequelize, { Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: `./database.sqlite`,
});
/* ----- Account model ----- */
class Account extends Model {
// Some account's methods definitions...
}
Account.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
username: DataTypes.STRING,
email: DataTypes.STRING,
password: DataTypes.STRING,
}, { sequelize });
/* ----- Group model ----- */
class Group extends Model {
// Some group's methods definitions...
}
Group.init({
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
}, { sequelize });
/* ----- AccountGroup model ----- */
class AccountGroup extends Model {
}
AccountGroup.init({
AccountId: {
type: DataTypes.INTEGER,
references: {
model: Account,
key: 'id',
},
onDelete: 'RESTRICT',
},
GroupId: {
type: DataTypes.INTEGER,
references: {
model: Group,
key: 'id',
},
allowNull: false,
onDelete: 'CASCADE',
},
}, {
sequelize,
timestamps: false,
});
/* ----- Association definition ----- */
Account.belongsToMany(Group, { through: AccountGroup });
Group.belongsToMany(Account, { through: AccountGroup });
sequelize.authenticate();
sequelize.sync({ force: true };
(async () => {
const group = new Group({ name: 'Group' });
await group.save();
const account = new Account({ name: 'Doe', username: 'John', email: 'john.doe#example.com', password: 'secret' });
account.addGroup(group);
await account.save();
// some processing code...
await account.destroy();
})();
After account.destroy() finished, the AccountGroup row is successfully deleted but not the Group. And i want to delete unreferenced groups.
What am i missing ?
This is the way that the cascading deletes works. In your example, when the Account is deleted, rows in the AccountGroup table may now have an invalid value in their AccountId foreign key column. You are telling the database that when this situation occurs, delete the AccountGroup entirely. Similarly, if you delete a Group, this will cascade down and delete any AccountGroup with that Group as its GroupId.
No such issue arises for the Account or Group tables when an AccountGroup is deleted. They do not contain any foreign keys that have been made invalid.
To find the functionality that you are searching for, deleting any groups that no longer belong to an AccountGroup, you will likely want to put a separate query in your code, or you may be able to use a Hook to be executed after an Account is deleted.

sequelize find data that is associated

I have 2 models Prophet and Task and they have a m:n relationship:
Prophets
const prophet = sequelize.define('prophets', {
name: {
type: Sequelize.STRING,
primaryKey: true,
unique: true
}
});
prophet.relationship = function(task) {
prophet.belongsToMany(task, {
through: 'prophetTasks',
as: 'tasks',
foreignKey: 'prophetName'
});
};
Tasks
const task = sequelize.define('tasks', {
name: {
type: Sequelize.STRING,
primaryKey: true,
unique: true
}
});
task.relationship = function(prophet) {
task.belongsToMany(prophet, {
through: 'prophetTasks',
as: 'prophets',
foreignKey: 'taskName'
});
};
EDITED:
my problem is sometimes I have to update a prophet which might remove some relationships with tasks, but I cant figure out how to delete the tasks that have no more relationship with any prophets.
I believe I should find all tasks that doesnt belong in prophetTasks table anymore, but I dont know how to query that with sequelize
You can use ON DELETE CASCADE
So whenever the row is deleted, data containing it as a foreign key will be deleted.

Sequelize Populate Relation Query Either Table

I have the following two tables in Sequelize
const Tokens = sequelize.define("Tokens", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
active: {
type: DataTypes.BOOLEAN
}
});
and
const User = sequelize.define("Users", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
first_name: {
type: DataTypes.STRING
}
});
User.associate = models => {
models["Users"].hasMany(models["Tokens"], {foreignKey: 'userID', as: 'tokens_userid'});
};
I'm trying to run the following query in Sequelize.
const token = await db.Tokens.findOne({
where: {
id,
active: true
},
include: ["tokens_userid"]
});
But I'm getting the following error.
Error: Association with alias "tokens_userid" does not exists
My main goal is to get the user based on a Token ID. Now I would just move that association to the User table, but the problem with that later on I will want to get all the tokens for a given User ID. So I will run into this problem either way.
I tried adding the following line, but it was complaining about circular relations or something like that.
models["Tokens"].hasOne(models["User"], {foreignKey: 'userID', as: 'tokens_userid'});
How can I query either the Users or Tokens table and have it populate correctly with the relation?
I was able to solve this by adding the following line to my table.
models["Tokens"].belongsTo(models["User"], {foreignKey: 'userID', as: 'tokens_userid_from_token'});
Basically what I tried before but changed hasOne to belongsTo.
Hopefully this helps someone else.

Sequelize belongsToMany get source model by querying on target model

I have two models Brand and Campaign.
A Brand can have many Campaigns
export default(sequelize, DataTypes)=> {
const Brand = sequelize.define('Brand', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
})
Brand.associate = models=> {
Brand.belongsToMany(models.Campaign, {
through: models.CampaignBrand,
foreignKey: 'brand',
})
}
return Brand
}
A Campaign can also have many Brand
export default(sequelize, DataTypes)=> {
const Campaign = sequelize.define('Campaign', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
})
Campaign.associate = models=> {
Campaign.belongsToMany(models.Brand, {
through: models.CampaignBrand,
foreignKey: 'campaign',
})
}
return Campaign
}
And here is through model:
export default(sequelize, DataTypes)=> {
const CampaignBrand = sequelize.define('CampaignBrand', {
// see enums
status: {
type: DataTypes.INTEGER,
allowNull: false,
},
roleText: {
type: DataTypes.STRING,
},
})
CampaignBrand.associate = models=> {
CampaignBrand.belongsTo(models.Campaign, {
foreignKey: 'campaign',
targetKey: 'id',
onDelete: 'CASCADE',
})
}
return CampaignBrand
}
In case I want to get Campaigns by brand. What should I do?
I have tried query likes document mentioned but it does not work for me
With Belongs-To-Many you can query based on through relation and select specific attributes. For example using findAll with through
User.findAll({
include: [{
model: Project,
through: {
attributes: ['createdAt', 'startedAt', 'finishedAt'],
where: {completed: true}
}
}]
});
I have found some ways to work around, but it is not what I am looking for:
SOLUTION 1:
Update belongsToMany Brand to hasMany CampaignBrand and the query by CampaignBrand.brand
SOLUTION 2:
Get Campaign by querying Brand
Any other advices?
Dialect: postgres
Database version: 9.4
Sequelize version: 4.2.1
I think you don't need this association in the the through model:
CampaignBrand.associate = models=> {
CampaignBrand.belongsTo(models.Campaign, {
foreignKey: 'campaign',
targetKey: 'id',
onDelete: 'CASCADE',
})
}
You already have the belongsToMany association in the definitions of Brand and Campaign, so I think you just need to create the CampaignBrand model with your status and roleText attributes.
As I understand it, then you can query brands through campaigns and it should return each brand element and its associated campaigns,
Brand.findAll({
include: [{
model: Campaign
}]
});
This answer is kinda old, but with belongstomany associations, you can use mixins.
https://sequelize.org/api/v6/class/src/associations/belongs-to-many.js~belongstomany
const campaigns = await CampaignModel.findAll();
const campaignsBrandsModels = await campaigns.getBrandsModels({
limit:1,
through: {where:{pivot_field:'pivot_value'} } // << You want this, i think!
});
Now, remember that, the mixins (get, set, count...) set their name with inflection library, so, a way to set the names without error (or without spending a lot of time searching the name in in the console) is setting an 'as' alias in the Association
CampaignBrand.belongsToMany(models.Campaign, {
through: models.CampaignBrand,
foreignKey: 'brand',
as: 'BrandsModels' //<<<< This is Super important! at least for easy usage
})
You can achieve this with an include in the findAll method too, BUT!
the 'limit: #' part will not work! (Will give you the error of "This only works with hasMany because separate: true")
I really hope this can help anyone, seya!

Categories