Runnable example with assertions
Here I provide a single-source-file runnable example that illustrates every single autogenreated method mentioned at https://sequelize.org/master/manual/assocs.html#many-to-many-relationships (archive) section "Foo.hasMany(Bar)
".
The model is of a website where users can create posts and like other users' posts.
npm install sequelize@6.5.1 sqlite3@5.0.2
main.js
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost'});
Post.belongsToMany(User, {through: 'UserLikesPost'});
await sequelize.sync({force: true});
// Create some users and posts.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Autogenerated add* methods
// Make user0 like post0
await user0.addPost(post0)
// Also works.
//await user0.addPost(post0.id)
// Make user0 and user2 like post1
await post1.addUsers([user0, user2])
// Autogenerated get* methods
// Get posts liked by a user.
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes.length === 0);
const user2Likes = await user2.getPosts({order: [['body', 'ASC']]})
assert(user2Likes[0].body === 'post1');
assert(user2Likes.length === 1);
// Get users that like a given post.
const post0Likers = await post0.getUsers({order: [['name', 'ASC']]})
assert(post0Likers[0].name === 'user0');
assert(post0Likers.length === 1);
const post1Likers = await post1.getUsers({order: [['name', 'ASC']]})
assert(post1Likers[0].name === 'user0');
assert(post1Likers[1].name === 'user2');
assert(post1Likers.length === 2);
const post2Likers = await post2.getUsers({order: [['name', 'ASC']]})
assert(post2Likers.length === 0);
// Same as getPosts but with the user ID instead of the model object.
{
const user0Likes = await Post.findAll({
include: [{
model: User,
where: {
id: user0.id
}
}],
})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
// Yet another way that can be more useful in nested includes.
{
const user0Likes = (await User.findOne({
where: {id: user0.id},
include: [{
model: Post,
}],
order: [[Post, 'body', 'ASC']],
})).Posts
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
// Autogenerated has* methods
// Check if user likes post.
assert( await user0.hasPost(post0))
assert( await user0.hasPost(post0.id)) // same
assert( await user0.hasPost(post1))
assert(!await user0.hasPost(post2))
// Check if post is liked by user.
assert( await post0.hasUser(user0))
assert(!await post0.hasUser(user1))
assert(!await post0.hasUser(user2))
// AND of multiple has checks at once.
assert( await user0.hasPosts([post0, post1]))
// false because user0 does not like post2
assert(!await user0.hasPosts([post0, post1, post2]))
// Autogenerated count* methods
// user0 likes 2 posts.
assert(await user0.countPosts() === 2)
// post0 is liked by 1 user.
assert(await post0.countUsers() === 1)
// Autogenerated remove* method
// user0 doesn't like post0 anymore.
await user0.removePost(post0)
// user0 and user 2 don't like post1 anymore.
await post1.removeUsers([user0, user2])
// Check that no-one likes anything anymore.
assert(await user0.countPosts() === 0)
assert(await post0.countUsers() === 0)
// Autogenerated create* method
// Create a new post and automatically make user0 like it.
const post3 = await user0.createPost({'body': 'post3'})
assert(await user0.hasPost(post3))
assert(await post3.hasUser(user0))
// Autogenerated set* method
// Make user0 like exactly these posts. Unlike anything else.
await user0.setPosts([post1, post2])
assert(!await user0.hasPost(post0))
assert( await user0.hasPost(post1))
assert( await user0.hasPost(post2))
assert(!await user0.hasPost(post3))
await sequelize.close();
})();
GitHub upstream.
The SQLite table generated is:
UserLikesPost is the name of the relation table.
Sequelize creates it automatically for us.
On SQLite that table looks like this:
CREATE TABLE `UserLikesPost` (
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,
`UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`PostId` INTEGER NOT NULL REFERENCES `Posts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (`UserId`, `PostId`)
);
Self-associations (AKA self-references)
This illustrates how to have many-to-many from a table to itself, e.g. for a user to follow another user.
Basically:
- you are forced to add the
as:
key to .belongsToMany
- TODO: no
addFollows
method was generated, only addFollow
, why?
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
await sequelize.sync({force: true});
// Create some users.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const user3 = await User.create({name: 'user3'})
// Make user0 follow user1 and user2
await user0.addFollows([user1, user2])
// Make user2 and user3 follow user0
await user2.addFollow(user0)
await user3.addFollow(user0)
// Check that the follows worked.
const user0Follows = await user0.getFollows({order: [['name', 'ASC']]})
assert(user0Follows[0].name === 'user1');
assert(user0Follows[1].name === 'user2');
assert(user0Follows.length === 2);
const user1Follows = await user1.getFollows({order: [['name', 'ASC']]})
assert(user1Follows.length === 0);
const user2Follows = await user2.getFollows({order: [['name', 'ASC']]})
assert(user2Follows[0].name === 'user0');
assert(user2Follows.length === 1);
const user3Follows = await user3.getFollows({order: [['name', 'ASC']]})
assert(user3Follows[0].name === 'user0');
assert(user3Follows.length === 1);
// Same but with ID instead of object.
{
const user0Follows = (await User.findOne({
where: {id: user0.id},
include: [{model: User, as: 'Follows'}],
})).Follows
assert(user0Follows[0].name === 'user1');
assert(user0Follows[1].name === 'user2');
assert(user0Follows.length === 2);
}
// has methods
assert(!await user0.hasFollow(user0))
assert(!await user0.hasFollow(user0.id))
assert( await user0.hasFollow(user1))
assert( await user0.hasFollow(user2))
assert(!await user0.hasFollow(user3))
// Count method
assert(await user0.countFollows() === 2)
await sequelize.close();
})();
The SQLite table generated is:
CREATE TABLE IF NOT EXISTS `UserFollowUser` (
`createdAt` DATETIME NOT NULL,
`updatedAt` DATETIME NOT NULL,=
`UserId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
`FollowId` INTEGER NOT NULL REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY (`UserId`, `FollowId`)
);
Also asked at: How to have a self-referencing many-to-many association in Sequelize?
Many-to-many with custom columns
Going back to the user likes post example, we can also achieve the same result with a custom table we create.
This allows us to add extra parameters to the relationship, e.g. here we add a score saying how much the user likes the post.
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
const UserLikesPost = sequelize.define('UserLikesPost', {
UserId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
PostId: {
type: DataTypes.INTEGER,
references: {
model: Post,
key: 'id'
}
},
score: {
type: DataTypes.INTEGER,
},
});
User.belongsToMany(Post, {through: UserLikesPost});
Post.belongsToMany(User, {through: UserLikesPost});
await sequelize.sync({force: true});
// Create some users and likes.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Make some useres like some posts.
await user0.addPost(post0, {through: {score: 1}})
await user1.addPost(post1, {through: {score: 2}})
await user1.addPost(post2, {through: {score: 3}})
// Find what user0 likes.
const user0Likes = await user0.getPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[0].UserLikesPost.score === 1);
assert(user0Likes.length === 1);
// Find what user1 likes.
const user1Likes = await user1.getPosts({order: [['body', 'ASC']]})
assert(user1Likes[0].body === 'post1');
assert(user1Likes[0].UserLikesPost.score === 2);
assert(user1Likes[1].body === 'post2');
assert(user1Likes[1].UserLikesPost.score === 3);
assert(user1Likes.length === 2);
// Where on the custom through table column.
// https://stackoverflow.com/questions/38857156/how-to-query-many-to-many-relationship-sequelize
{
const user1LikesWithScore3 = await Post.findAll({
include: [{
model: User,
where: {
id: user1.id
},
through: {where: {score: 3}},
}],
})
assert(user1LikesWithScore3[0].body === 'post2');
assert(user1LikesWithScore3[0].UserLikesPost.score === 3);
assert(user1LikesWithScore3.length === 1);
}
// TODO: this doesn't work. Possible at all in a single addUsers call?
// Make user0 and user2 like post1
// This method automatically generated.
//await post1.addUsers(
// [user0, user2],
// {through: [
// {score: 2},
// {score: 3},
// ]}
//)
await sequelize.close();
})();
This had been asked at:
and is also documented at: https://sequelize.org/master/manual/advanced-many-to-many.html
How to query by the score
has been asked at: How to query many to many relationship sequelize?
How to make complex JOIN queries with multiple associations?
Consider for example the use case:
users can follow users, users can create posts, find all posts by users that a given user follows
To solve this, we basically just nest include:
statements as shown below:
#!/usr/bin/env node
// Find all posts by users that a given user follows.
// https://stackoverflow.com/questions/42632943/sequelize-multiple-where-clause
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'db.sqlite3',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(User, {through: 'UserFollowUser', as: 'Follows'});
User.hasMany(Post);
Post.belongsTo(User);
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
{name: 'user2'},
{name: 'user3'},
])
const posts = await Post.bulkCreate([
{body: 'body00', UserId: users[0].id},
{body: 'body01', UserId: users[0].id},
{body: 'body10', UserId: users[1].id},
{body: 'body11', UserId: users[1].id},
{body: 'body20', UserId: users[2].id},
{body: 'body21', UserId: users[2].id},
{body: 'body30', UserId: users[3].id},
{body: 'body31', UserId: users[3].id},
])
await users[0].addFollows([users[1], users[2]])
// Get all posts by authors that user0 follows.
// The posts are placed inside their respetive authors under .Posts
// so we loop to gather all of them.
{
const user0Follows = (await User.findByPk(users[0].id, {
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
assert(postsFound[0].body === 'body10')
assert(postsFound[1].body === 'body11')
assert(postsFound[2].body === 'body20')
assert(postsFound[3].body === 'body21')
assert(postsFound.length === 4)
}
// With ordering, offset and limit.
// The posts are placed inside their respetive authors under .Posts
// The only difference is that posts that we didn't select got removed.
{
const user0Follows = (await User.findByPk(users[0].id, {
offset: 1,
limit: 2,
// TODO why is this needed? It does try to make a subquery otherwise, and then it doesn't work.
// https://selleo.com/til/posts/ddesmudzmi-offset-pagination-with-subquery-in-sequelize-
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
assert(user0Follows[0].name === 'user1')
assert(user0Follows[1].name === 'user2')
assert(user0Follows.length === 2)
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
postsFound.sort((x, y) => { return x.body < y.body ? -1 : x.body > y.body ? 1 : 0 })
// Note that what happens is that some of the
assert(postsFound[0].body === 'body11')
assert(postsFound[1].body === 'body20')
assert(postsFound.length === 2)
// Same as above, but now with DESC ordering.
{
const user0Follows = (await User.findByPk(users[0].id, {
order: [[
{model: User, as: 'Follows'},
Post,
'body',
'DESC'
]],
offset: 1,
limit: 2,
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
// Note how user ordering is also reversed from an ASC.
// it likely takes the use that has the first post.
assert(user0Follows[0].name === 'user2')
assert(user0Follows[1].name === 'user1')
assert(user0Follows.length === 2)
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
// In this very specific data case, this would not be needed.
// because user2 has the second post body and user1 has the first
// alphabetically.
postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
// Note that what happens is that some of the
assert(postsFound[0].body === 'body20')
assert(postsFound[1].body === 'body11')
assert(postsFound.length === 2)
}
// Here user2 would have no post hits due to the limit,
// so it is entirely pruned from the user list as desired.
// Otherwise we would fetch a lot of unwanted user data
// in a large database.
const user0FollowsLimit2 = (await User.findByPk(users[0].id, {
limit: 2,
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [ { model: Post } ],
},
],
})).Follows
assert(user0FollowsLimit2[0].name === 'user1')
assert(user0FollowsLimit2.length === 1)
// Get just the count of the posts authored by users followed by user0.
// attributes: [] excludes all other data from the SELECT of the queries
// to optimize things a bit.
// https://stackoverflow.com/questions/37817808/counting-associated-entries-with-sequelize
{
const user0Follows = await User.findByPk(users[0].id, {
attributes: [
[Sequelize.fn('COUNT', Sequelize.col('Follows.Posts.id')), 'count']
],
include: [
{
model: User,
as: 'Follows',
attributes: [],
through: {
attributes: []
},
include: [
{
model: Post,
attributes: [],
}
],
},
],
})
assert.strictEqual(user0Follows.dataValues.count, 4);
}
// Case in which our post-sorting is needed.
// TODO: possible to get sequelize to do this for us by returning
// a flat array directly?
// Managed with super many to many as shown below.
// It's not big deal since the LIMITed result should be small,
// but feels wasteful.
// https://stackoverflow.com/questions/41502699/return-flat-object-from-sequelize-with-association
// https://github.com/sequelize/sequelize/issues/4419
{
await Post.truncate({restartIdentity: true})
const posts = await Post.bulkCreate([
{body: 'body0', UserId: users[0].id},
{body: 'body1', UserId: users[1].id},
{body: 'body2', UserId: users[2].id},
{body: 'body3', UserId: users[3].id},
{body: 'body4', UserId: users[0].id},
{body: 'body5', UserId: users[1].id},
{body: 'body6', UserId: users[2].id},
{body: 'body7', UserId: users[3].id},
])
const user0Follows = (await User.findByPk(users[0].id, {
order: [[
{model: User, as: 'Follows'},
Post,
'body',
'DESC'
]],
subQuery: false,
include: [
{
model: User,
as: 'Follows',
include: [
{
model: Post,
}
],
},
],
})).Follows
assert(user0Follows[0].name === 'user2')
assert(user0Follows[1].name === 'user1')
assert(user0Follows.length === 2)
const postsFound = []
for (const followedUser of user0Follows) {
postsFound.push(...followedUser.Posts)
}
// We need this here, otherwise we would get all user2 posts first:
// body6, body2, body5, body1
postsFound.sort((x, y) => { return x.body < y.body ? 1 : x.body > y.body ? -1 : 0 })
assert(postsFound[0].body === 'body6')
assert(postsFound[1].body === 'body5')
assert(postsFound[2].body === 'body2')
assert(postsFound[3].body === 'body1')
assert(postsFound.length === 4)
}
}
await sequelize.close();
})();
Super many to many to do the "posts by followed users" query without post processing
Super many to many means explicitly setting belongsTo
/hasMany
between each model and the through table, in addition to the belongsToMany
of each model.
This is the only way I found to nicely make the "posts by followed users" query without post processing.
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes, Op } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
define: {
timestamps: false
},
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
});
const UserFollowUser = sequelize.define('UserFollowUser', {
UserId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
FollowId: {
type: DataTypes.INTEGER,
references: {
model: User,
key: 'id'
}
},
}
);
// Super many to many.
User.belongsToMany(User, {through: UserFollowUser, as: 'Follows'});
UserFollowUser.belongsTo(User)
User.hasMany(UserFollowUser)
User.hasMany(Post);
Post.belongsTo(User);
await sequelize.sync({force: true});
// Create data.
const users = await User.bulkCreate([
{name: 'user0'},
{name: 'user1'},
{name: 'user2'},
{name: 'user3'},
])
const posts = await Post.bulkCreate([
{body: 'body0', UserId: users[0].id},
{body: 'body1', UserId: users[1].id},
{body: 'body2', UserId: users[2].id},
{body: 'body3', UserId: users[3].id},
{body: 'body4', UserId: users[0].id},
{body: 'body5', UserId: users[1].id},
{body: 'body6', UserId: users[2].id},
{body: 'body7', UserId: users[3].id},
])
await users[0].addFollows([users[1], users[2]])
// Get all the posts by authors that user0 follows.
// without any post process sorting. We only managed to to this
// with a super many to many, because that allows us to specify
// a reversed order in the through table with `on`, since we need to
// match with `FollowId` and not `UserId`.
{
const postsFound = await Post.findAll({
order: [[
'body',
'DESC'
]],
include: [
{
model: User,
attributes: [],
required: true,
include: [
{
model: UserFollowUser,
on: {
FollowId: {[Op.col]: 'User.id' },
},
attributes: [],
where: {UserId: users[0].id},
}
],
},
],
})
assert.strictEqual(postsFound[0].body, 'body6')
assert.strictEqual(postsFound[1].body, 'body5')
assert.strictEqual(postsFound[2].body, 'body2')
assert.strictEqual(postsFound[3].body, 'body1')
assert.strictEqual(postsFound.length, 4)
}
await sequelize.close();
})();
Related: Return flat object from sequelize with association
Aliases: multiple many to many associations between two models
Suppose now that users can both like and follow posts.
To model that, we will have two many-to-many associations between users and posts.
The problem however is that so far we have used the table names User
and Post
as the identifier for associations.
So to disambiguate this, we are forced to use the as:
parameter, which creates a table alias.
const assert = require('assert');
const path = require('path');
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'tmp.' + path.basename(__filename) + '.sqlite',
});
(async () => {
// Create the tables.
const User = sequelize.define('User', {
name: { type: DataTypes.STRING },
}, {});
const Post = sequelize.define('Post', {
body: { type: DataTypes.STRING },
}, {});
User.belongsToMany(Post, {through: 'UserLikesPost', as: 'likedPosts'});
Post.belongsToMany(User, {through: 'UserLikesPost', as: 'likers'});
User.belongsToMany(Post, {through: 'UserFollowsPost', as: 'followedPosts'});
Post.belongsToMany(User, {through: 'UserFollowsPost', as: 'followers'});
await sequelize.sync({force: true});
// Create some users and likes.
const user0 = await User.create({name: 'user0'})
const user1 = await User.create({name: 'user1'})
const user2 = await User.create({name: 'user2'})
const post0 = await Post.create({body: 'post0'});
const post1 = await Post.create({body: 'post1'});
const post2 = await Post.create({body: 'post2'});
// Autogenerated add* methods
// Setup likes and follows.
await user0.addLikedPost(post0)
await post1.addLikers([user0, user2])
await user1.addFollowedPosts([post0, post1])
await post1.addFollower(user2)
// Autogenerated get* methods
// Get likes by a user.
const user0Likes = await user0.getLikedPosts({order: [['body', 'ASC']]})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
const user1Likes = await user1.getLikedPosts({order: [['body', 'ASC']]})
assert(user1Likes.length === 0);
const user2Likes = await user2.getLikedPosts({order: [['body', 'ASC']]})
assert(user2Likes[0].body === 'post1');
assert(user2Likes.length === 1);
// Get users that liked a given post.
const post0Likers = await post0.getLikers({order: [['name', 'ASC']]})
assert(post0Likers[0].name === 'user0');
assert(post0Likers.length === 1);
const post1Likers = await post1.getLikers({order: [['name', 'ASC']]})
assert(post1Likers[0].name === 'user0');
assert(post1Likers[1].name === 'user2');
assert(post1Likers.length === 2);
const post2Likers = await post2.getLikers({order: [['name', 'ASC']]})
assert(post2Likers.length === 0);
// Get follows by a user.
const user0Follows = await user0.getFollowedPosts({order: [['body', 'ASC']]})
assert(user0Follows.length === 0);
const user1Follows = await user1.getFollowedPosts({order: [['body', 'ASC']]})
assert(user1Follows[0].body === 'post0');
assert(user1Follows[1].body === 'post1');
assert(user1Follows.length === 2);
const user2Follows = await user2.getFollowedPosts({order: [['body', 'ASC']]})
assert(user2Follows[0].body === 'post1');
assert(user2Follows.length === 1);
// Get users that followed a given post.
const post0Followers = await post0.getFollowers({order: [['name', 'ASC']]})
assert(post0Followers[0].name === 'user1');
assert(post0Followers.length === 1);
const post1Followers = await post1.getFollowers({order: [['name', 'ASC']]})
assert(post1Followers[0].name === 'user1');
assert(post1Followers[1].name === 'user2');
assert(post1Followers.length === 2);
const post2Followers = await post2.getFollowers({order: [['name', 'ASC']]})
assert(post2Followers.length === 0);
// Same as getLikedPosts but with the user ID instead of the model object.
{
const user0Likes = await Post.findAll({
include: [{
model: User,
as: 'likers',
where: {id: user0.id},
}],
order: [['body', 'ASC']],
})
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
// Yet another way that can be more useful in nested includes.
{
const user0Likes = (await User.findOne({
where: {id: user0.id},
include: [{
model: Post,
as: 'likedPosts',
}],
order: [[{model: Post, as: 'likedPosts'}, 'body', 'ASC']],
})).likedPosts
assert(user0Likes[0].body === 'post0');
assert(user0Likes[1].body === 'post1');
assert(user0Likes.length === 2);
}
await sequelize.close();
})();
Bonus: aliased many-to-one requires foreignKey
Aliased many-to-many works without foreignKey
. But many-to-one doesn't: it would create two separate IDs e.g. UserId
and authorId
.
This is the only way I can get it to work (suppose each post has exactly one author and one reviewer):
User.hasMany(Post, {as: 'authoredPosts', foreignKey: 'authorId'});
Post.belongsTo(User, {as: 'author', foreignKey: 'authorId'});
User.hasMany(Post, {as: 'reviewedPosts', foreignKey: 'reviewerId'});
Post.belongsTo(User, {as: 'reviewer', foreignKey: 'reviewerId'});
How to list all the automatically generated methods?
How to display all methods of an object? works for this.
JOIN
+ GROUP BY
+ an aggregate like COUNT
See: Sequelize query with count in inner join
Tested on Ubuntu 21.04, node.js v14.17.0.