Skip to content

Commit

Permalink
fix: pass transaction client from query builder to model instance
Browse files Browse the repository at this point in the history
The model instance must use the transaction client when transaction is pending
after the initial fetch call
  • Loading branch information
thetutlage committed Nov 8, 2019
1 parent 35103b5 commit 2851a21
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 7 deletions.
4 changes: 2 additions & 2 deletions adonis-typings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ declare module '@ioc:Adonis/Lucid/Model' {
this: T,
result?: ModelObject,
sideloadAttributes?: ModelObject,
options?: ModelOptions,
options?: ModelAdapterOptions,
): null | InstanceType<T>

/**
Expand All @@ -616,7 +616,7 @@ declare module '@ioc:Adonis/Lucid/Model' {
this: T,
results: ModelObject[],
sideloadAttributes?: ModelObject,
options?: ModelOptions,
options?: ModelAdapterOptions,
): InstanceType<T>[]

/**
Expand Down
14 changes: 11 additions & 3 deletions src/Orm/BaseModel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export class BaseModel implements ModelContract {
/**
* Returns the model query instance for the given model
*/
public static query (options?: ModelOptions): any {
public static query (options?: ModelAdapterOptions): any {
return this.$adapter.query(this, options)
}

Expand All @@ -171,7 +171,7 @@ export class BaseModel implements ModelContract {
public static $createFromAdapterResult (
adapterResult: ModelObject,
sideloadAttributes?: ModelObject,
options?: ModelOptions,
options?: ModelAdapterOptions,
): any | null {
if (typeof (adapterResult) !== 'object' || Array.isArray(adapterResult)) {
return null
Expand All @@ -180,6 +180,14 @@ export class BaseModel implements ModelContract {
const instance = new this()
instance.$consumeAdapterResult(adapterResult, sideloadAttributes)
instance.$hydrateOriginals()

/**
* Set $trx when defined
*/
if (options && options.client && options.client.isTransaction) {
instance.$trx = options.client as TransactionClientContract
}

instance.$options = options
instance.$persisted = true
instance.$isLocal = false
Expand All @@ -198,7 +206,7 @@ export class BaseModel implements ModelContract {
this: T,
adapterResults: ModelObject[],
sideloadAttributes?: ModelObject,
options?: ModelOptions,
options?: ModelAdapterOptions,
): InstanceType<T>[] {
if (!Array.isArray(adapterResults)) {
return []
Expand Down
5 changes: 3 additions & 2 deletions src/Orm/QueryBuilder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { Exception } from '@poppinss/utils'

import {
ModelObject,
ModelOptions,
ModelContract,
ModelAdapterOptions,
ModelConstructorContract,
ModelQueryBuilderContract,
} from '@ioc:Adonis/Lucid/Model'
Expand Down Expand Up @@ -55,7 +55,8 @@ export class ModelQueryBuilder extends Chainable implements ModelQueryBuilderCon
/**
* Options that must be passed to all new model instances
*/
public clientOptions: ModelOptions = {
public clientOptions: ModelAdapterOptions = {
client: this.client,
connection: this.client.connectionName,
profiler: this.client.profiler,
}
Expand Down
136 changes: 136 additions & 0 deletions test/orm/base-model-options.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,58 @@ test.group('Model options | QueryBuilder', (group) => {
assert.equal(user!.$options!.connection, 'primary')
assert.instanceOf(user!.$options!.profiler, Profiler)
})

test('query builder use transaction when updating rows', async (assert) => {
class User extends BaseModel {
public static $table = 'users'

@column({ primary: true })
public id: number

@column()
public username: string
}

await db.insertQuery().table('users').insert({ username: 'virk' })
const trx = await db.transaction()

const users = await User.query({ client: trx }).exec()
assert.lengthOf(users, 1)

users[0].username = 'nikk'
await users[0].save()

await trx.rollback()

const usersFresh = await User.query().exec()
assert.equal(usersFresh[0].username, 'virk')
})

test('cleanup transaction reference after commit or rollback', async (assert) => {
class User extends BaseModel {
public static $table = 'users'

@column({ primary: true })
public id: number

@column()
public username: string
}

await db.insertQuery().table('users').insert({ username: 'virk' })
const trx = await db.transaction()

const users = await User.query({ client: trx }).exec()
assert.lengthOf(users, 1)
await trx.commit()

assert.isUndefined(users[0].$trx)
users[0].username = 'nikk'
await users[0].save()

const usersFresh = await User.query().exec()
assert.equal(usersFresh[0].username, 'nikk')
})
})

test.group('Model options | Adapter', (group) => {
Expand Down Expand Up @@ -730,6 +782,90 @@ test.group('Model options | Query Builder Preloads', (group) => {
assert.deepEqual(users[0].$sideloaded, { id: 1 })
assert.deepEqual(users[0].profile.$sideloaded, { id: 2 })
})

test('use transaction client to update preloaded rows', async (assert) => {
class Profile extends BaseModel {
@column({ primary: true })
public id: number

@column()
public userId: number

@column()
public displayName: string
}

class User extends BaseModel {
@column({ primary: true })
public id: number

@column()
public username: string

@hasOne(() => Profile)
public profile: Profile
}

await db.insertQuery().table('users').insert({ username: 'virk' })
await db.insertQuery().table('profiles').insert({ user_id: 1, display_name: 'Virk' })

const trx = await db.transaction()
const users = await User.query({ client: trx }).preload('profile').exec()

assert.lengthOf(users, 1)

users[0].profile.displayName = 'Nikk'
await users[0].profile.save()

await trx.rollback()

const profiles = await Profile.all()
assert.lengthOf(profiles, 1)
assert.equal(profiles[0].displayName, 'Virk')
})

test('cleanup transaction reference after commit or rollback', async (assert) => {
class Profile extends BaseModel {
@column({ primary: true })
public id: number

@column()
public userId: number

@column()
public displayName: string
}

class User extends BaseModel {
@column({ primary: true })
public id: number

@column()
public username: string

@hasOne(() => Profile)
public profile: Profile
}

await db.insertQuery().table('users').insert({ username: 'virk' })
await db.insertQuery().table('profiles').insert({ user_id: 1, display_name: 'Virk' })

const trx = await db.transaction()
const users = await User.query({ client: trx }).preload('profile').exec()

assert.lengthOf(users, 1)
await trx.commit()

assert.isUndefined(users[0].$trx)
assert.isUndefined(users[0].profile.$trx)

users[0].profile.displayName = 'Nikk'
await users[0].profile.save()

const profiles = await Profile.all()
assert.lengthOf(profiles, 1)
assert.equal(profiles[0].displayName, 'Nikk')
})
})

test.group('Model options | Model Preloads', (group) => {
Expand Down

0 comments on commit 2851a21

Please sign in to comment.