Sequelize: Defining Associations - node.js

Reading the documentation of Sequelize I'm in some level confused, what Sequelize will provide automatically for us and what we need to explicitly tell it.
I have two models: User and Post. As you have guessed a User can have multiple Posts and a Post belongs only to one User. Setting the respective relationships will look so:
Post.associate = (models) => {
Post.belongsTo(models.users, {
as:'user',
foreignKey: {
name: 'user_id',
allowNull: false
}
}
}
User.associate = (models) => {
User.hasMany(models.posts, {
as:'posts',
onDelete:'CASCADE',
onUpdate:'CASCADE'
}
}
My question is: should I specify the foreignKey one more time when declaring the hasMany association, or it is enough for Sequelize to have the foreignKey in one of the declared relationships between two models (in the example - belongsTo)?

From what I think happens:
Sequelize goes through all your association one by one
If you already provided a foreign key name then fine
Else it will guess/name the foreign key on its own
Like what it says about options.foreignKey in docs e.g. for belongsTo : https://sequelize.org/master/class/lib/model.js~Model.html#static-method-belongsTo (same description for hasOne, hasMany, belongsToMany )
options.foreignKey || string OR object || optional
The name of the foreign key attribute in the source table or an object representing the type definition for the foreign 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
If sequelize is guessing your foreignKey names then you will face issues only if your foreignKey name is not matching (tableName + Id) OR (tableName + _ + id)
💡 Hence, better to give foreignKey names on your own to both sides of associations to never face any issues going further.

Related

Sequelize hasOne with BelongsToMany

I have to models DistrictArea and Order which Order must have a DistrictArea instance in it.
so I have done this so far:
Order.hasOne(models.DistricArea)
and in migration file I used this.
queryInterface.addColumn('Orders', 'district_area_id', {
type: Sequelize.INTEGER,
references: {
model: 'DistricAreas',
key: 'id'
}
})
and I have two questions. If we use associated methods in the model , we have to explicitly define these field to our models?
district_area_id: DataTypes.INTEGER
and do I need to define belongsTo in another model (DistricArea)?
the second question is I didn't define any field in DistricArea that associated with Orders. but when I want to use query DistricArea.findAll() it comes with this error:
"column "OrderId" does not exist"
so this is the goal I am trying to achieve:
I need to tell we have many orders that, every order has a
DistricArea.
Yes you still need to define the fields in your model
from what you described above it should be belongsTo instead of hasOne for order model , and hasMany in DistricArea model and then define your sourceKey using your district_area_id field.
Order.belongsTo(models.DistricArea,{foreignKey: 'district_area_id'})
DistricArea.hasMany(models.Order, {foreignKey: 'district_area_id', sourceKey: 'id'})
after this , in DistricArea.findAll() you would get all DistricArea and then inside you will have a Order array which contain every order for each DistricArea, or in a Order.findAll() call you will see all orders with a field district_area_id

referencing a model that hasn't been created, avoid the error: relation "TABLENAME" does not exist

I am very new to sequelize and postgresql. I looked a lot for an answer to this issue but I couldn't find anything related to my it. If this question is has an answer in another post I apologize for repeating and I'll gladly refer to the other post and remove my post from here.
At the beginning I did:
sequelize model:generate --name user --attributes username:string,email:string,password:string,collection_id:INTEGER
where collection_id references another table ID. I did the same for the collection table:
sequelize model:generate --name collection --attributes plant_id:integer,user_id:integer
in my models I updated the association
in the collection model I added:
models.collection.belongsTo(models.user, {foreignKey: 'user_id'})
and in the user model I added:
models.user.hasOne(models.collection, {foreignKey: 'collection_id'})
And in the migration files I added:
user_id: {
type: Sequelize.INTEGER,
references: {
model: {
tableName: 'user'
}
}
}
collection_id: {
type: Sequelize.INTEGER,
references: {
model: {
tableName: 'collection'
}
}
}
in their respective files. Now the issue here is that sequelize is trying to migrate the user model before the collection model, however the later is referenced in the first model and I am getting this error "relation "collection" does not exist" which only makes sense. Now if I removed the reference, is there a way to add it later after I migrate?
reference sets a foreign key on the table under the hood. But you can't have 2 tables with foreign keys pointing to each other (circular reference).
You set a foreign key on the "child" table which points to the "parent" table. In your case, if collection belongs to user then collection is "child".
You can read this paragraph from Sequalize docs with some details about the difference between hasOne and belongsTo (and read about those associations in Sequalize separately as well). {foreignKey: 'collection_id'} which you pass to hasOne method is actually supposed to be in collections table (target model), means in your case it doesn't make sense and should be like this instead:
models.user.hasOne(models.collection, {foreignKey: 'user_id'})
So, you don't need collection_id column in your user table, can safely remove it.

sequelize association explanation on docs

while reading sequelize doc association part, explanation below really confuses me
hasOne and belongsTo insert the association key in different models
from each other. hasOne inserts the association key in target model
whereas belongsTo inserts the association key in the source model.
and also,
Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team
it seems that if I set relation between two models, sequelize automatically adds foreign key to the target model.
but according to the article,
https://lorenstewart.me/2016/09/12/sequelize-table-associations-joins/z
we have to manually add foreign key to the model file.
which one is right..?
it has been a while since I questioned about this issue.
Any detailed explanation would really be appreciated.
Any recommended articles about sequelize association to read would also be appreciated since sequelize doc seems not that kind for ORM beginners.
Actually it depends on how are you creating your tables. Via sync or via migrations.
For example
const User = sequelize.define('user', {
username: Sequelize.STRING,
});
const Address = sequelize.define('add', {
address: Sequelize.STRING,
});
User.hasOne(Address);
sequelize.sync({ force: true })
.then(() => User.create({
username: 'test'
}))
.then(item => {
console.log(item.dataValues.itemPrice);
});
This will create a userId field in the database
Note that I am use sync. However if you are using migrations, you would have to create this on your own.
PS: Its better to use migrations instead of sync.

Node/Express Project: Create and Sync a "Join Class/Model" to a Postgres Database

I'm building a Node/Express/Postgres version of an app that I already built in Rails. I'm learning Node, so I figured I'd rebuild something that I know works.
For now, I'm dumping everything in one file (set up my database, defined my models, etc.), just to make sure I have everything set up correctly before I divvy them up into different files.
I set up my postgres database at the very top of the file, like so:
var Sequelize = require('sequelize');
var db = new Sequelize('my_database_name', 'my_username', null, {
host: 'localhost',
dialect: 'postgres',
});
With regard to my models, I have a Politician model:
var Politician = db.define("politician", {
name: {
type: Sequelize.STRING,
},
politicalParty: {
type: Sequelize.STRING
}
});
A Category model:
var Category = db.define("category", {
name: {
type: Sequelize.STRING
},
keywords: {
type: Sequelize.ARRAY(Sequelize.TEXT)
},
});
And a join model of Politician and Category, called "Interest". Because Interest is a join model, it will have a "politicianId" and "categoryId" properties....but will those properties automatically generate in the database? And so, is this how I would define the Interest model, with no properties?
Interest Model:
var Interest = db.define("interest")
Or, will I have to be specific, and create "politicianId" and "categoryId" properties? Like so:
Interest Model:
var Interest = db.define("interest", {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
categoryId: {
type: Sequelize.INTEGER,
foreignKey: true
},
politicianId: {
type: Sequelize.INTEGER,
foreignKey: true
}
});
Also, do I need the "foreignKey: true" bit? Or will it automatically know that those properties are foreign keys? Also, do I need the "id" property? I know models automatically create their own primary key "id"...but again, I've been at this for hours, looking at docs, and trying everything.
I then defined my associations (again, all of this is the same file):
Politician.belongsToMany(Category, {through: "Interest"});
Category.belongsToMany(Politician, {through: "Interest"});
The Node/Sequelize docs seems to suggest that defining those 2 associations above will automatically "create a new model called Interest with the equivalent foreign keys politicianId and categoryId." So, do I even need to define a "Interest" model? Also, do I need the follow associations to describe that Interest belongs to Politician and Category?
Interest.belongsTo(Politician);
Interest.belongsTo(Category);
If I don't write the associations saying that Interest belongs to Politican and Catetory, I don't get the "politicianId" and "categoryId" columns in the Interest table. Just the "id" and createdAt/updatedAt columns.
I then created an instance of Politician, Category, and Interest, to persist everything to the database, to see if everything is there and set up correctly:
Politician Object:
var politician1 = Politician.sync({force: true}).then(function(){
return Politician.create(aPoliticianObjectDefinedInthisFile);
});
This works perfectly. I see this object in the politician table in the database.
Category Object:
var category1 = Category.sync({force: true}).then(function(){
return Category.create(aCategoryObjectDefinedInThisFile);
});
This works perfectly. I see this object in the category table in the database.
Here is what doesn't work. Creating an instance/object of Interest and synching it to the database. My thinking is, if I put integers as values, it will know that "politicianId: 1" means point to the politician object with an id of 1, and the same for "categoryId: 1". But when I write it as I have it below, the Interest table doesn't even show up in the Postgres database at all.
Interest Object:
Interest.sync({force: true}).then(function(){
return Interest.create(
{
politicianId: 1,
categoryId: 1
}
);
});
However, when I create the object of Interest like this, with no properties defined, the Interest table appears in the database, along with the "politicianId" and "categoryId" columns, however, those columns are empty. The object's primary id is in there at 1, and the "createdAt" and "updatedAt" columns have data too. But the foreign key columns are blank.
Interest Object:
Interest.sync({force: true}).then(function()
{
return Interest.create(
{
// No properties defined.
}
);
}
);
Sorry for this long post, lol, but, in all:
Am I creating the "Interest" model correctly?
Am I writing the associations for "Interest" correctly?
Do I even need to write associations for Interest, if I already have associations for its parent classes, Politican and Category defined?
In my Rails app, my associations for Politican and Category are like so:
Politician has_many interests
Politican has_many categories through interests
Category has_many interests
Category has_many politicians through interests
Interest belongs_to politician
Interest belongs_to category
But I use the "belongsToMay" association in Node because I got an error telling me to do so.
Basically, I need to create an instance of Politician, an instance of Category, and an instance of Interest that has "politicianId" and "categoryId" columns that point to those aforementioned instances of those classes.
politicanABC -- id: 1
categoryABC -- id: 1
instanceABC -- id: 1; politicanId: 1 (referring to politicanABC); categoryid: 1 (referring to categoryABC).
My app is set up like that in Rails and works wonderfully.
Help and thank you in advance :-)
You don't have to define the Interest model if you are not going to add any additional fields. Sequelize will internally define the model and add all required fields once you do following:
Politician.belongsToMany(Category, {through: "Interest"});
Category.belongsToMany(Politician, {through: "Interest"});
Sync needs to run on database level and not on tables since Interest model is implicit at this point.
db.sync({force: true});
Sequelize will add relationship build methods on both Politician and Category instances. Category will have methods addPolitician(), addPoliticians([]), setPoliticians([]), getPliticians(). Politician instances will have similar functions to associate categories to them. You can connect these after create option is performed on both objects successfully.
Politician.create({name: 'John Doe', politicalParty: 'Nice Party'})
.then(function(politician) {
Category.create({name: 'Nicers'})
.then(function(category) {
politician.addCategory(category);
});
});
You can also search and associate existing items using helper methods. Alternatively you can associate objects manually by accessing db.models.Interest model and running creates on it.

How to implemet a Many-to-Many relationship in Sequelizejs which also have relationship attributes?

So there are a lot of answers that explain how you model a Many-to-Many relationship in sequelizejs using hasMany() etc. But none of them has explained how and where do you store attributes which are created due to such an association, for eg: A customer can belong to or have many merchants and a merchant can have many customers, one such attribute of this relationship is a unique customer_id for a particular merchant-cutomer. Now where should this key(and any other detail) reside if we follow this: Stackoverflow answer
If you want additional attributes in your join table, you can define a model for the join table in sequelize, before you define the association, and then tell sequelize that it should use that model for joining, instead of creating a new one:
Customer = sequelize.define('customer', {})
Merchant = sequelize.define('merchant', {})
MerchantCustomers = sequelize.define('merchant_customers', {
customer_id: DataTypes.INTEGER
})
Merchant.belongsToMany(Customer, { through: MerchantCustomers })
Customer.belongsToMany(Merchant, { through: MerchantCustomers })
customer.addMerchant(merchant, { customer_id: 42 })
http://docs.sequelizejs.com/en/latest/docs/associations/#belongs-to-many-associations
To access the join table attributes:
c.getMerchants().then(function (merchants) {
merchants[0].merchant_customer.customer_id // Or perhaps merchant_customers, can't remember
});

Resources