diff --git a/src/spell.js b/src/spell.js index 5731ad21..79b78f62 100644 --- a/src/spell.js +++ b/src/spell.js @@ -853,7 +853,14 @@ class Spell { if (qualifier in joins) { throw new Error(`invalid join target. ${qualifier} already defined.`); } - joins[qualifier] = { Model, on: parseConditions(Model, onConditions, ...values)[0] }; + + const association = this.Model.associations[qualifier] || this.Model.associations[pluralize(qualifier, 1)]; + + if (!association) { + joins[qualifier] = { Model, on: parseConditions(Model, onConditions, ...values)[0] }; + } else { + joinAssociation(this, this.Model, this.Model.tableAlias, qualifier, { onConditions, values }); + } return this; } diff --git a/test/integration/suite/basics.test.js b/test/integration/suite/basics.test.js index 11dfb43f..706ba401 100644 --- a/test/integration/suite/basics.test.js +++ b/test/integration/suite/basics.test.js @@ -268,12 +268,12 @@ describe('=> Basic', () => { }, /unable to use virtual attribute realname as condition in model User/); await assert.rejects(async () => { - Post.hasOne('user', { + Post.belongsTo('user', { foreignKey: 'realname' }); }, /unable to use virtual attribute realname as foreign key in model User/); - Post.hasOne('user', { + Post.belongsTo('user', { foreignKey: 'authorId' }); diff --git a/test/integration/suite/querying.test.js b/test/integration/suite/querying.test.js index f9abee54..e6c0ebc1 100644 --- a/test/integration/suite/querying.test.js +++ b/test/integration/suite/querying.test.js @@ -13,6 +13,7 @@ const Like = require('../../models/like'); const Post = require('../../models/post'); const Tag = require('../../models/tag'); const TagMap = require('../../models/tagMap'); +const User = require('../../models/user'); const { logger } = require('../../../src/utils'); describe('=> Query', function() { @@ -502,11 +503,14 @@ describe('=> Count / Group / Having', function() { describe('=> Group / Join / Subqueries', function() { before(async function() { await Post.remove({}, true); + await User.remove({}, true); + const user = await User.create({ name: 'Tyrael', nickname: 'Tyrael', email: 'tyrael@console.com' }); + const user1 = await User.create({ name: 'Lazarus', nickname: 'Lazarus', email: 'lazarus@console.com' }); const posts = await Post.bulkCreate([ - { id: 1, title: 'New Post' }, - { id: 2, title: 'Archbishop Lazarus' }, - { id: 3, title: 'Archangel Tyrael' }, - { id: 4, title: 'New Post 2' }, + { id: 1, title: 'New Post', authorId: user.id }, + { id: 2, title: 'Archbishop Lazarus', authorId: user1.id }, + { id: 3, title: 'Archangel Tyrael', authorId: user.id }, + { id: 4, title: 'New Post 2', authorId: user1.id }, ]); await Attachment.bulkCreate([ @@ -530,6 +534,47 @@ describe('=> Group / Join / Subqueries', function() { ]); }); + it('Bone.find().join() with association', async function() { + // https://github.com/cyjake/leoric/issues/417 + // SELECT `posts`.*, `comments`.* FROM `articles` AS `posts` LEFT JOIN `comments` AS `comments` ON `comments`.`article_id` = `posts`.`id` WHERE `posts`.`gmt_deleted` IS NULL + assert.equal(Post.find().join(Comment, 'comments.articleId = posts.id').toSqlString(), Post.include('comments').toSqlString()); + const posts = await Post.find().join(Comment, 'comments.articleId = posts.id'); + assert.equal(posts.length, 4); + + assert.equal(posts[0].comments.length, 0); + assert.equal(posts[1].comments.length, 2); + assert.equal(posts[2].comments.length, 1); + assert.equal(posts[3].comments.length, 0); + assert.ok(posts[1].comments[0] instanceof Comment); + }); + + it('Bone.find().join().limit() with association', async function() { + // https://github.com/cyjake/leoric/issues/417 + const posts = await Post.find().limit(1).join(Comment, 'comments.articleId = posts.id').where({ + 'posts.title': { $like: 'Archb%' }, + }); + assert.equal(posts.length, 1); + assert.ok(posts[0].comments[0] instanceof Comment); + }); + + it('Bone.find().join() without association', async function() { + // https://github.com/cyjake/leoric/issues/417 + const posts = await Post.find().join(User, 'posts.authorId = users.id'); + assert.equal(posts.length, 4); + + assert.ok(posts[0].users); + assert.ok(posts[1].users instanceof User); + }); + + it('Bone.find().join().limit() without association', async function() { + // https://github.com/cyjake/leoric/issues/417 + const posts = await Post.find().limit(1).join(User, 'posts.authorId = users.id').where({ + 'posts.title': { $like: 'Archb%' }, + }); + assert.equal(posts.length, 1); + assert.ok(posts[0].users instanceof User); + }); + it('Bone.group() subquery', async function() { const posts = await Post.find({ id: Comment.select('articleId').from( diff --git a/test/start.sh b/test/start.sh index 9f9a1414..97fae90f 100755 --- a/test/start.sh +++ b/test/start.sh @@ -10,8 +10,8 @@ function run { args=("${args[@]:1}"); fi echo ""; - printf '"%s" ' "${args[@]}" | xargs echo "> DEBUG=leoric mocha --node-option require=ts-node/register,enable-source-maps -R dot --exit --timeout 5000 ${file}"; - printf '"%s" ' "${args[@]}" | DEBUG=leoric xargs mocha --node-option require=ts-node/register,enable-source-maps -R dot --exit --timeout 5000 ${file} || exit $?; + printf '"%s" ' "${args[@]}" | xargs echo "> mocha --node-option require=ts-node/register,enable-source-maps -R spec --exit --timeout 5000 ${file}"; + printf '"%s" ' "${args[@]}" | xargs mocha --node-option require=ts-node/register,enable-source-maps -R spec --exit --timeout 5000 ${file} || exit $?; } ##