Let's Do: Sequelize Associations
Published 2020-02-01
Photo by Louis Hansel on Unsplash
Sequelize is a Node.js ORM for relational databases. Relations are represented in code with "associations".
These are the 4 types of associations, where A and B are sequelize Model
s:
A.hasOne(B);
A.hasMany(B);
A.belongsTo(B);
A.belongsToMany(B, {through: 'C'}); // where C is a junction table
In the above examples, Sequelize would refer to A
as the source and B
as the target.
The has
methods define the foreign key in the target, while the belongs
methods define the foreign key in the source. Note the subtle different in whether to use sourceKey
or targetKey
:
A.hasMany(B, {
foreignKey: 'a_id', // B.a_id
sourceKey: 'id', // the A.id
});
B.belongsTo(A, {
foreignKey: 'a_id', // B.a_id
targetKey: 'id', // the A.id
})
The way I remember this is by thinking of those horrible trashy "property of..." tattoos. In this metaphor, the foreign key is the tattoo.
If A belongs to B, who gets the foreignKey tattoo? A.
But instead if A has many B's, then who gets the tattoos? The B's do...
(I know, dumb metaphor 🐐)
Examples
Let's use the exmaple of a pet hospital where we have Users that own Pets that have Appointments at Hospitals. Note that the Hospital would also own Appointments.
An important thing to note when defining relationships is you need to either... a. define relationships in a single file, or b. define the relationship from only one side
Doing the below (separate files) will not work.
// THIS WON'T WORK
// user.js
User.hasMany(Pet, {
foreignKey: 'user_id',
sourceKey: 'id',
});
// pet.js
Pet.belongsTo(User, {
foreignKey: 'user_id',
targetKey: 'id',
});
I tried to do this and it seemed like it created something similar to a circular reference. It claimed the target on belongsTo()
was not a subclass of Sequelize.Model (which it definitely was 🤷♀️).
Error: comment.belongsTo called with something that's not a subclass of Sequelize.Model
The way to get this to work is either put relationships in a single file or do only one side of the relationship. I thought the all-in-one-file approach was a little icky, so I went with only one side.
To keep things consistent, I stuck with using only the has
functions since it felt more natural that way to me.
Here is what I ended up with.
// user.js
User.hasMany(Pet, {
foreignKey: 'user_email',
sourceKey: 'email',
});
User.hasMany(Appointment, {
foreignKey: 'user_email',
sourceKey: 'email',
});
// location.js
Location.hasMany(Appointment, {
foreignKey: 'loc_id',
sourceKey: 'id',
});
//
The only case where I could need to use any of the belongs
functions is with belongsToMany
, since that is the only way to define a many-to-many relationship.
If, for example, we were to include Veterinarians into the relationship that worked at a few different hospitals (do they?), then we could define that through a junction table.
// veterinarian.js
Veterinarian.belongsToMany(Location, {
through: 'VeterinarianLocations'
});
Sequelize will automatically create the VeterinarianLocations
model which will act as the junction model. The model would contain both Veterinarian primary key and Location primary key.
And that's it!
Happy coding 👩💻
#nodejs#sql#javascript