Skip to content

Commit

Permalink
Implement model save and delete methods.
Browse files Browse the repository at this point in the history
Model now has a reference to the collection. Collection can be
retrieved via `model.getCollection()`
  • Loading branch information
ivandotv committed Jul 1, 2022
1 parent 7d7d39b commit 9885620
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 7 deletions.
8 changes: 8 additions & 0 deletions .changeset/shiny-cooks-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@fuerte/core': minor
---

Implement model save and delete methods.

Model now has a reference to the collection. Collection can be
retrieved via `model.getCollection()`
2 changes: 2 additions & 0 deletions packages/core/src/__tests__/__fixtures__/TestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ export class TestModel extends Model<TestCollection> {
super.onDestroy()
}
}

const model = new TestModel()
4 changes: 2 additions & 2 deletions packages/core/src/__tests__/__fixtures__/TestTransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export class TestTransport implements Transport<TestModel> {
})
}

save(_model: TestModel, config: any) {
save(_model: TestModel, config?: any) {
return Promise.resolve({ data: { id: nanoid() } })
}

delete(_model: TestModel, config: any): Promise<{ data: any }> {
delete(_model: TestModel, config: any) {
return Promise.resolve({ data: undefined })
}

Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/__tests__/model-save.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ describe('Model - save #save #model', () => {
test('When the model properties change, model payload property is updated', () => {
const newFooValue = 'new foo'
const newBarValue = 'new bar'
const model = fixtures.model()
const id = '123'
const model = fixtures.model({ id })

model.foo = newFooValue
model.bar = newBarValue

expect(model.payload).toStrictEqual({
foo: newFooValue,
bar: newBarValue,
id: ''
id
})
})
})
Expand Down
56 changes: 56 additions & 0 deletions packages/core/src/__tests__/model.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,60 @@ describe('Model #model', () => {
expect(model.isDirty).toBe(false)
})
})

test('calling model.save proxies the call to collection.save', async () => {
const transport = fixtures.transport()
const transportConfg = 'config'
const collection = fixtures.collection(fixtures.factory(), transport)
const model = fixtures.model()
const transportSaveSpy = jest.spyOn(transport, 'save')
collection.add(model)

await model.save(transportConfg)

expect(transportSaveSpy).toHaveBeenCalledTimes(1)
expect(transportSaveSpy).toHaveBeenCalledWith(model, transportConfg)
expect(collection.getById(model.identity)).toBe(model)
})

test('calling model.save will throw if the model is not a part of the collection', async () => {
const model = fixtures.model()

await expect(model.save(undefined)).rejects.toThrow(
'not part of the collection'
)
})

test('calling model.delete proxies the call to collection.delete', async () => {
const transport = fixtures.transport()
const transportConfg = 'config'
const collection = fixtures.collection(fixtures.factory(), transport)
const model = fixtures.model()
const transportDeleteSpy = jest.spyOn(transport, 'delete')

await collection.save(model)

await model.delete(transportConfg)

expect(transportDeleteSpy).toHaveBeenCalledTimes(1)
expect(transportDeleteSpy).toHaveBeenCalledWith(model, transportConfg)
expect(collection.models).toHaveLength(0)
})

test('calling model.delete will throw if the model is not a part of the collection', async () => {
const model = fixtures.model()

await expect(model.delete(undefined)).rejects.toThrow(
'not part of the collection'
)
})

test('retrieve the collection from the model', () => {
const collection = fixtures.collection()
const model = fixtures.model()

collection.add(model)

expect(model.getCollection()).toBe(collection)
})
})
40 changes: 37 additions & 3 deletions packages/core/src/model/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { nanoid } from 'nanoid/non-secure'
import { Collection } from '../collection/Collection'
import {
DeleteConfig,
DeleteResult,
ExtractTransport,
ModelDeleteErrorCallback,
ModelDeleteStartCallback,
Expand All @@ -19,13 +20,13 @@ import {
ModelSaveStartCallback,
ModelSaveSuccessCallback,
ModelTransportErrors,
Payload,
SaveConfig,
SaveResult,
Transport
} from '../types'

// @ts-expect-error: using return type on a protected method
type Payload<T extends Model> = ReturnType<T['serialize']>
const collectionError = 'Model is not part of the collection'

export abstract class Model<
TCollection extends Collection<any, any, any> = Collection<any, any, any>
Expand Down Expand Up @@ -136,7 +137,7 @@ export abstract class Model<
})
}

protected abstract serialize(): any
abstract serialize(): any

//TODO - add collection
// @internal
Expand Down Expand Up @@ -270,6 +271,25 @@ export abstract class Model<
this.onSaveStart({ config, transportConfig })
}

/**
* Get the collection the model is part of
*/
getCollection(): TCollection | undefined {
return this.collection
}

/**
* Save the model by calling {@link Collection.save}. If the model is not part of the collection, it will throw
* @param config - transport configuration from the collection transport.
*/
async save<TConfig = Parameters<ExtractTransport<TCollection>['save']>[1]>(
config: TConfig
): Promise<SaveResult<this, ExtractTransport<TCollection>>> {
if (!this.collection) throw new Error(collectionError)

return await this.collection!.save(this, undefined, config)
}

protected onSaveStart(data: ModelSaveStartCallback): void {}

// @internal
Expand Down Expand Up @@ -375,6 +395,20 @@ export abstract class Model<
this._isNew = isNew
}

/**
* Delete the model by calling {@link Collection.delete}. If the model is not part of the collection, it will throw
* @param config - transport configuration from the collection transport.
*/
async delete<
TConfig = Parameters<ExtractTransport<TCollection>['delete']>[1]
>(
config: TConfig
): Promise<DeleteResult<this, ExtractTransport<TCollection>>> {
if (!this.collection) throw new Error(collectionError)

return await this.collection!.delete(this.cid, undefined, config)
}

// @internal
_onDeleteStart(data: { config: DeleteConfig; transportConfig: any }): void {
this._isDeleting = true
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Collection } from './collection/Collection'
import {
DuplicateModelStrategy,
ModelCompareResult
Expand Down Expand Up @@ -267,3 +268,9 @@ export type AutosaveCollectionConfig = CollectionConfig & {
export type RequiredAutosaveCollectionConfig = RequiredCollectionConfig & {
autoSave: Required<AutoSaveConfig>
}

export type ExtractTransport<P> = P extends Collection<any, any, infer T>
? T
: never

export type Payload<T extends Model> = ReturnType<T['serialize']>

0 comments on commit 9885620

Please sign in to comment.