From 8bec10a47d9220e9f43d9cd543dbf01c9038d78e Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Tue, 23 Nov 2021 15:53:32 -0800 Subject: [PATCH 01/21] Init MorphMany --- src/index.ts | 4 + src/model/Model.ts | 17 ++ src/model/attributes/relations/MorphMany.ts | 102 +++++++++++ .../attributes/relations/MorphMany.ts | 20 +++ .../relations/morph_many_retrieve.spec.ts | 164 ++++++++++++++++++ 5 files changed, 307 insertions(+) create mode 100644 src/model/attributes/relations/MorphMany.ts create mode 100644 src/model/decorators/attributes/relations/MorphMany.ts create mode 100644 test/feature/relations/morph_many_retrieve.spec.ts diff --git a/src/index.ts b/src/index.ts index b8a78fa..f358616 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,6 +16,7 @@ export * from './model/decorators/attributes/relations/BelongsTo' export * from './model/decorators/attributes/relations/HasMany' export * from './model/decorators/attributes/relations/HasManyBy' export * from './model/decorators/attributes/relations/MorphOne' +export * from './model/decorators/attributes/relations/MorphMany' export * from './model/decorators/Contracts' export * from './model/decorators/NonEnumerable' export * from './model/attributes/Attribute' @@ -31,6 +32,7 @@ export { BelongsTo as BelongsToAttr } from './model/attributes/relations/Belongs export { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' export { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' export { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' +export { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' export * from './modules/RootModule' export * from './modules/RootState' export * from './modules/Module' @@ -63,6 +65,7 @@ import { BelongsTo as BelongsToAttr } from './model/attributes/relations/Belongs import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' +import { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' import { Repository } from './repository/Repository' import { Interpreter } from './interpreter/Interpreter' import { Query } from './query/Query' @@ -88,6 +91,7 @@ export default { HasManyAttr, HasManyByAttr, MorphOneAttr, + MorphManyAttr, Repository, Interpreter, Query, diff --git a/src/model/Model.ts b/src/model/Model.ts index e99d548..fe9bfb8 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -12,6 +12,7 @@ import { BelongsTo } from './attributes/relations/BelongsTo' import { HasMany } from './attributes/relations/HasMany' import { HasManyBy } from './attributes/relations/HasManyBy' import { MorphOne } from './attributes/relations/MorphOne' +import { MorphMany } from './attributes/relations/MorphMany' export type ModelFields = Record export type ModelSchemas = Record @@ -247,6 +248,22 @@ export class Model { return new MorphOne(model, related.newRawInstance(), id, type, localKey) } + /** + * Create a new MorphMany relation instance. + */ + static morphMany( + related: typeof Model, + id: string, + type: string, + localKey?: string + ): MorphMany { + const model = this.newRawInstance() + + localKey = localKey ?? model.$getLocalKey() + + return new MorphMany(model, related.newRawInstance(), id, type, localKey) + } + /** * Get the constructor for this model. */ diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts new file mode 100644 index 0000000..1c04beb --- /dev/null +++ b/src/model/attributes/relations/MorphMany.ts @@ -0,0 +1,102 @@ +import { Schema as NormalizrSchema } from 'normalizr' +import { Schema } from '../../../schema/Schema' +import { Element, Collection } from '../../../data/Data' +import { Query } from '../../../query/Query' +import { Model } from '../../Model' +import { Relation, Dictionary } from './Relation' + +export class MorphMany extends Relation { + /** + * The field name that contains id of the parent model. + */ + protected morphId: string + + /** + * The field name that contains type of the parent model. + */ + protected morphType: string + + /** + * The local key of the model. + */ + protected localKey: string + + /** + * Create a new morph-many relation instance. + */ + constructor( + parent: Model, + related: Model, + morphId: string, + morphType: string, + localKey: string + ) { + super(parent, related) + this.morphId = morphId + this.morphType = morphType + this.localKey = localKey + } + + /** + * Get all related models for the relationship. TODO + */ + getRelateds(): Model[] { + return [this.related] + } + + /** + * Define the normalizr schema for the relation. TODO + */ + define(schema: Schema): NormalizrSchema { + return schema.many(this.related, this.parent) + } + + /** + * Attach the parent type and id to the given relation. TODO + */ + attach(record: Element, child: Element): void { + child[this.morphId] = record[this.localKey] + child[this.morphType] = this.parent.$entity() + } + + /** + * Set the constraints for an eager load of the relation. TODO + */ + addEagerConstraints(query: Query, models: Collection): void { + query.where(this.morphType, this.parent.$entity()) + query.whereIn(this.morphId, this.getKeys(models, this.localKey)) + } + + /** + * Match the eagerly loaded results to their parents. + */ + match(relation: string, models: Collection, results: Collection): void { + const dictionary = this.buildDictionary(results) + + models.forEach((model) => { + const key = model[this.localKey] + + dictionary[key] + ? model.$setRelation(relation, dictionary[key]) + : model.$setRelation(relation, []) + }) + } + + /** + * Build model dictionary keyed by the relation's foreign key. + */ + protected buildDictionary(results: Collection): Dictionary { + return this.mapToDictionary(results, (result) => { + return [result[this.morphId], result] + }) + } + + /** + * Make related models. TODO + */ + make(elements?: Element[]): Model[] { + return elements + ? elements.map((element) => this.related.$newInstance(element)) + : [] + } +} diff --git a/src/model/decorators/attributes/relations/MorphMany.ts b/src/model/decorators/attributes/relations/MorphMany.ts new file mode 100644 index 0000000..fabf01a --- /dev/null +++ b/src/model/decorators/attributes/relations/MorphMany.ts @@ -0,0 +1,20 @@ +import { Model } from '../../../Model' +import { PropertyDecorator } from '../../Contracts' + +/** + * Create a morph-many attribute property decorator. + */ +export function MorphMany( + related: () => typeof Model, + id: string, + type: string, + localKey?: string +): PropertyDecorator { + return (target, propertyKey) => { + const self = target.$self() + + self.setRegistry(propertyKey, () => + self.morphMany(related(), id, type, localKey) + ) + } +} diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts new file mode 100644 index 0000000..0c76afe --- /dev/null +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -0,0 +1,164 @@ +import { createStore, fillState, assertModel, assertInstanceOf } from 'test/Helpers' +import { Model, Str, Num, MorphMany } from '@/index' + +describe('feature/relations/morph_many_retrieve', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Num(0) commentableId!: number + @Str('') commentableType!: string + } + + class Video extends Model { + static entity = 'videos' + + @Num(0) id!: number + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + class Post extends Model { + static entity = 'posts' + + @Num(0) id!: number + @Str('') title!: string + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + const ENTITIES = { + videos: { 1: { id: 1, link: '/video.mp4' } }, + posts: { + 1: { id: 1, title: 'Hello, world!' }, + 2: { id: 2, title: 'Hello, world! Again!' } + }, + comments: { + 1: { + id: 1, + body: 'Cool Video!', + commentableId: 1, + commentableType: 'videos' + }, + 2: { + id: 2, + body: 'Cool Video Again!', + commentableId: 1, + commentableType: 'videos' + }, + 3: { + id: 3, + body: 'Cool Post!', + commentableId: 1, + commentableType: 'posts' + }, + 4: { + id: 4, + body: 'Cool Post 2!', + commentableId: 2, + commentableType: 'posts' + } + } + } + + describe('when there are comments', () => { + const store = createStore() + + fillState(store, ENTITIES) + + it('can eager load morph many relation for video', () => { + const video = store.$repo(Video).with('comments').first()! + + expect(video).toBeInstanceOf(Video) + assertInstanceOf(video.comments, Comment) + assertModel(video, { + id: 1, + link: '/video.mp4', + comments: [ + { + id: 1, + body: 'Cool Video!', + commentableId: 1, + commentableType: 'videos' + }, + { + id: 2, + body: 'Cool Video Again!', + commentableId: 1, + commentableType: 'videos' + } + ] + }) + }) + + it('can eager load morph one relation for post', () => { + const post = store.$repo(Post).with('comments').first()! + + expect(post).toBeInstanceOf(Post) + assertInstanceOf(post.comments, Comment) + assertModel(post, { + id: 1, + title: 'Hello, world!', + comments: [{ + id: 3, + body: 'Cool Post!', + commentableId: 1, + commentableType: 'posts' + }] + }) + }) + }) + + describe('when there are no images', () => { + const store = createStore() + + fillState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + posts: {}, + comments: {} + }) + + it('can eager load missing relation as empty array', () => { + const video = store.$repo(Video).with('comments').first()! + + expect(video).toBeInstanceOf(Video) + assertModel(video, { + id: 1, + link: '/video.mp4', + comments: [] + }) + }) + }) + + it('can revive "has many" relations', () => { + const store = createStore() + + fillState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: { + 1: { id: 1, commentableId: 1, commentableType: 'videos', body: 'Cool Video!' }, + 2: { id: 2, commentableId: 1, commentableType: 'videos', body: 'Cool Video Again!' } + } + }) + + const schema = { + id: '1', + comments: [{ id: 2 }, { id: 1 }] + } + + const video = store.$repo(Video).revive(schema)! + + expect(video.comments.length).toBe(2) + expect(video.comments[0]).toBeInstanceOf(Comment) + expect(video.comments[1]).toBeInstanceOf(Comment) + expect(video.comments[0].id).toBe(2) + expect(video.comments[1].id).toBe(1) + }) +}) From 2f75dcd1dd2956c46f5c2ba897e9a139a9ad4686 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Wed, 24 Nov 2021 14:57:30 -0800 Subject: [PATCH 02/21] Adding MorphMany tests, removing TODOs from tested MorphMany methods. --- src/model/attributes/relations/MorphMany.ts | 8 +- .../relations/morph_many_retrieve.spec.ts | 43 ++++-- .../feature/relations/morph_many_save.spec.ts | 127 ++++++++++++++++++ .../morph_many_save_custom_key.spec.ts | 121 +++++++++++++++++ .../relations/morph_many_save_uid.spec.ts | 111 +++++++++++++++ test/unit/model/Model_Relations.spec.ts | 32 ++++- 6 files changed, 424 insertions(+), 18 deletions(-) create mode 100644 test/feature/relations/morph_many_save.spec.ts create mode 100644 test/feature/relations/morph_many_save_custom_key.spec.ts create mode 100644 test/feature/relations/morph_many_save_uid.spec.ts diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts index 1c04beb..0ef330c 100644 --- a/src/model/attributes/relations/MorphMany.ts +++ b/src/model/attributes/relations/MorphMany.ts @@ -45,14 +45,14 @@ export class MorphMany extends Relation { } /** - * Define the normalizr schema for the relation. TODO + * Define the normalizr schema for the relation. */ define(schema: Schema): NormalizrSchema { return schema.many(this.related, this.parent) } /** - * Attach the parent type and id to the given relation. TODO + * Attach the parent type and id to the given relation. */ attach(record: Element, child: Element): void { child[this.morphId] = record[this.localKey] @@ -60,7 +60,7 @@ export class MorphMany extends Relation { } /** - * Set the constraints for an eager load of the relation. TODO + * Set the constraints for an eager load of the relation. */ addEagerConstraints(query: Query, models: Collection): void { query.where(this.morphType, this.parent.$entity()) @@ -92,7 +92,7 @@ export class MorphMany extends Relation { } /** - * Make related models. TODO + * Make related models. */ make(elements?: Element[]): Model[] { return elements diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 0c76afe..1629ff3 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -1,5 +1,10 @@ -import { createStore, fillState, assertModel, assertInstanceOf } from 'test/Helpers' -import { Model, Str, Num, MorphMany } from '@/index' +import { + createStore, + fillState, + assertModel, + assertInstanceOf +} from 'test/Helpers' +import { Model, Attr, Str, Num, MorphMany } from '@/index' describe('feature/relations/morph_many_retrieve', () => { class Comment extends Model { @@ -7,8 +12,8 @@ describe('feature/relations/morph_many_retrieve', () => { @Num(0) id!: number @Str('') body!: string - @Num(0) commentableId!: number - @Str('') commentableType!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null } class Video extends Model { @@ -102,12 +107,14 @@ describe('feature/relations/morph_many_retrieve', () => { assertModel(post, { id: 1, title: 'Hello, world!', - comments: [{ - id: 3, - body: 'Cool Post!', - commentableId: 1, - commentableType: 'posts' - }] + comments: [ + { + id: 3, + body: 'Cool Post!', + commentableId: 1, + commentableType: 'posts' + } + ] }) }) }) @@ -135,7 +142,7 @@ describe('feature/relations/morph_many_retrieve', () => { }) }) - it('can revive "has many" relations', () => { + it('can revive "morph many" relations', () => { const store = createStore() fillState(store, { @@ -143,8 +150,18 @@ describe('feature/relations/morph_many_retrieve', () => { 1: { id: 1, link: '/video.mp4' } }, comments: { - 1: { id: 1, commentableId: 1, commentableType: 'videos', body: 'Cool Video!' }, - 2: { id: 2, commentableId: 1, commentableType: 'videos', body: 'Cool Video Again!' } + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } } }) diff --git a/test/feature/relations/morph_many_save.spec.ts b/test/feature/relations/morph_many_save.spec.ts new file mode 100644 index 0000000..212abe3 --- /dev/null +++ b/test/feature/relations/morph_many_save.spec.ts @@ -0,0 +1,127 @@ +import { createStore, fillState, assertState } from 'test/Helpers' +import { Model, Attr, Num, Str, MorphMany } from '@/index' + +describe('feature/relations/morph_many_save', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Num(0) id!: number + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + it('saves a model to the store with "morph many" relation', () => { + const store = createStore() + + fillState(store, { + videos: {}, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Some Comment' + } + } + }) + + store.$repo(Video).save({ + id: 1, + link: '/video.mp4', + comments: [ + { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + ] + }) + + assertState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('generates missing foreign key', async () => { + const store = createStore() + + store.$repo(Video).save({ + id: 1, + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('can insert a record with missing related data', async () => { + const store = createStore() + + store.$repo(Video).save({ + id: 1, + link: '/video.mp4' + }) + + assertState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: {} + }) + }) +}) diff --git a/test/feature/relations/morph_many_save_custom_key.spec.ts b/test/feature/relations/morph_many_save_custom_key.spec.ts new file mode 100644 index 0000000..5bc16fa --- /dev/null +++ b/test/feature/relations/morph_many_save_custom_key.spec.ts @@ -0,0 +1,121 @@ +import { createStore, assertState } from 'test/Helpers' +import { Model, Attr, Num, Str, MorphMany } from '@/index' + +describe('feature/relations/morph_many_save_custom_key', () => { + beforeEach(() => { + Model.clearRegistries() + }) + + it('inserts "morph many" relation with custom primary key', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + static primaryKey = 'morphableId' + + @Num(0) morphableId!: number + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + const store = createStore() + + store.$repo(Video).save({ + morphableId: 1, + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + 1: { morphableId: 1, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('inserts "morph many" relation with custom local key', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Num(0) id!: number + @Num(0) morphableId!: number + @Str('') link!: string + + @MorphMany( + () => Comment, + 'commentableId', + 'commentableType', + 'morphableId' + ) + comments!: Comment[] + } + + const store = createStore() + + store.$repo(Video).save({ + id: 1, + morphableId: 2, + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + 1: { id: 1, morphableId: 2, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 2, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 2, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) +}) diff --git a/test/feature/relations/morph_many_save_uid.spec.ts b/test/feature/relations/morph_many_save_uid.spec.ts new file mode 100644 index 0000000..d951f61 --- /dev/null +++ b/test/feature/relations/morph_many_save_uid.spec.ts @@ -0,0 +1,111 @@ +import { createStore, assertState, mockUid } from 'test/Helpers' +import { Model, Attr, Uid, Str, Num, MorphMany } from '@/index' + +describe('feature/relations/morph_many_save_uid', () => { + beforeEach(() => { + Model.clearRegistries() + }) + + it('inserts "morph many" relation with parent having "uid" field as the primary key', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: string | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Uid() id!: string + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + mockUid(['uid1']) + + const store = createStore() + + store.$repo(Video).save({ + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + uid1: { id: 'uid1', link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('inserts "morph many" relation with related having "uid" as the related key', () => { + class Comment extends Model { + static entity = 'comments' + + @Uid() id!: number + @Str('') body!: string + @Attr(null) commentableId!: string | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Uid() id!: string + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + mockUid(['uid1', 'uid2', 'uid3']) + + const store = createStore() + + store.$repo(Video).save({ + link: '/video.mp4', + comments: [{ body: 'Cool Video!' }, { body: 'Cool Video Again!' }] + }) + + assertState(store, { + videos: { + uid1: { id: 'uid1', link: '/video.mp4' } + }, + comments: { + uid2: { + id: 'uid2', + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video!' + }, + uid3: { + id: 'uid3', + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) +}) diff --git a/test/unit/model/Model_Relations.spec.ts b/test/unit/model/Model_Relations.spec.ts index 721bac8..0a16f51 100644 --- a/test/unit/model/Model_Relations.spec.ts +++ b/test/unit/model/Model_Relations.spec.ts @@ -6,7 +6,8 @@ import { BelongsTo, HasMany, HasManyBy, - MorphOne + MorphOne, + MorphMany } from '@/index' describe('unit/model/Model_Relations', () => { @@ -31,6 +32,9 @@ describe('unit/model/Model_Relations', () => { @MorphOne(() => Image, 'imageableId', 'imageableType') image!: Image | null + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] } class Phone extends Model { @@ -67,6 +71,15 @@ describe('unit/model/Model_Relations', () => { @Attr() imageableType!: string } + class Comment extends Model { + static entity = 'comments' + + @Attr() id!: number + @Attr() body!: string + @Attr() commentableId!: string | null + @Attr() commentableType!: string | null + } + it('fills "has one" relation', () => { const store = createStore() @@ -138,4 +151,21 @@ describe('unit/model/Model_Relations', () => { expect(user.image!).toBeInstanceOf(Image) expect(user.image!.id).toBe(2) }) + + it('fills "morph many" relation', () => { + const store = createStore() + + const user = store.$repo(User).make({ + id: 1, + comments: [ + { id: 2, body: 'Hey User!' }, + { id: 3, body: 'Hey User Again!' } + ] + }) + + expect(user.comments[0]).toBeInstanceOf(Comment) + expect(user.comments[1]).toBeInstanceOf(Comment) + expect(user.comments[0].id).toBe(2) + expect(user.comments[1].id).toBe(3) + }) }) From 1bba4467b1389a50c91dc9e43fec039a0b40a41a Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Wed, 24 Nov 2021 14:58:39 -0800 Subject: [PATCH 03/21] Removing TODO from tested method in MorphMany --- src/model/attributes/relations/MorphMany.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts index 0ef330c..0ef3fd8 100644 --- a/src/model/attributes/relations/MorphMany.ts +++ b/src/model/attributes/relations/MorphMany.ts @@ -38,7 +38,7 @@ export class MorphMany extends Relation { } /** - * Get all related models for the relationship. TODO + * Get all related models for the relationship. */ getRelateds(): Model[] { return [this.related] From e774cbedbd310d5db725a2e2381406084a35f87f Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 10:53:32 -0800 Subject: [PATCH 04/21] Fixing a typo --- test/feature/relations/morph_many_retrieve.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 1629ff3..009c945 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -119,7 +119,7 @@ describe('feature/relations/morph_many_retrieve', () => { }) }) - describe('when there are no images', () => { + describe('when there are no comments', () => { const store = createStore() fillState(store, { From 03160be7fdbba6f71a38060920e22521c83d6d98 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 11:02:30 -0800 Subject: [PATCH 05/21] Moving fill state into before all block --- .../relations/morph_many_retrieve.spec.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 009c945..0875758 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -72,7 +72,9 @@ describe('feature/relations/morph_many_retrieve', () => { describe('when there are comments', () => { const store = createStore() - fillState(store, ENTITIES) + beforeAll(() => { + fillState(store, ENTITIES) + }) it('can eager load morph many relation for video', () => { const video = store.$repo(Video).with('comments').first()! @@ -122,12 +124,14 @@ describe('feature/relations/morph_many_retrieve', () => { describe('when there are no comments', () => { const store = createStore() - fillState(store, { - videos: { - 1: { id: 1, link: '/video.mp4' } - }, - posts: {}, - comments: {} + beforeAll(() => { + fillState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + posts: {}, + comments: {} + }) }) it('can eager load missing relation as empty array', () => { From d6dd6c60af68995a5f456cea041f4633e14e9a43 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 11:03:40 -0800 Subject: [PATCH 06/21] Correcting a typo --- test/feature/relations/morph_many_retrieve.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 0875758..39e4450 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -101,7 +101,7 @@ describe('feature/relations/morph_many_retrieve', () => { }) }) - it('can eager load morph one relation for post', () => { + it('can eager load morph many relation for post', () => { const post = store.$repo(Post).with('comments').first()! expect(post).toBeInstanceOf(Post) From 41aa1a88ab51fd66009f173a3851c62254718b68 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 11:07:03 -0800 Subject: [PATCH 07/21] Touching up some test descriptions --- test/feature/relations/morph_many_save.spec.ts | 2 +- test/feature/relations/morph_many_save_uid.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/feature/relations/morph_many_save.spec.ts b/test/feature/relations/morph_many_save.spec.ts index 212abe3..dde2643 100644 --- a/test/feature/relations/morph_many_save.spec.ts +++ b/test/feature/relations/morph_many_save.spec.ts @@ -76,7 +76,7 @@ describe('feature/relations/morph_many_save', () => { }) }) - it('generates missing foreign key', async () => { + it('generates missing relational key', async () => { const store = createStore() store.$repo(Video).save({ diff --git a/test/feature/relations/morph_many_save_uid.spec.ts b/test/feature/relations/morph_many_save_uid.spec.ts index d951f61..70e318e 100644 --- a/test/feature/relations/morph_many_save_uid.spec.ts +++ b/test/feature/relations/morph_many_save_uid.spec.ts @@ -59,7 +59,7 @@ describe('feature/relations/morph_many_save_uid', () => { }) }) - it('inserts "morph many" relation with related having "uid" as the related key', () => { + it('inserts "morph many" relation with related having "uid" as the relational key', () => { class Comment extends Model { static entity = 'comments' From cafc529967d166a5c26da9f77f08bb15289af6ab Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Mon, 29 Nov 2021 09:52:35 -0800 Subject: [PATCH 08/21] Addressing PR comments --- src/index.cjs.ts | 4 ++++ test/feature/relations/morph_many_retrieve.spec.ts | 5 ++--- test/unit/model/Model_Relations.spec.ts | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/index.cjs.ts b/src/index.cjs.ts index 0abcf68..8f60e54 100644 --- a/src/index.cjs.ts +++ b/src/index.cjs.ts @@ -16,6 +16,7 @@ import { BelongsTo } from './model/decorators/attributes/relations/BelongsTo' import { HasMany } from './model/decorators/attributes/relations/HasMany' import { HasManyBy } from './model/decorators/attributes/relations/HasManyBy' import { MorphOne } from './model/decorators/attributes/relations/MorphOne' +import { MorphMany } from './model/decorators/attributes/relations/MorphMany' import { Attribute } from './model/attributes/Attribute' import { Type } from './model/attributes/types/Type' import { Attr as AttrAttr } from './model/attributes/types/Attr' @@ -29,6 +30,7 @@ import { BelongsTo as BelongsToAttr } from './model/attributes/relations/Belongs import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' +import { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' import { Repository } from './repository/Repository' import { Interpreter } from './interpreter/Interpreter' import { Query } from './query/Query' @@ -51,6 +53,7 @@ export default { HasMany, HasManyBy, MorphOne, + MorphMany, Attribute, Type, AttrAttr, @@ -64,6 +67,7 @@ export default { HasManyAttr, HasManyByAttr, MorphOneAttr, + MorphManyAttr, Repository, Interpreter, Query, diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 39e4450..663294e 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -176,9 +176,8 @@ describe('feature/relations/morph_many_retrieve', () => { const video = store.$repo(Video).revive(schema)! - expect(video.comments.length).toBe(2) - expect(video.comments[0]).toBeInstanceOf(Comment) - expect(video.comments[1]).toBeInstanceOf(Comment) + expect(video.comments).toHaveLength(2) + assertInstanceOf(video.comments, Comment) expect(video.comments[0].id).toBe(2) expect(video.comments[1].id).toBe(1) }) diff --git a/test/unit/model/Model_Relations.spec.ts b/test/unit/model/Model_Relations.spec.ts index 0a16f51..3c10c04 100644 --- a/test/unit/model/Model_Relations.spec.ts +++ b/test/unit/model/Model_Relations.spec.ts @@ -1,4 +1,4 @@ -import { createStore } from 'test/Helpers' +import { createStore, assertInstanceOf } from 'test/Helpers' import { Model, Attr, @@ -163,8 +163,7 @@ describe('unit/model/Model_Relations', () => { ] }) - expect(user.comments[0]).toBeInstanceOf(Comment) - expect(user.comments[1]).toBeInstanceOf(Comment) + assertInstanceOf(user.comments, Comment) expect(user.comments[0].id).toBe(2) expect(user.comments[1].id).toBe(3) }) From 7d7c50d1b10e75ce608c23669b69cf441d3b3318 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Fri, 3 Dec 2021 08:13:30 -0800 Subject: [PATCH 09/21] Refactoring MorphMany match method to use query instead of results --- src/model/attributes/relations/MorphMany.ts | 4 ++-- test/unit/model/Model_Relations.spec.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts index 0ef3fd8..882cdfe 100644 --- a/src/model/attributes/relations/MorphMany.ts +++ b/src/model/attributes/relations/MorphMany.ts @@ -70,8 +70,8 @@ export class MorphMany extends Relation { /** * Match the eagerly loaded results to their parents. */ - match(relation: string, models: Collection, results: Collection): void { - const dictionary = this.buildDictionary(results) + match(relation: string, models: Collection, query: Query): void { + const dictionary = this.buildDictionary(query.get()) models.forEach((model) => { const key = model[this.localKey] diff --git a/test/unit/model/Model_Relations.spec.ts b/test/unit/model/Model_Relations.spec.ts index 3a1a7d9..f539dcb 100644 --- a/test/unit/model/Model_Relations.spec.ts +++ b/test/unit/model/Model_Relations.spec.ts @@ -8,7 +8,6 @@ import { HasManyBy, MorphOne, MorphTo, - MorphOne, MorphMany } from '@/index' From 6eb73665be6ebc4556e5589e78a7404af807a8ea Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Tue, 23 Nov 2021 15:53:32 -0800 Subject: [PATCH 10/21] Init MorphMany --- src/index.ts | 4 + src/model/Model.ts | 17 ++ src/model/attributes/relations/MorphMany.ts | 102 +++++++++++ .../attributes/relations/MorphMany.ts | 20 +++ .../relations/morph_many_retrieve.spec.ts | 164 ++++++++++++++++++ 5 files changed, 307 insertions(+) create mode 100644 src/model/attributes/relations/MorphMany.ts create mode 100644 src/model/decorators/attributes/relations/MorphMany.ts create mode 100644 test/feature/relations/morph_many_retrieve.spec.ts diff --git a/src/index.ts b/src/index.ts index ca80412..4f2a69a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,6 +17,7 @@ export * from './model/decorators/attributes/relations/HasMany' export * from './model/decorators/attributes/relations/HasManyBy' export * from './model/decorators/attributes/relations/MorphOne' export * from './model/decorators/attributes/relations/MorphTo' +export * from './model/decorators/attributes/relations/MorphMany' export * from './model/decorators/Contracts' export * from './model/decorators/NonEnumerable' export * from './model/attributes/Attribute' @@ -33,6 +34,7 @@ export { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' export { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' export { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' export { MorphTo as MorphToAttr } from './model/attributes/relations/MorphTo' +export { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' export * from './modules/RootModule' export * from './modules/RootState' export * from './modules/Module' @@ -66,6 +68,7 @@ import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' import { MorphTo as MorphToAttr } from './model/attributes/relations/MorphTo' +import { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' import { Repository } from './repository/Repository' import { Interpreter } from './interpreter/Interpreter' import { Query } from './query/Query' @@ -92,6 +95,7 @@ export default { HasManyByAttr, MorphOneAttr, MorphToAttr, + MorphManyAttr, Repository, Interpreter, Query, diff --git a/src/model/Model.ts b/src/model/Model.ts index 87e31b2..f424cf1 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -13,6 +13,7 @@ import { HasMany } from './attributes/relations/HasMany' import { HasManyBy } from './attributes/relations/HasManyBy' import { MorphOne } from './attributes/relations/MorphOne' import { MorphTo } from './attributes/relations/MorphTo' +import { MorphMany } from './attributes/relations/MorphMany' export type ModelFields = Record export type ModelSchemas = Record @@ -263,6 +264,22 @@ export class Model { return new MorphTo(instance, relatedModels, id, type, ownerKey) } + /* + * Create a new MorphMany relation instance. + */ + static morphMany( + related: typeof Model, + id: string, + type: string, + localKey?: string + ): MorphMany { + const model = this.newRawInstance() + + localKey = localKey ?? model.$getLocalKey() + + return new MorphMany(model, related.newRawInstance(), id, type, localKey) + } + /** * Get the constructor for this model. */ diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts new file mode 100644 index 0000000..1c04beb --- /dev/null +++ b/src/model/attributes/relations/MorphMany.ts @@ -0,0 +1,102 @@ +import { Schema as NormalizrSchema } from 'normalizr' +import { Schema } from '../../../schema/Schema' +import { Element, Collection } from '../../../data/Data' +import { Query } from '../../../query/Query' +import { Model } from '../../Model' +import { Relation, Dictionary } from './Relation' + +export class MorphMany extends Relation { + /** + * The field name that contains id of the parent model. + */ + protected morphId: string + + /** + * The field name that contains type of the parent model. + */ + protected morphType: string + + /** + * The local key of the model. + */ + protected localKey: string + + /** + * Create a new morph-many relation instance. + */ + constructor( + parent: Model, + related: Model, + morphId: string, + morphType: string, + localKey: string + ) { + super(parent, related) + this.morphId = morphId + this.morphType = morphType + this.localKey = localKey + } + + /** + * Get all related models for the relationship. TODO + */ + getRelateds(): Model[] { + return [this.related] + } + + /** + * Define the normalizr schema for the relation. TODO + */ + define(schema: Schema): NormalizrSchema { + return schema.many(this.related, this.parent) + } + + /** + * Attach the parent type and id to the given relation. TODO + */ + attach(record: Element, child: Element): void { + child[this.morphId] = record[this.localKey] + child[this.morphType] = this.parent.$entity() + } + + /** + * Set the constraints for an eager load of the relation. TODO + */ + addEagerConstraints(query: Query, models: Collection): void { + query.where(this.morphType, this.parent.$entity()) + query.whereIn(this.morphId, this.getKeys(models, this.localKey)) + } + + /** + * Match the eagerly loaded results to their parents. + */ + match(relation: string, models: Collection, results: Collection): void { + const dictionary = this.buildDictionary(results) + + models.forEach((model) => { + const key = model[this.localKey] + + dictionary[key] + ? model.$setRelation(relation, dictionary[key]) + : model.$setRelation(relation, []) + }) + } + + /** + * Build model dictionary keyed by the relation's foreign key. + */ + protected buildDictionary(results: Collection): Dictionary { + return this.mapToDictionary(results, (result) => { + return [result[this.morphId], result] + }) + } + + /** + * Make related models. TODO + */ + make(elements?: Element[]): Model[] { + return elements + ? elements.map((element) => this.related.$newInstance(element)) + : [] + } +} diff --git a/src/model/decorators/attributes/relations/MorphMany.ts b/src/model/decorators/attributes/relations/MorphMany.ts new file mode 100644 index 0000000..fabf01a --- /dev/null +++ b/src/model/decorators/attributes/relations/MorphMany.ts @@ -0,0 +1,20 @@ +import { Model } from '../../../Model' +import { PropertyDecorator } from '../../Contracts' + +/** + * Create a morph-many attribute property decorator. + */ +export function MorphMany( + related: () => typeof Model, + id: string, + type: string, + localKey?: string +): PropertyDecorator { + return (target, propertyKey) => { + const self = target.$self() + + self.setRegistry(propertyKey, () => + self.morphMany(related(), id, type, localKey) + ) + } +} diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts new file mode 100644 index 0000000..0c76afe --- /dev/null +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -0,0 +1,164 @@ +import { createStore, fillState, assertModel, assertInstanceOf } from 'test/Helpers' +import { Model, Str, Num, MorphMany } from '@/index' + +describe('feature/relations/morph_many_retrieve', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Num(0) commentableId!: number + @Str('') commentableType!: string + } + + class Video extends Model { + static entity = 'videos' + + @Num(0) id!: number + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + class Post extends Model { + static entity = 'posts' + + @Num(0) id!: number + @Str('') title!: string + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + const ENTITIES = { + videos: { 1: { id: 1, link: '/video.mp4' } }, + posts: { + 1: { id: 1, title: 'Hello, world!' }, + 2: { id: 2, title: 'Hello, world! Again!' } + }, + comments: { + 1: { + id: 1, + body: 'Cool Video!', + commentableId: 1, + commentableType: 'videos' + }, + 2: { + id: 2, + body: 'Cool Video Again!', + commentableId: 1, + commentableType: 'videos' + }, + 3: { + id: 3, + body: 'Cool Post!', + commentableId: 1, + commentableType: 'posts' + }, + 4: { + id: 4, + body: 'Cool Post 2!', + commentableId: 2, + commentableType: 'posts' + } + } + } + + describe('when there are comments', () => { + const store = createStore() + + fillState(store, ENTITIES) + + it('can eager load morph many relation for video', () => { + const video = store.$repo(Video).with('comments').first()! + + expect(video).toBeInstanceOf(Video) + assertInstanceOf(video.comments, Comment) + assertModel(video, { + id: 1, + link: '/video.mp4', + comments: [ + { + id: 1, + body: 'Cool Video!', + commentableId: 1, + commentableType: 'videos' + }, + { + id: 2, + body: 'Cool Video Again!', + commentableId: 1, + commentableType: 'videos' + } + ] + }) + }) + + it('can eager load morph one relation for post', () => { + const post = store.$repo(Post).with('comments').first()! + + expect(post).toBeInstanceOf(Post) + assertInstanceOf(post.comments, Comment) + assertModel(post, { + id: 1, + title: 'Hello, world!', + comments: [{ + id: 3, + body: 'Cool Post!', + commentableId: 1, + commentableType: 'posts' + }] + }) + }) + }) + + describe('when there are no images', () => { + const store = createStore() + + fillState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + posts: {}, + comments: {} + }) + + it('can eager load missing relation as empty array', () => { + const video = store.$repo(Video).with('comments').first()! + + expect(video).toBeInstanceOf(Video) + assertModel(video, { + id: 1, + link: '/video.mp4', + comments: [] + }) + }) + }) + + it('can revive "has many" relations', () => { + const store = createStore() + + fillState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: { + 1: { id: 1, commentableId: 1, commentableType: 'videos', body: 'Cool Video!' }, + 2: { id: 2, commentableId: 1, commentableType: 'videos', body: 'Cool Video Again!' } + } + }) + + const schema = { + id: '1', + comments: [{ id: 2 }, { id: 1 }] + } + + const video = store.$repo(Video).revive(schema)! + + expect(video.comments.length).toBe(2) + expect(video.comments[0]).toBeInstanceOf(Comment) + expect(video.comments[1]).toBeInstanceOf(Comment) + expect(video.comments[0].id).toBe(2) + expect(video.comments[1].id).toBe(1) + }) +}) From f3b449db8708b9408b0e744735866ae5b4bbfef1 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Wed, 24 Nov 2021 14:57:30 -0800 Subject: [PATCH 11/21] Adding MorphMany tests, removing TODOs from tested MorphMany methods. --- src/model/attributes/relations/MorphMany.ts | 8 +- .../relations/morph_many_retrieve.spec.ts | 43 ++++-- .../feature/relations/morph_many_save.spec.ts | 127 ++++++++++++++++++ .../morph_many_save_custom_key.spec.ts | 121 +++++++++++++++++ .../relations/morph_many_save_uid.spec.ts | 111 +++++++++++++++ test/unit/model/Model_Relations.spec.ts | 32 ++++- 6 files changed, 424 insertions(+), 18 deletions(-) create mode 100644 test/feature/relations/morph_many_save.spec.ts create mode 100644 test/feature/relations/morph_many_save_custom_key.spec.ts create mode 100644 test/feature/relations/morph_many_save_uid.spec.ts diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts index 1c04beb..0ef330c 100644 --- a/src/model/attributes/relations/MorphMany.ts +++ b/src/model/attributes/relations/MorphMany.ts @@ -45,14 +45,14 @@ export class MorphMany extends Relation { } /** - * Define the normalizr schema for the relation. TODO + * Define the normalizr schema for the relation. */ define(schema: Schema): NormalizrSchema { return schema.many(this.related, this.parent) } /** - * Attach the parent type and id to the given relation. TODO + * Attach the parent type and id to the given relation. */ attach(record: Element, child: Element): void { child[this.morphId] = record[this.localKey] @@ -60,7 +60,7 @@ export class MorphMany extends Relation { } /** - * Set the constraints for an eager load of the relation. TODO + * Set the constraints for an eager load of the relation. */ addEagerConstraints(query: Query, models: Collection): void { query.where(this.morphType, this.parent.$entity()) @@ -92,7 +92,7 @@ export class MorphMany extends Relation { } /** - * Make related models. TODO + * Make related models. */ make(elements?: Element[]): Model[] { return elements diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 0c76afe..1629ff3 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -1,5 +1,10 @@ -import { createStore, fillState, assertModel, assertInstanceOf } from 'test/Helpers' -import { Model, Str, Num, MorphMany } from '@/index' +import { + createStore, + fillState, + assertModel, + assertInstanceOf +} from 'test/Helpers' +import { Model, Attr, Str, Num, MorphMany } from '@/index' describe('feature/relations/morph_many_retrieve', () => { class Comment extends Model { @@ -7,8 +12,8 @@ describe('feature/relations/morph_many_retrieve', () => { @Num(0) id!: number @Str('') body!: string - @Num(0) commentableId!: number - @Str('') commentableType!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null } class Video extends Model { @@ -102,12 +107,14 @@ describe('feature/relations/morph_many_retrieve', () => { assertModel(post, { id: 1, title: 'Hello, world!', - comments: [{ - id: 3, - body: 'Cool Post!', - commentableId: 1, - commentableType: 'posts' - }] + comments: [ + { + id: 3, + body: 'Cool Post!', + commentableId: 1, + commentableType: 'posts' + } + ] }) }) }) @@ -135,7 +142,7 @@ describe('feature/relations/morph_many_retrieve', () => { }) }) - it('can revive "has many" relations', () => { + it('can revive "morph many" relations', () => { const store = createStore() fillState(store, { @@ -143,8 +150,18 @@ describe('feature/relations/morph_many_retrieve', () => { 1: { id: 1, link: '/video.mp4' } }, comments: { - 1: { id: 1, commentableId: 1, commentableType: 'videos', body: 'Cool Video!' }, - 2: { id: 2, commentableId: 1, commentableType: 'videos', body: 'Cool Video Again!' } + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } } }) diff --git a/test/feature/relations/morph_many_save.spec.ts b/test/feature/relations/morph_many_save.spec.ts new file mode 100644 index 0000000..212abe3 --- /dev/null +++ b/test/feature/relations/morph_many_save.spec.ts @@ -0,0 +1,127 @@ +import { createStore, fillState, assertState } from 'test/Helpers' +import { Model, Attr, Num, Str, MorphMany } from '@/index' + +describe('feature/relations/morph_many_save', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Num(0) id!: number + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + it('saves a model to the store with "morph many" relation', () => { + const store = createStore() + + fillState(store, { + videos: {}, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Some Comment' + } + } + }) + + store.$repo(Video).save({ + id: 1, + link: '/video.mp4', + comments: [ + { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + ] + }) + + assertState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('generates missing foreign key', async () => { + const store = createStore() + + store.$repo(Video).save({ + id: 1, + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('can insert a record with missing related data', async () => { + const store = createStore() + + store.$repo(Video).save({ + id: 1, + link: '/video.mp4' + }) + + assertState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + comments: {} + }) + }) +}) diff --git a/test/feature/relations/morph_many_save_custom_key.spec.ts b/test/feature/relations/morph_many_save_custom_key.spec.ts new file mode 100644 index 0000000..5bc16fa --- /dev/null +++ b/test/feature/relations/morph_many_save_custom_key.spec.ts @@ -0,0 +1,121 @@ +import { createStore, assertState } from 'test/Helpers' +import { Model, Attr, Num, Str, MorphMany } from '@/index' + +describe('feature/relations/morph_many_save_custom_key', () => { + beforeEach(() => { + Model.clearRegistries() + }) + + it('inserts "morph many" relation with custom primary key', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + static primaryKey = 'morphableId' + + @Num(0) morphableId!: number + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + const store = createStore() + + store.$repo(Video).save({ + morphableId: 1, + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + 1: { morphableId: 1, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 1, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('inserts "morph many" relation with custom local key', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: number | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Num(0) id!: number + @Num(0) morphableId!: number + @Str('') link!: string + + @MorphMany( + () => Comment, + 'commentableId', + 'commentableType', + 'morphableId' + ) + comments!: Comment[] + } + + const store = createStore() + + store.$repo(Video).save({ + id: 1, + morphableId: 2, + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + 1: { id: 1, morphableId: 2, link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 2, + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 2, + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) +}) diff --git a/test/feature/relations/morph_many_save_uid.spec.ts b/test/feature/relations/morph_many_save_uid.spec.ts new file mode 100644 index 0000000..d951f61 --- /dev/null +++ b/test/feature/relations/morph_many_save_uid.spec.ts @@ -0,0 +1,111 @@ +import { createStore, assertState, mockUid } from 'test/Helpers' +import { Model, Attr, Uid, Str, Num, MorphMany } from '@/index' + +describe('feature/relations/morph_many_save_uid', () => { + beforeEach(() => { + Model.clearRegistries() + }) + + it('inserts "morph many" relation with parent having "uid" field as the primary key', () => { + class Comment extends Model { + static entity = 'comments' + + @Num(0) id!: number + @Str('') body!: string + @Attr(null) commentableId!: string | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Uid() id!: string + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + mockUid(['uid1']) + + const store = createStore() + + store.$repo(Video).save({ + link: '/video.mp4', + comments: [ + { id: 1, body: 'Cool Video!' }, + { id: 2, body: 'Cool Video Again!' } + ] + }) + + assertState(store, { + videos: { + uid1: { id: 'uid1', link: '/video.mp4' } + }, + comments: { + 1: { + id: 1, + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video!' + }, + 2: { + id: 2, + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) + + it('inserts "morph many" relation with related having "uid" as the related key', () => { + class Comment extends Model { + static entity = 'comments' + + @Uid() id!: number + @Str('') body!: string + @Attr(null) commentableId!: string | null + @Attr(null) commentableType!: string | null + } + + class Video extends Model { + static entity = 'videos' + + @Uid() id!: string + @Str('') link!: string + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] + } + + mockUid(['uid1', 'uid2', 'uid3']) + + const store = createStore() + + store.$repo(Video).save({ + link: '/video.mp4', + comments: [{ body: 'Cool Video!' }, { body: 'Cool Video Again!' }] + }) + + assertState(store, { + videos: { + uid1: { id: 'uid1', link: '/video.mp4' } + }, + comments: { + uid2: { + id: 'uid2', + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video!' + }, + uid3: { + id: 'uid3', + commentableId: 'uid1', + commentableType: 'videos', + body: 'Cool Video Again!' + } + } + }) + }) +}) diff --git a/test/unit/model/Model_Relations.spec.ts b/test/unit/model/Model_Relations.spec.ts index f10bceb..2498ff7 100644 --- a/test/unit/model/Model_Relations.spec.ts +++ b/test/unit/model/Model_Relations.spec.ts @@ -6,8 +6,9 @@ import { BelongsTo, HasMany, HasManyBy, + MorphOne, MorphTo, - MorphOne + MorphMany } from '@/index' describe('unit/model/Model_Relations', () => { @@ -32,6 +33,9 @@ describe('unit/model/Model_Relations', () => { @MorphOne(() => Image, 'imageableId', 'imageableType') image!: Image | null + + @MorphMany(() => Comment, 'commentableId', 'commentableType') + comments!: Comment[] } class Phone extends Model { @@ -71,6 +75,15 @@ describe('unit/model/Model_Relations', () => { imageable!: User | null } + class Comment extends Model { + static entity = 'comments' + + @Attr() id!: number + @Attr() body!: string + @Attr() commentableId!: string | null + @Attr() commentableType!: string | null + } + it('fills "has one" relation', () => { const store = createStore() @@ -158,4 +171,21 @@ describe('unit/model/Model_Relations', () => { expect(image.imageable!).toBeInstanceOf(User) expect(image.imageable!.id).toBe(2) }) + + it('fills "morph many" relation', () => { + const store = createStore() + + const user = store.$repo(User).make({ + id: 1, + comments: [ + { id: 2, body: 'Hey User!' }, + { id: 3, body: 'Hey User Again!' } + ] + }) + + expect(user.comments[0]).toBeInstanceOf(Comment) + expect(user.comments[1]).toBeInstanceOf(Comment) + expect(user.comments[0].id).toBe(2) + expect(user.comments[1].id).toBe(3) + }) }) From f13c298e838f272bd693c41b148538b218e5b402 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Wed, 24 Nov 2021 14:58:39 -0800 Subject: [PATCH 12/21] Removing TODO from tested method in MorphMany --- src/model/attributes/relations/MorphMany.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts index 0ef330c..0ef3fd8 100644 --- a/src/model/attributes/relations/MorphMany.ts +++ b/src/model/attributes/relations/MorphMany.ts @@ -38,7 +38,7 @@ export class MorphMany extends Relation { } /** - * Get all related models for the relationship. TODO + * Get all related models for the relationship. */ getRelateds(): Model[] { return [this.related] From 3defb889dc44c41129ee9f442711f136a44c3092 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 10:53:32 -0800 Subject: [PATCH 13/21] Fixing a typo --- test/feature/relations/morph_many_retrieve.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 1629ff3..009c945 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -119,7 +119,7 @@ describe('feature/relations/morph_many_retrieve', () => { }) }) - describe('when there are no images', () => { + describe('when there are no comments', () => { const store = createStore() fillState(store, { From b83e961de893777acbcbe0e77639a93cc61d7329 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 11:02:30 -0800 Subject: [PATCH 14/21] Moving fill state into before all block --- .../relations/morph_many_retrieve.spec.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 009c945..0875758 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -72,7 +72,9 @@ describe('feature/relations/morph_many_retrieve', () => { describe('when there are comments', () => { const store = createStore() - fillState(store, ENTITIES) + beforeAll(() => { + fillState(store, ENTITIES) + }) it('can eager load morph many relation for video', () => { const video = store.$repo(Video).with('comments').first()! @@ -122,12 +124,14 @@ describe('feature/relations/morph_many_retrieve', () => { describe('when there are no comments', () => { const store = createStore() - fillState(store, { - videos: { - 1: { id: 1, link: '/video.mp4' } - }, - posts: {}, - comments: {} + beforeAll(() => { + fillState(store, { + videos: { + 1: { id: 1, link: '/video.mp4' } + }, + posts: {}, + comments: {} + }) }) it('can eager load missing relation as empty array', () => { From de213c4b2e9773a0ab698b5461cde0cfc5d563dd Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 11:03:40 -0800 Subject: [PATCH 15/21] Correcting a typo --- test/feature/relations/morph_many_retrieve.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 0875758..39e4450 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -101,7 +101,7 @@ describe('feature/relations/morph_many_retrieve', () => { }) }) - it('can eager load morph one relation for post', () => { + it('can eager load morph many relation for post', () => { const post = store.$repo(Post).with('comments').first()! expect(post).toBeInstanceOf(Post) From ae8453ec42b75ed456ed49ac2f25411224a142c2 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Thu, 25 Nov 2021 11:07:03 -0800 Subject: [PATCH 16/21] Touching up some test descriptions --- test/feature/relations/morph_many_save.spec.ts | 2 +- test/feature/relations/morph_many_save_uid.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/feature/relations/morph_many_save.spec.ts b/test/feature/relations/morph_many_save.spec.ts index 212abe3..dde2643 100644 --- a/test/feature/relations/morph_many_save.spec.ts +++ b/test/feature/relations/morph_many_save.spec.ts @@ -76,7 +76,7 @@ describe('feature/relations/morph_many_save', () => { }) }) - it('generates missing foreign key', async () => { + it('generates missing relational key', async () => { const store = createStore() store.$repo(Video).save({ diff --git a/test/feature/relations/morph_many_save_uid.spec.ts b/test/feature/relations/morph_many_save_uid.spec.ts index d951f61..70e318e 100644 --- a/test/feature/relations/morph_many_save_uid.spec.ts +++ b/test/feature/relations/morph_many_save_uid.spec.ts @@ -59,7 +59,7 @@ describe('feature/relations/morph_many_save_uid', () => { }) }) - it('inserts "morph many" relation with related having "uid" as the related key', () => { + it('inserts "morph many" relation with related having "uid" as the relational key', () => { class Comment extends Model { static entity = 'comments' From ea25ea6bc41d495c9aabd4c8438c8cedf7008fd1 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Mon, 29 Nov 2021 09:52:35 -0800 Subject: [PATCH 17/21] Addressing PR comments --- src/index.cjs.ts | 4 ++++ test/feature/relations/morph_many_retrieve.spec.ts | 5 ++--- test/unit/model/Model_Relations.spec.ts | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/index.cjs.ts b/src/index.cjs.ts index 0abcf68..8f60e54 100644 --- a/src/index.cjs.ts +++ b/src/index.cjs.ts @@ -16,6 +16,7 @@ import { BelongsTo } from './model/decorators/attributes/relations/BelongsTo' import { HasMany } from './model/decorators/attributes/relations/HasMany' import { HasManyBy } from './model/decorators/attributes/relations/HasManyBy' import { MorphOne } from './model/decorators/attributes/relations/MorphOne' +import { MorphMany } from './model/decorators/attributes/relations/MorphMany' import { Attribute } from './model/attributes/Attribute' import { Type } from './model/attributes/types/Type' import { Attr as AttrAttr } from './model/attributes/types/Attr' @@ -29,6 +30,7 @@ import { BelongsTo as BelongsToAttr } from './model/attributes/relations/Belongs import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' +import { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' import { Repository } from './repository/Repository' import { Interpreter } from './interpreter/Interpreter' import { Query } from './query/Query' @@ -51,6 +53,7 @@ export default { HasMany, HasManyBy, MorphOne, + MorphMany, Attribute, Type, AttrAttr, @@ -64,6 +67,7 @@ export default { HasManyAttr, HasManyByAttr, MorphOneAttr, + MorphManyAttr, Repository, Interpreter, Query, diff --git a/test/feature/relations/morph_many_retrieve.spec.ts b/test/feature/relations/morph_many_retrieve.spec.ts index 39e4450..663294e 100644 --- a/test/feature/relations/morph_many_retrieve.spec.ts +++ b/test/feature/relations/morph_many_retrieve.spec.ts @@ -176,9 +176,8 @@ describe('feature/relations/morph_many_retrieve', () => { const video = store.$repo(Video).revive(schema)! - expect(video.comments.length).toBe(2) - expect(video.comments[0]).toBeInstanceOf(Comment) - expect(video.comments[1]).toBeInstanceOf(Comment) + expect(video.comments).toHaveLength(2) + assertInstanceOf(video.comments, Comment) expect(video.comments[0].id).toBe(2) expect(video.comments[1].id).toBe(1) }) diff --git a/test/unit/model/Model_Relations.spec.ts b/test/unit/model/Model_Relations.spec.ts index 2498ff7..f539dcb 100644 --- a/test/unit/model/Model_Relations.spec.ts +++ b/test/unit/model/Model_Relations.spec.ts @@ -1,4 +1,4 @@ -import { createStore } from 'test/Helpers' +import { createStore, assertInstanceOf } from 'test/Helpers' import { Model, Attr, @@ -183,8 +183,7 @@ describe('unit/model/Model_Relations', () => { ] }) - expect(user.comments[0]).toBeInstanceOf(Comment) - expect(user.comments[1]).toBeInstanceOf(Comment) + assertInstanceOf(user.comments, Comment) expect(user.comments[0].id).toBe(2) expect(user.comments[1].id).toBe(3) }) From 8afbfd8e09f283ba76d3f1dba0b986d11e68e588 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Fri, 3 Dec 2021 08:13:30 -0800 Subject: [PATCH 18/21] Refactoring MorphMany match method to use query instead of results --- src/model/attributes/relations/MorphMany.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/attributes/relations/MorphMany.ts b/src/model/attributes/relations/MorphMany.ts index 0ef3fd8..882cdfe 100644 --- a/src/model/attributes/relations/MorphMany.ts +++ b/src/model/attributes/relations/MorphMany.ts @@ -70,8 +70,8 @@ export class MorphMany extends Relation { /** * Match the eagerly loaded results to their parents. */ - match(relation: string, models: Collection, results: Collection): void { - const dictionary = this.buildDictionary(results) + match(relation: string, models: Collection, query: Query): void { + const dictionary = this.buildDictionary(query.get()) models.forEach((model) => { const key = model[this.localKey] From ad6571001323af2d08173c350e96d1e8afdf5dba Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Fri, 3 Dec 2021 08:21:02 -0800 Subject: [PATCH 19/21] Resolving some duplicate code after resolving conflicts --- src/index.ts | 3 --- src/model/Model.ts | 17 ----------------- 2 files changed, 20 deletions(-) diff --git a/src/index.ts b/src/index.ts index 1fea0f0..d545c66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,6 @@ export { BelongsTo as BelongsToAttr } from './model/attributes/relations/Belongs export { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' export { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' export { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' -export { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' export { MorphTo as MorphToAttr } from './model/attributes/relations/MorphTo' export { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' export * from './modules/RootModule' @@ -69,7 +68,6 @@ import { BelongsTo as BelongsToAttr } from './model/attributes/relations/Belongs import { HasMany as HasManyAttr } from './model/attributes/relations/HasMany' import { HasManyBy as HasManyByAttr } from './model/attributes/relations/HasManyBy' import { MorphOne as MorphOneAttr } from './model/attributes/relations/MorphOne' -import { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' import { MorphTo as MorphToAttr } from './model/attributes/relations/MorphTo' import { MorphMany as MorphManyAttr } from './model/attributes/relations/MorphMany' import { Repository } from './repository/Repository' @@ -97,7 +95,6 @@ export default { HasManyAttr, HasManyByAttr, MorphOneAttr, - MorphManyAttr, MorphToAttr, MorphManyAttr, Repository, diff --git a/src/model/Model.ts b/src/model/Model.ts index b31ac81..a97d52f 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -12,7 +12,6 @@ import { BelongsTo } from './attributes/relations/BelongsTo' import { HasMany } from './attributes/relations/HasMany' import { HasManyBy } from './attributes/relations/HasManyBy' import { MorphOne } from './attributes/relations/MorphOne' -import { MorphMany } from './attributes/relations/MorphMany' import { MorphTo } from './attributes/relations/MorphTo' import { MorphMany } from './attributes/relations/MorphMany' @@ -250,22 +249,6 @@ export class Model { return new MorphOne(model, related.newRawInstance(), id, type, localKey) } - /** - * Create a new MorphMany relation instance. - */ - static morphMany( - related: typeof Model, - id: string, - type: string, - localKey?: string - ): MorphMany { - const model = this.newRawInstance() - - localKey = localKey ?? model.$getLocalKey() - - return new MorphMany(model, related.newRawInstance(), id, type, localKey) - } - /* * Create a new MorphTo relation instance. */ From 7e5f0fe266dab6bf95baa8ca8a3e2cf1a0087e91 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Fri, 3 Dec 2021 08:29:21 -0800 Subject: [PATCH 20/21] Removing another duplicated import --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index d545c66..4f2a69a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,6 @@ export * from './model/decorators/attributes/relations/BelongsTo' export * from './model/decorators/attributes/relations/HasMany' export * from './model/decorators/attributes/relations/HasManyBy' export * from './model/decorators/attributes/relations/MorphOne' -export * from './model/decorators/attributes/relations/MorphMany' export * from './model/decorators/attributes/relations/MorphTo' export * from './model/decorators/attributes/relations/MorphMany' export * from './model/decorators/Contracts' From f16c6218f26048f632622ac18d75324f98bed339 Mon Sep 17 00:00:00 2001 From: ryandialpad Date: Fri, 3 Dec 2021 08:30:39 -0800 Subject: [PATCH 21/21] Fixing up some comment headers --- src/model/Model.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/Model.ts b/src/model/Model.ts index a97d52f..1c87db9 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -249,7 +249,7 @@ export class Model { return new MorphOne(model, related.newRawInstance(), id, type, localKey) } - /* + /** * Create a new MorphTo relation instance. */ static morphTo( @@ -264,7 +264,7 @@ export class Model { return new MorphTo(instance, relatedModels, id, type, ownerKey) } - /* + /** * Create a new MorphMany relation instance. */ static morphMany(