toJSON: Hide ’pivot" but extract some pivot attrs as instance attrs


#1

Imagine, I have a model users and groups with manyToMany relation. Connecting table has an additional attribute role, defining user’s role in each group:

groups() {
    return this.belongsToMany('App/Models/Group', 'userId', 'groupId')
       .pivotModel(UserGroup)
       .withPivot(['role'])
  }

When requesting user.groups().fetch() ... toJSON(), I would like to get

{
  name: 'John',
  role: 'customer'
}

but currently Adonis 4.0 produces only:

{
  name: 'John',
  pivot: { userId: 1, groupId: 2, role: 'customer' }
}

In Adonis 3.x I was able to set computed property role and then:

static get computed() {
    return ['role']
  }

  getRole() {
    return this._pivot_role
  }

  static get hidden() {
    return ['_pivot_userId', '_pivot_groupId', '_pivot_role']
  }

To get the desired result. However, it appears to be that Adonis 4.0 does not pass pivot props into getter functions and ignores pivot in hidden getter. So

// This works but looks real dirty
getRole(ctx) {
    return this.$relations.pivot.$attributes.role
  }

// This does not work at all
  static get hidden() {
    return ['pivot']
  }

What would be the best way to hide pivot key from JSON output of the instance and to extract role right into attrs instead?


#2

how did you resolve this issue ?


#3

Well, I resolved it by creating my own serializer like this:

const VanillaSerializer = require('@adonisjs/lucid/src/Lucid/Serializers/Vanilla')
const camelcase = require('camelcase')

class GenericSerializer extends VanillaSerializer {
  // Taken from https://github.com/adonisjs/adonis-lucid/blob/21d42c0d80e7d98397336d55312bcb284925584c/src/Lucid/Serializers/Vanilla.js
  /**
   * Returns the json object for a given model instance
   *
   * @method _getRowJSON
   *
   * @param  {Model}    modelInstance
   *
   * @return {Object}
   *
   * @private
   */
  _getRowJSON(modelInstance) {
    const json = modelInstance.toObject()
    this._attachRelations(modelInstance, json)
    this._attachMeta(modelInstance, json)

    // Delete meta & pivot fields
    delete json.__meta__
    delete json.pivot

    // If relationship is present, remove linked fields
    this._deleteRelationLinks(modelInstance, json)

    return json
  }

  _deleteRelationLinks(modelInstance, json) {
    if (!modelInstance.$relations) return
    const loadedRelations = Object.keys(modelInstance.$relations)
    loadedRelations.forEach(r => { delete json[`${r}Id`] })
  }
}

module.exports = GenericSerializer

And then connecting Serializer in my models like:

const GenericSerializer = use('App/Serializers/GenericSerializer')

class Generic extends Model {

  static get Serializer() {
    return GenericSerializer
  }

 static get computed() {
    return ['role']
  }

  // Get role via a bit ugly but still working computed method. Note that in some cases attr is in $sideLoaded and in other in relations
  getRole() {
    if (this.$relations && this.$relations.pivot) {
      return this.$relations.pivot.$attributes.role
    } else if (this.$sideLoaded && this.$sideLoaded.pivot_role) {
      return this.$sideLoaded.pivot_role
    }

    return undefined
  }
}