From 3dc5376b2a9a548fb6b5abd60dc573385940076f Mon Sep 17 00:00:00 2001 From: Italo Izaac Date: Sun, 12 Jan 2020 03:44:42 -0300 Subject: [PATCH] =?UTF-8?q?feat(relations):=20allow=20to=20hide=20or=20ren?= =?UTF-8?q?ame=20pivot=20attribute=20on=20belongsTo=E2=80=A6=20(#433)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(relations): allow to hide or rename pivot attribute on belongsToMany relations * feat(relations): small refactor and new test added to belongsToMany relation - rename `_isHiddenPivotKey` to `_isHiddenPivotAttribute` - new test added to increase coverage on belongsToMany * feat(relations): adding renamed methods that were forgotten in the previous commit. * feat(relations): fix failing tests * feat(relations): fix coverage decreased and remove unnecessary if statement. --- src/Lucid/Relations/BelongsToMany.js | 84 ++++++++- test/unit/lucid-belongs-to-many.spec.js | 235 ++++++++++++++++++++++++ 2 files changed, 316 insertions(+), 3 deletions(-) diff --git a/src/Lucid/Relations/BelongsToMany.js b/src/Lucid/Relations/BelongsToMany.js index 698dd0d7..21dfb43f 100644 --- a/src/Lucid/Relations/BelongsToMany.js +++ b/src/Lucid/Relations/BelongsToMany.js @@ -79,7 +79,8 @@ class BelongsToMany extends BaseRelation { table: util.makePivotTableName(parentInstance.constructor.name, relatedModel.name), withTimestamps: false, withFields: [], - pivotPrimaryKey: 'id' + pivotPrimaryKey: 'id', + pivotAttribute: 'pivot' } this._relatedFields = [] @@ -154,6 +155,30 @@ class BelongsToMany extends BaseRelation { return [this.relatedForeignKey, this.foreignKey].concat(this._pivot.withFields) } + /** + * Returns the pivot attribute name. + * + * If this is `false` the attribute `pivot` not display. + * + * @attribute $pivotAttribute + * + * @return {String|Boolean} + */ + get $pivotAttribute () { + let pivotAttribute = this._pivot.pivotAttribute + + if (this._PivotModel && !_.isUndefined(this._PivotModel.pivotAttribute)) { + pivotAttribute = this._PivotModel.pivotAttribute + } + + // Is `true`. set default value `pivot`. + if (pivotAttribute === true) { + pivotAttribute = 'pivot' + } + + return pivotAttribute + } + /** * Returns the name of select statement on pivot table * @@ -323,8 +348,45 @@ class BelongsToMany extends BaseRelation { }) const pivotModel = this._newUpPivotModel() - pivotModel.newUp(pivotAttributes) - row.setRelated('pivot', pivotModel) + + /** + * Not continue to add `pivot` attribute if exists `$pivot` field to hidden. + * + * @link https://github.com/adonisjs/adonis-lucid/issues/366 + */ + if (!this._isHiddenPivotAttribute(pivotModel)) { + pivotModel.newUp(pivotAttributes) + row.setRelated(this.$pivotAttribute, pivotModel) + } + } + + /** + * If exists `$pivot` field into `static get hidden() {}` list of PivotModel + * this return `true`. + * + * This method resolve enhancement of "Issue #366": + * @link https://github.com/adonisjs/adonis-lucid/issues/366 + * + * @method _isHiddenPivotAttribute + * + * @param {Object} pivotModel + * + * @return {Boolean} + * + * @private + */ + _isHiddenPivotAttribute (pivotModel) { + /** + * Get hidden field list. + * + * @type {Array} + */ + let hidden = _.get(pivotModel, '$hidden', []) + + return ( + (_.isArray(hidden) && hidden.includes('$pivot') === true) || + this.$pivotAttribute === false + ) } /** @@ -489,6 +551,22 @@ class BelongsToMany extends BaseRelation { return this } + /** + * Define the pivot attribute. + * + * If this is `true` the pivot attribute return default value `pivot`. + * If this is `false` the pivot attribute not displayed. + * If this is an `string` the pivot table renamed to passed `string` attr. + * + * @param {String|Boolean} attr + * + * @chainable + */ + pivotAttribute (attr) { + this._pivot.pivotAttribute = attr + return this + } + /** * Define the primary key to be selected for the * pivot table. diff --git a/test/unit/lucid-belongs-to-many.spec.js b/test/unit/lucid-belongs-to-many.spec.js index f5b95c88..fb50ca6d 100644 --- a/test/unit/lucid-belongs-to-many.spec.js +++ b/test/unit/lucid-belongs-to-many.spec.js @@ -2748,4 +2748,239 @@ test.group('Relations | Belongs To Many', (group) => { assert.equal(userQuery.sql, helpers.formatQuery('select * from "users" where exists (select * from "posts" inner join "post_user" on "posts"."id" = "post_user"."post_id" where "users"."id" = "post_user"."user_id" and "post_user"."deleted_at" is null)')) }) + + test('hide \'pivot\' attribute when added \'$pivot\' into hidden field list', async (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + static get hidden () { + return ['$pivot'] + } + + static get table () { + return 'post_user' + } + } + + class User extends Model { + posts () { + return this.belongsToMany(Post) + .pivotModel(PostUser) + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + await ioc.use('Database').table('users').insert({ id: 2, username: 'virk' }) + await ioc.use('Database').table('posts').insert([{ title: 'Adonis 101' }, { title: 'Lucid 101' }]) + await ioc.use('Database').table('post_user').insert([ + { post_id: 1, user_id: 2, is_published: true }, + { post_id: 2, user_id: 2 } + ]) + + const user = await User.find(2) + const userPosts = await user.posts().fetch() + + assert.notProperty(userPosts.toJSON()[0], 'pivot') + assert.notProperty(userPosts.toJSON()[1], 'pivot') + }) + + test('hide \'pivot\' attribute when passed false to \'pivotAttribute()\' in relationship', async (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + static get table () { + return 'post_user' + } + } + + class User extends Model { + posts () { + return this.belongsToMany(Post) + .pivotModel(PostUser) + .pivotAttribute(false) + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + await ioc.use('Database').table('users').insert({ id: 2, username: 'virk' }) + await ioc.use('Database').table('posts').insert([{ title: 'Adonis 101' }, { title: 'Lucid 101' }]) + await ioc.use('Database').table('post_user').insert([ + { post_id: 1, user_id: 2, is_published: true }, + { post_id: 2, user_id: 2 } + ]) + + const user = await User.find(2) + const userPosts = await user.posts().fetch() + + assert.notProperty(userPosts.toJSON()[0], 'pivot') + assert.notProperty(userPosts.toJSON()[1], 'pivot') + }) + + test('hide \'pivot\' attribute when return false on \'pivotAttribute()\' static method in pivot model', async (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + static get table () { + return 'post_user' + } + + static get pivotAttribute () { + return false + } + } + + class User extends Model { + posts () { + return this.belongsToMany(Post) + .pivotModel(PostUser) + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + await ioc.use('Database').table('users').insert({ id: 2, username: 'virk' }) + await ioc.use('Database').table('posts').insert([{ title: 'Adonis 101' }, { title: 'Lucid 101' }]) + await ioc.use('Database').table('post_user').insert([ + { post_id: 1, user_id: 2, is_published: true }, + { post_id: 2, user_id: 2 } + ]) + + const user = await User.find(2) + const userPosts = await user.posts().fetch() + + assert.notProperty(userPosts.toJSON()[0], 'pivot') + assert.notProperty(userPosts.toJSON()[1], 'pivot') + }) + + test('rename \'pivot\' attribute when passed string to \'.pivotAttribute()\' in relationship', async (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + static get table () { + return 'post_user' + } + } + + class User extends Model { + posts () { + return this.belongsToMany(Post) + .pivotModel(PostUser) + .pivotAttribute('renamed_pivot') + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + await ioc.use('Database').table('users').insert({ id: 2, username: 'virk' }) + await ioc.use('Database').table('posts').insert([{ title: 'Adonis 101' }, { title: 'Lucid 101' }]) + await ioc.use('Database').table('post_user').insert([ + { post_id: 1, user_id: 2, is_published: true }, + { post_id: 2, user_id: 2 } + ]) + + const user = await User.find(2) + const userPosts = await user.posts().fetch() + + assert.property(userPosts.toJSON()[0], 'renamed_pivot') + assert.property(userPosts.toJSON()[1], 'renamed_pivot') + }) + + test('rename \'pivot\' attribute when returns string on \'pivotAttribute()\' static method in pivot model', async (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + static get pivotAttribute () { + return 'renamed_pivot' + } + + static get table () { + return 'post_user' + } + } + + class User extends Model { + posts () { + return this.belongsToMany(Post) + .pivotModel(PostUser) + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + await ioc.use('Database').table('users').insert({ id: 2, username: 'virk' }) + await ioc.use('Database').table('posts').insert([{ title: 'Adonis 101' }, { title: 'Lucid 101' }]) + await ioc.use('Database').table('post_user').insert([ + { post_id: 1, user_id: 2, is_published: true }, + { post_id: 2, user_id: 2 } + ]) + + const user = await User.find(2) + const userPosts = await user.posts().fetch() + + assert.property(userPosts.toJSON()[0], 'renamed_pivot') + assert.property(userPosts.toJSON()[1], 'renamed_pivot') + }) + + test('expect pivot attribute name is \'pivot\' when passing true to \'.pivotAttribute()\' in relationship.', (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + } + + class User extends Model { + posts () { + return this.belongsToMany(Post) + .pivotModel(PostUser) + .pivotAttribute(true) + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + const user = new User() + const userPosts = user.posts() + assert.equal(userPosts.$pivotAttribute, 'pivot') + }) + + test('throw exception when pivotModel is defined and calling pivotPrimaryKey', (assert) => { + class Post extends Model { + } + + class PostUser extends Model { + } + + class User extends Model { + posts () { + return this.belongsToMany(Post).pivotModel(PostUser).pivotPrimaryKey('key') + } + } + + User._bootIfNotBooted() + Post._bootIfNotBooted() + PostUser._bootIfNotBooted() + + const user = new User() + const fn = () => user.posts() + assert.throw(fn, 'E_INVALID_RELATION_METHOD: Cannot call pivotPrimaryKey since pivotModel has been defined') + }) })