Skip to content

Commit

Permalink
feat(relations): allow to hide or rename pivot attribute on belongsTo… (
Browse files Browse the repository at this point in the history
#433)

* 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.
  • Loading branch information
italoiz authored and thetutlage committed Jan 12, 2020
1 parent 2cf02e1 commit 3dc5376
Show file tree
Hide file tree
Showing 2 changed files with 316 additions and 3 deletions.
84 changes: 81 additions & 3 deletions src/Lucid/Relations/BelongsToMany.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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
)
}

/**
Expand Down Expand Up @@ -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.
Expand Down
235 changes: 235 additions & 0 deletions test/unit/lucid-belongs-to-many.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})

0 comments on commit 3dc5376

Please sign in to comment.