Skip to content

Commit

Permalink
fix(belongstomany): add transaction support to attach,detach & sync
Browse files Browse the repository at this point in the history
fix #244
  • Loading branch information
thetutlage committed Dec 23, 2017
1 parent 4d25fcc commit d6fa6aa
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 17 deletions.
13 changes: 10 additions & 3 deletions src/Lucid/Model/PivotModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,11 @@ class PivotModel extends BaseModel {
* @method save
* @async
*
* @param {Object} trx
*
* @return {void}
*/
async save () {
async save (trx) {
/**
* Set timestamps when user has defined them on pivot
* relationship via `withTimestamps` method.
Expand All @@ -154,8 +156,13 @@ class PivotModel extends BaseModel {
this.$attributes['updated_at'] = moment().format(DATE_FORMAT)
}

const result = await this
.query(this.$table, this.$connection)
const query = this.query(this.$table, this.$connection)

if (trx) {
query.transacting(trx)
}

const result = await query
.returning('id')
.insert(this.$attributes)

Expand Down
45 changes: 31 additions & 14 deletions src/Lucid/Relations/BelongsToMany.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,12 +318,13 @@ class BelongsToMany extends BaseRelation {
*
* @param {Number|String} value
* @param {Function} [pivotCallback]
* @param {Object} [trx]
*
* @return {Object} Instance of pivot model
*
* @private
*/
async _attachSingle (value, pivotCallback) {
async _attachSingle (value, pivotCallback, trx) {
/**
* The relationship values
*
Expand Down Expand Up @@ -357,7 +358,7 @@ class BelongsToMany extends BaseRelation {
pivotCallback(pivotModel)
}

await pivotModel.save()
await pivotModel.save(trx)
return pivotModel
}

Expand Down Expand Up @@ -391,11 +392,16 @@ class BelongsToMany extends BaseRelation {
*
* @private
*/
async _loadAndCachePivot () {
async _loadAndCachePivot (trx) {
if (_.size(this._existingPivotInstances) === 0) {
this._existingPivotInstances = (await this
.pivotQuery().fetch()
).rows
const query = this.pivotQuery()

if (trx) {
query.transacting(trx)
}

const result = await query.fetch()
this._existingPivotInstances = result.rows
}
}

Expand Down Expand Up @@ -736,16 +742,17 @@ class BelongsToMany extends BaseRelation {
*
* @param {Number|String|Array} references
* @param {Function} [pivotCallback]
* @param {trx} Transaction
*
* @return {Promise}
*/
async attach (references, pivotCallback = null) {
await this._loadAndCachePivot()
async attach (references, pivotCallback = null, trx) {
await this._loadAndCachePivot(trx)
const rows = !Array.isArray(references) ? [references] : references

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

Expand Down Expand Up @@ -797,12 +804,14 @@ class BelongsToMany extends BaseRelation {
* @method detach
* @async
*
* @param {Array} references
* @param {Array} references
* @param {Object} trx
*
* @return {Number} The number of effected rows
*/
detach (references) {
detach (references, trx) {
const query = this.pivotQuery(false)

if (references) {
const rows = !Array.isArray(references) ? [references] : references
query.whereIn(this.relatedForeignKey, rows)
Expand All @@ -812,6 +821,14 @@ class BelongsToMany extends BaseRelation {
} else {
this._existingPivotInstances = []
}

/**
* Wrap inside transaction if trx is passed
*/
if (trx) {
query.transacting(trx)
}

return query.delete()
}

Expand All @@ -825,9 +842,9 @@ class BelongsToMany extends BaseRelation {
*
* @return {void}
*/
async sync (references, pivotCallback) {
await this.detach()
return this.attach(references, pivotCallback)
async sync (references, pivotCallback, trx) {
await this.detach(null, trx)
return this.attach(references, pivotCallback, trx)
}

/**
Expand Down
118 changes: 118 additions & 0 deletions test/unit/lucid-belongs-to-many.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2165,4 +2165,122 @@ 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 "posts"."deleted_at" is null)'))
})

test('attach existing model inside transaction', async (assert) => {
class Post extends Model {
}

class User extends Model {
posts () {
return this.belongsToMany(Post)
}
}

User._bootIfNotBooted()
Post._bootIfNotBooted()

const trx = await ioc.use('Database').beginTransaction()

const user = new User()
user.username = 'virk'
await user.save(trx)

await user.posts().attach(1, null, trx)
trx.rollback()

const pivotValues = await ioc.use('Database').table('post_user')
assert.lengthOf(pivotValues, 0)
})

test('sync data inside a transaction', async (assert) => {
class Post extends Model {
}

class User extends Model {
posts () {
return this.belongsToMany(Post)
}
}

User._bootIfNotBooted()
Post._bootIfNotBooted()

const trx = await ioc.use('Database').beginTransaction()

const user = new User()
user.username = 'virk'
await user.save(trx)

await user.posts().sync(1, null, trx)
trx.rollback()

const pivotValues = await ioc.use('Database').table('post_user')
assert.lengthOf(pivotValues, 0)
})

test('attach data inside transaction using custom pivotModel', 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)
}
}

User._bootIfNotBooted()
Post._bootIfNotBooted()
PostUser._bootIfNotBooted()

const trx = await ioc.use('Database').beginTransaction()

const user = new User()
user.username = 'virk'
await user.save(trx)

await user.posts().attach(1, null, trx)
trx.rollback()

const pivotValues = await ioc.use('Database').table('post_user')
assert.lengthOf(pivotValues, 0)
})

test('sync data inside transaction using custom pivotModel', 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)
}
}

User._bootIfNotBooted()
Post._bootIfNotBooted()
PostUser._bootIfNotBooted()

const trx = await ioc.use('Database').beginTransaction()

const user = new User()
user.username = 'virk'
await user.save(trx)

await user.posts().sync(1, null, trx)
trx.rollback()

const pivotValues = await ioc.use('Database').table('post_user')
assert.lengthOf(pivotValues, 0)
})
})

0 comments on commit d6fa6aa

Please sign in to comment.