Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(relations): allow to hide or rename pivot attribute on belongsTo… #433

Merged
merged 5 commits into from
Jan 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')
})
})