key(keyName) {
      if (this[keyName]) return this[keyName];
      switch (keyName) {
        case 'otherKey':
          this[keyName] = singularMemo(this.targetTableName) + '_' + this.targetIdAttribute;
          break;
        case 'throughForeignKey':
          this[keyName] = singularMemo(this.joinTable()) + '_' + this.throughIdAttribute;
          break;
        case 'foreignKey':
          switch (this.type) {
            case 'morphTo': {
              const idKeyName = this.columnNames && this.columnNames[1] ? this.columnNames[1] : this.morphName + '_id';
              this[keyName] = idKeyName;
              break;
            }
            case 'belongsTo':
              this[keyName] = singularMemo(this.targetTableName) + '_' + this.targetIdAttribute;
              break;
            default:
              if (this.isMorph()) {
                this[keyName] = this.columnNames && this.columnNames[1] ? this.columnNames[1] : this.morphName + '_id';
                break;
              }
              this[keyName] = singularMemo(this.parentTableName) + '_' + this.parentIdAttribute;
              break;
          }
          break;
        case 'morphKey':
          this[keyName] = this.columnNames && this.columnNames[0] ? this.columnNames[0] : this.morphName + '_type';
          break;
        case 'morphValue':
          this[keyName] = this.morphValue || this.parentTableName || this.targetTableName;
          break;
      }
      return this[keyName];
    }
Generates and returns a specified key.

@param {string} keyName
  Can be one of `foreignKey`, `morphKey`, `morphValue`, `otherKey` or `throughForeignKey`.
@return {string|undefined}