Skip to content

Commit

Permalink
feat: add method
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Dec 11, 2019
1 parent 43c129e commit ab9cec4
Show file tree
Hide file tree
Showing 3 changed files with 225 additions and 14 deletions.
16 changes: 13 additions & 3 deletions adonis-typings/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,9 +490,19 @@ declare module '@ioc:Adonis/Lucid/Model' {
/**
* Read/write realtionships
*/
$hasRelated (key: string): boolean
$setRelated (key: string, result: ModelContract | ModelContract[]): void
$getRelated (key: string, defaultValue?: any): ModelContract
$hasRelated<K extends keyof this = keyof this> (key: K): boolean
$setRelated<K extends keyof this = keyof this> (
key: string,
result: ModelContract | ModelContract[]
): void
$pushRelated<K extends keyof this = keyof this> (
key: K,
result: ModelContract | ModelContract[],
): void
$getRelated<K extends keyof this = keyof this> (
key: K,
defaultValue?: any,
): ModelContract

/**
* Consume the adapter result and hydrate the model
Expand Down
55 changes: 45 additions & 10 deletions src/Orm/BaseModel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import { ManyToMany } from '../Relations/ManyToMany'
import { ensureRelation, isObject } from '../../utils'
import { HasManyThrough } from '../Relations/HasManyThrough'

const MANY_RELATIONS = ['hasMany', 'manyToMany', 'hasManyThrough']

function StaticImplements<T> () {
return (_t: T) => {}
}
Expand Down Expand Up @@ -797,22 +799,22 @@ export class BaseModel implements ModelContract {
/**
* Returns the related model or default value when model is missing
*/
public $getRelated (key: string): any {
public $getRelated (key: any): any {
return this.$preloaded[key]
}

/**
* A boolean to know if relationship has been preloaded or not
*/
public $hasRelated (key: string): boolean {
public $hasRelated (key: any): boolean {
return this.$preloaded[key] !== undefined
}

/**
* Sets the related data on the model instance. The method internally handles
* `one to one` or `many` relations
*/
public $setRelated (key: string, models: ModelContract | ModelContract[]) {
public $setRelated (key: any, models: ModelContract | ModelContract[]) {
const Model = this.constructor as typeof BaseModel
const relation = Model.$relations.get(key as string)

Expand All @@ -824,19 +826,52 @@ export class BaseModel implements ModelContract {
}

/**
* Create multiple for `hasMany` and one for `belongsTo` and `hasOne`
* Reset array before invoking $pushRelated
*/
const manyRelationships = ['hasMany', 'manyToMany', 'hasManyThrough']
if (manyRelationships.includes(relation.type)) {
if (MANY_RELATIONS.includes(relation.type)) {
if (!Array.isArray(models)) {
throw new Exception(
`${Model}.${key} must be an array (${manyRelationships.join(',')} relationships)`,
`${Model.name}.${key} must be an array when setting ${relation.type} relationship`,
)
}
this.$preloaded[key] = models
} else {
this.$preloaded[key] = models as unknown as ModelContract
this.$preloaded[key] = []
}

return this.$pushRelated(key, models)
}

/**
* Push related adds to the existing related collection
*/
public $pushRelated (key: any, models: ModelContract | ModelContract[]) {
const Model = this.constructor as typeof BaseModel
const relation = Model.$relations.get(key as string)

/**
* Ignore when relation is not defined
*/
if (!relation) {
return
}

/**
* Create multiple for `hasMany` `manyToMany` and `hasManyThrough`
*/
if (MANY_RELATIONS.includes(relation.type)) {
this.$preloaded[key] = ((this.$preloaded[key] || []) as ModelContract[]).concat(models)
return
}

/**
* Dis-allow setting multiple model instances for a one to one relationship
*/
if (Array.isArray(models)) {
throw new Error(
`${Model.name}.${key} cannot reference more than one instance of ${relation.relatedModel().name} model`
)
}

this.$preloaded[key] = models
}

/**
Expand Down
168 changes: 167 additions & 1 deletion test/orm/base-model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
/// <reference path="../../adonis-typings/index.ts" />

import test from 'japa'
import { column, computed, hasOne } from '../../src/Orm/Decorators'
import { column, computed, hasMany, hasOne } from '../../src/Orm/Decorators'
import {
getDb,
cleanup,
Expand Down Expand Up @@ -1310,6 +1310,172 @@ test.group('Base Model | relations', (group) => {
},
})
})

test('push relationship', async (assert) => {
const adapter = new FakeAdapter()
class Profile extends BaseModel {
@column()
public username: string

@column()
public userId: number
}

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

@hasMany(() => Profile)
public profiles: Profile[]
}

const user = new User()
Profile.$adapter = adapter
user.$consumeAdapterResult({ id: 1 })
user.$pushRelated('profiles', await Profile.create({ username: 'nikk' }))

assert.deepEqual(user.toJSON(), {
id: 1,
profiles: [
{
username: 'nikk',
},
],
})
})

test('push relationship to existing list', async (assert) => {
const adapter = new FakeAdapter()
class Profile extends BaseModel {
@column()
public username: string

@column()
public userId: number
}

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

@hasMany(() => Profile)
public profiles: Profile[]
}

const user = new User()
Profile.$adapter = adapter
user.$consumeAdapterResult({ id: 1 })
user.$setRelated('profiles', [await Profile.create({ username: 'virk' })])
user.$pushRelated('profiles', await Profile.create({ username: 'nikk' }))

assert.deepEqual(user.toJSON(), {
id: 1,
profiles: [
{
username: 'virk',
},
{
username: 'nikk',
},
],
})
})

test('push an array of relationships', async (assert) => {
const adapter = new FakeAdapter()
class Profile extends BaseModel {
@column()
public username: string

@column()
public userId: number
}

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

@hasMany(() => Profile)
public profiles: Profile[]
}

const user = new User()
Profile.$adapter = adapter
user.$consumeAdapterResult({ id: 1 })
user.$pushRelated('profiles', [
await Profile.create({ username: 'virk' }),
await Profile.create({ username: 'nikk' }),
])

assert.deepEqual(user.toJSON(), {
id: 1,
profiles: [
{
username: 'virk',
},
{
username: 'nikk',
},
],
})
})

test('raise error when pushing an array of relationships for hasOne', async (assert) => {
const adapter = new FakeAdapter()
class Profile extends BaseModel {
@column()
public username: string

@column()
public userId: number
}

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

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

const user = new User()
Profile.$adapter = adapter
user.$consumeAdapterResult({ id: 1 })

const profile = await Profile.create({ username: 'virk' })
const profile1 = await Profile.create({ username: 'virk' })

const fn = () => user.$pushRelated('profile', [profile, profile1])
assert.throw(fn, 'User.profile cannot reference more than one instance of Profile model')
})

test('raise error when setting single relationships for hasMany', async (assert) => {
const adapter = new FakeAdapter()
class Profile extends BaseModel {
@column()
public username: string

@column()
public userId: number
}

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

@hasMany(() => Profile)
public profiles: Profile[]
}

const user = new User()
Profile.$adapter = adapter
user.$consumeAdapterResult({ id: 1 })

const profile = await Profile.create({ username: 'virk' })

const fn = () => user.$setRelated('profiles', profile)
assert.throw(fn, 'User.profiles must be an array when setting hasMany relationship')
})
})

test.group('Base Model | fetch', (group) => {
Expand Down

0 comments on commit ab9cec4

Please sign in to comment.