Skip to content

Commit

Permalink
feat(relation): add detach and delete on belongsToMany
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Jul 5, 2017
1 parent 646d0cb commit 0e26f92
Show file tree
Hide file tree
Showing 3 changed files with 373 additions and 7 deletions.
20 changes: 16 additions & 4 deletions src/Lucid/Model/PivotModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,21 @@ class PivotModel extends BaseModel {
this.$attributes[name] = value
}

/**
* Returns query builder instance for a given connection
* and table
*
* @method query
*
* @param {String} table
* @param {Object} connection
*
* @return {Object}
*/
query (table, connection) {
return new QueryBuilder(this.constructor, connection).table(table)
}

/**
* Save the model instance to the database.
*
Expand All @@ -138,10 +153,7 @@ class PivotModel extends BaseModel {
this.$attributes['updated_at'] = moment().format(DATE_FORMAT)
}

const result = await new QueryBuilder(this, this.$connection)
.table(this.$table)
.returning('id')
.insert(this.$attributes)
const result = await this.query(this.$table, this.$connection).returning('id').insert(this.$attributes)

this.primaryKeyValue = result[0]
this.$persisted = true
Expand Down
123 changes: 120 additions & 3 deletions src/Lucid/Relations/BelongsToMany.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ class BelongsToMany extends BaseRelation {
withTimestamps: false,
withFields: []
}

/**
* Here we store the existing pivot rows, to make
* sure we are not inserting duplicates.
*
* @type {Array}
*/
this._existingPivotInstances = []
}

/**
Expand Down Expand Up @@ -233,6 +241,7 @@ class BelongsToMany extends BaseRelation {
}

const pivotModel = this._newUpPivotModel()
this._existingPivotInstances.push(pivotModel)
pivotModel.fill(pivotValues)

/**
Expand Down Expand Up @@ -275,6 +284,41 @@ class BelongsToMany extends BaseRelation {
}
}

/**
* Loads the pivot relationship and then caches
* it inside memory, so that more calls to
* this function are not hitting database.
*
* @method _loadAndCachePivot
*
* @return {void}
*
* @private
*/
async _loadAndCachePivot () {
if (_.size(this._existingPivotInstances) === 0) {
this._existingPivotInstances = (await this
.pivotQuery().fetch()
).rows
}
}

/**
* Returns the existing pivot instance for a given
* value.
*
* @method _getPivotInstance
*
* @param {String|Number} value
*
* @return {Object|Null}
*
* @private
*/
_getPivotInstance (value) {
return _.find(this._existingPivotInstances, (instance) => instance[this.relatedForeignKey] === value)
}

/**
* Define a fully qualified model to be used for
* making pivot table queries and using defining
Expand Down Expand Up @@ -481,6 +525,28 @@ class BelongsToMany extends BaseRelation {
return { key: this.primaryKey, values: transformedValues, defaultValue: new Serializer([]) }
}

/**
* Returns the query for pivot table
*
* @method pivotQuery
*
* @param {Boolean} selectFields
*
* @return {Object}
*/
pivotQuery (selectFields = true) {
const query = this._PivotModel
? this._PivotModel.query()
: new PivotModel().query(this.$pivotTable, this.RelatedModel.$connection)

if (selectFields) {
query.select(this.$pivotColumns)
}

query.where(this.foreignKey, this.$primaryKeyValue)
return query
}

/**
* Adds a where clause to limit the select search
* to related rows only.
Expand Down Expand Up @@ -515,9 +581,60 @@ class BelongsToMany extends BaseRelation {
*
* @return {Promise}
*/
async attach (relatedPrimaryKeyValue, pivotCallback = null) {
const rows = relatedPrimaryKeyValue instanceof Array === false ? [relatedPrimaryKeyValue] : relatedPrimaryKeyValue
return Promise.all(rows.map((row) => this._attachSingle(row, pivotCallback)))
async attach (references, pivotCallback = null) {
await this._loadAndCachePivot()
const rows = references instanceof Array === false ? [references] : references

return Promise.all(rows.map((row) => {
const pivotInstance = this._getPivotInstance(row)
return pivotInstance ? Promise.resolve(pivotInstance) : this._attachSingle(row, pivotCallback)
}))
}

/**
* Delete related model rows in bulk and also detach
* them from the pivot table.
*
* NOTE: This method will run 3 queries in total. First is to
* fetch the related rows, next is to delete them and final
* is to remove the relationship from pivot table.
*
* @method delete
* @async
*
* @return {Number} Number of effected rows
*/
async delete () {
this._makeJoinQuery()
this.wherePivot(this.foreignKey, this.$primaryKeyValue)
const foreignKeyValues = await this.relatedQuery.pluck(this.relatedForeignKey)
const effectedRows = await this.RelatedModel.query().whereIn(this.relatedPrimaryKey, foreignKeyValues).delete()
await this.detach(foreignKeyValues)
return effectedRows
}

/**
* Detach existing relations from the pivot table
*
* @method detach
* @async
*
* @param {Array} references
*
* @return {Number} The number of effected rows
*/
detach (references) {
const query = this.pivotQuery(false)
if (references) {
const rows = references instanceof Array === false ? [references] : references
query.whereIn(this.relatedForeignKey, rows)
_.remove(this._existingPivotInstances, (pivotInstance) => {
return _.includes(rows, pivotInstance[this.relatedForeignKey])
})
} else {
this._existingPivotInstances = []
}
return query.delete()
}

/**
Expand Down
Loading

0 comments on commit 0e26f92

Please sign in to comment.