diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js index c336534..2cb4d39 100755 --- a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many.js @@ -70,7 +70,7 @@ const user2Likes = await user2.getPosts({order: [['body', 'ASC']]}) assert(user2Likes[0].body === 'post1'); assert(user2Likes.length === 1); -// Same as get* but with the user ID instead of the model object. +// Same as get but with the user ID instead of the model object. { const user0Likes = await Post.findAll({ include: [{ @@ -84,6 +84,20 @@ assert(user2Likes.length === 1); 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); +} + // Get users that liked a given likes. const post0Likers = await post0.getUsers({order: [['name', 'ASC']]}) @@ -96,9 +110,7 @@ assert(post1Likers[1].name === 'user2'); assert(post1Likers.length === 2); const post2Likers = await post2.getUsers({order: [['name', 'ASC']]}) -assert(post1Likers[0].name === 'user0'); -assert(post1Likers[1].name === 'user2'); -assert(post1Likers.length === 2); +assert(post2Likers.length === 0); // Autogenerated has* methods diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_double.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_double.js new file mode 100755 index 0000000..0a8252f --- /dev/null +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_many_double.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node + +// Two associations between two specific models. Requires us to use alias with `as:` +// to disambiguate them. + +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(); +})(); diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_one.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_one.js new file mode 100755 index 0000000..fcf0076 --- /dev/null +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_one.js @@ -0,0 +1,70 @@ +#!/usr/bin/env node + +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.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}, +]) + +// Get user from post and vice versa. + +const user0Posts = await users[0].getPosts({order: [['body', 'ASC']]}) +assert(user0Posts[0].body === 'body00') +assert(user0Posts[1].body === 'body01') +assert(user0Posts.length === 2) + +const user1Posts = await users[1].getPosts({order: [['body', 'ASC']]}) +assert(user1Posts[0].body === 'body10') +assert(user1Posts[1].body === 'body11') +assert(user1Posts.length === 2) + +const post00User = await posts[0].getUser() +assert(post00User.name === 'user0') + +const post01User = await posts[1].getUser() +assert(post01User.name === 'user0') + +const post10User = await posts[2].getUser() +assert(post10User.name === 'user1') + +const post11User = await posts[3].getUser() +assert(post11User.name === 'user1') + +await sequelize.close(); +})(); diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_one_double.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_one_double.js new file mode 100755 index 0000000..a5bf83d --- /dev/null +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_many_to_one_double.js @@ -0,0 +1,123 @@ +#!/usr/bin/env node + +// Two associations between two models: posts +// now have the author, and a mandatory reviewer. +// Requires us to use as do disambiguate them. + +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', + 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 }, +}, {}); + +// TODO possible without specifying foreignKey? Feels like duplication. +// But without it, hasMany creates a column called UserId, in addition +// to the desired authorId, and then bad things happen. +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'}); +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', authorId: users[0].id, reviewerId: users[0].id}, + {body: 'body01', authorId: users[0].id, reviewerId: users[1].id}, + {body: 'body10', authorId: users[1].id, reviewerId: users[2].id}, + {body: 'body11', authorId: users[1].id, reviewerId: users[3].id}, + {body: 'body20', authorId: users[2].id, reviewerId: users[0].id}, + {body: 'body21', authorId: users[2].id, reviewerId: users[1].id}, + {body: 'body30', authorId: users[3].id, reviewerId: users[2].id}, + {body: 'body31', authorId: users[3].id, reviewerId: users[3].id}, +]) + +// Get user from post and vice versa. + +const user0AuthoredPosts = await users[0].getAuthoredPosts() +assert(user0AuthoredPosts[0].body === 'body00') +assert(user0AuthoredPosts[1].body === 'body01') +assert(user0AuthoredPosts.length === 2) + +const user1AuthoredPosts = await users[1].getAuthoredPosts() +assert(user1AuthoredPosts[0].body === 'body10') +assert(user1AuthoredPosts[1].body === 'body11') +assert(user1AuthoredPosts.length === 2) + +const user0ReviewedPosts = await users[0].getReviewedPosts() +assert(user0ReviewedPosts[0].body === 'body00') +assert(user0ReviewedPosts[1].body === 'body20') +assert(user0ReviewedPosts.length === 2) + +const user1ReviewedPosts = await users[1].getReviewedPosts() +assert(user1ReviewedPosts[0].body === 'body01') +assert(user1ReviewedPosts[1].body === 'body21') +assert(user1ReviewedPosts.length === 2) + +assert((await posts[0].getAuthor()).name === 'user0') +assert((await posts[1].getAuthor()).name === 'user0') +assert((await posts[2].getAuthor()).name === 'user1') +assert((await posts[3].getAuthor()).name === 'user1') + +assert((await posts[0].getReviewer()).name === 'user0') +assert((await posts[1].getReviewer()).name === 'user1') +assert((await posts[2].getReviewer()).name === 'user2') +assert((await posts[3].getReviewer()).name === 'user3') + +// Same as getAuthoredPosts but with the user ID instead of the model object. +{ + const user0AuthoredPosts = await Post.findAll({ + include: [{ + model: User, + // We need the `as` here to disambiguate. + as: 'author', + where: {id: users[0].id}, + }], + order: [['body', 'ASC']], + }) + assert(user0AuthoredPosts[0].body === 'body00'); + assert(user0AuthoredPosts[1].body === 'body01'); + assert(user0AuthoredPosts.length === 2); +} + +// Yet another way that can be more useful in nested includes. +{ + const user0AuthoredPosts = (await User.findOne({ + where: {id: users[0].id}, + include: [{ + model: Post, + as: 'authoredPosts', + }], + order: [[{model: Post, as: 'authoredPosts'}, 'body', 'ASC']], + })).authoredPosts + assert(user0AuthoredPosts[0].body === 'body00'); + assert(user0AuthoredPosts[1].body === 'body01'); + assert(user0AuthoredPosts.length === 2); +} + +await sequelize.close(); +})(); diff --git a/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js b/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js index 8fdc970..1800edc 100755 --- a/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js +++ b/rootfs_overlay/lkmc/nodejs/sequelize/association_nested_include.js @@ -37,7 +37,7 @@ const users = await User.bulkCreate([ const posts = await Post.bulkCreate([ {body: 'body00', UserId: users[0].id}, - {body: 'body11', 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}, @@ -108,7 +108,6 @@ await users[0].addFollows([users[1], users[2]]) 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) @@ -229,6 +228,37 @@ await users[0].addFollows([users[1], users[2]]) assert(postsFound[3].body === 'body1') assert(postsFound.length === 4) } + + //// This is likely almost it. We just have to understand the undocumented custom on: + //// to specify from which side of the UserFollowsUser we are coming. + //{ + // const postsFound = await Post.findAll({ + // order: [[ + // 'body', + // 'DESC' + // ]], + // subQuery: false, + // include: [ + // { + // model: User, + // on: {'id': '$Post.User.FollowId$'}, + // include: [ + // { + // model: User, + // as: 'Follows', + // where: {id: users[0].id}, + // } + // ], + // }, + // ], + // }) + // console.error(postsFound.length); + // assert.strictEqual(postsFound[0].body, 'body6') + // assert.strictEqual(postsFound[1].body, 'body5') + // assert.strictEqual(postsFound[0].body, 'body1') + // assert.strictEqual(postsFound[1].body, 'body2') + // assert.strictEqual(postsFound.length, 4) + //} } await sequelize.close();