Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/content/1.guide/2.model/5.lifecycle-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class User extends Model {

Mutation lifecycle hooks are called when you mutate data in the store. The corresponding hook name are `creating`, `updating`, `saving`, `deleting`, `created`, `updated`, `deleted` and `saved`.

The `save`, `update`, `new`, `destroy`, `delete` and `fresh` methods fire the matching hooks. `fresh` fires `saving`/`creating` and `saved`/`created` for every record — pass `{ raw: true }` as second argument to persist the data without firing any hooks. Note that `insert` and `flush` do not fire any mutation hooks.

When in `creating`, `updating` or `saving`, you can modify the record directly to mutate the data to be saved. You also have access
to the data passed to the model. (Also available in `created`, `updated` and `saved`)

Expand Down
6 changes: 6 additions & 0 deletions docs/content/1.guide/4.repository/3.inserting-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ useRepo(User).fresh({ id: 3, name: 'Johnny Doe' })
}
```

The `fresh` method fires the `saving`/`creating` and `saved`/`created` lifecycle hooks for every record. If you want to persist raw data without triggering any hooks — for example when initializing the store with backend data — pass `{ raw: true }` as second argument.

```js
useRepo(User).fresh({ id: 3, name: 'Johnny Doe' }, { raw: true })
```

And of course, you may pass an array of records as well.

```js
Expand Down
32 changes: 28 additions & 4 deletions packages/pinia-orm/src/query/Query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -880,14 +880,38 @@ export class Query<M extends Model = Model> {

/**
* Insert the given records to the store by replacing any existing records.
* The `saving`/`creating` and `saved`/`created` lifecycle hooks are fired
* for every record unless `raw` is set to `true`. Records for which a
* before hook returns `false` are not persisted.
*/
fresh (records: Element[]): Collection<M>
fresh (record: Element): M
fresh (records: Element | Element[]): M | Collection<M> {
fresh (records: Element[], options?: { raw?: boolean }): Collection<M>
fresh (record: Element, options?: { raw?: boolean }): M
fresh (records: Element | Element[], options: { raw?: boolean } = {}): M | Collection<M> {
this.hydratedDataCache.clear()
const models = this.hydrate(records, { action: 'update' })
const modelArray = isArray(models) ? models : [models]
const recordArray = isArray(records) ? records : [records]

let persistableModels = modelArray
const afterHooks: (() => void)[] = []

if (!options.raw) {
persistableModels = modelArray.filter((model, index) => {
const record = recordArray[index]
const isSaving = model.$self().saving(model, record)
const isCreating = model.$self().creating(model, record)
if (isSaving === false || isCreating === false) { return false }

afterHooks.push(() => {
model.$self().saved(model, record)
model.$self().created(model, record)
})
return true
})
}

this.commit('fresh', this.compile(models))
this.commit('fresh', this.compile(persistableModels))
afterHooks.forEach(hook => hook())

return models
}
Expand Down
8 changes: 4 additions & 4 deletions packages/pinia-orm/src/repository/Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,10 @@ export class Repository<M extends Model = Model> {
/**
* Insert the given records to the store by replacing any existing records.
*/
fresh (records: Element[]): Collection<M>
fresh (record: Element): M
fresh (records: Element | Element[]): M | Collection<M> {
return this.query().fresh(records)
fresh (records: Element[], options?: { raw?: boolean }): Collection<M>
fresh (record: Element, options?: { raw?: boolean }): M
fresh (records: Element | Element[], options?: { raw?: boolean }): M | Collection<M> {
return this.query().fresh(records, options)
}

/**
Expand Down
114 changes: 114 additions & 0 deletions packages/pinia-orm/tests/feature/hooks/fresh.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, it, vi } from 'vitest'

import { Model, useRepo } from '../../../src'
import { Attr, Str } from '../../../src/decorators'
import { assertState } from '../../helpers'

describe('feature/hooks/fresh', () => {
it('fires the saving/creating and saved/created hooks', () => {
class User extends Model {
static entity = 'users'

@Attr() id!: any
@Str('') name!: string
}

const savingMethod = vi.spyOn(User, 'saving')
const creatingMethod = vi.spyOn(User, 'creating')
const savedMethod = vi.spyOn(User, 'saved')
const createdMethod = vi.spyOn(User, 'created')

useRepo(User).fresh([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
])

expect(savingMethod).toHaveBeenCalledTimes(2)
expect(creatingMethod).toHaveBeenCalledTimes(2)
expect(savedMethod).toHaveBeenCalledTimes(2)
expect(createdMethod).toHaveBeenCalledTimes(2)

assertState({
users: {
1: { id: 1, name: 'John Doe' },
2: { id: 2, name: 'Jane Doe' },
},
})
})

it('is able to change values in the saving hook', () => {
class User extends Model {
static entity = 'users'

@Attr() id!: any
@Str('') name!: string

static saving (model: Model) {
model.name = 'Modified'
}
}

useRepo(User).fresh({ id: 1, name: 'John Doe' })

assertState({
users: {
1: { id: 1, name: 'Modified' },
},
})
})

it('does not persist records for which a before hook returns false', () => {
class User extends Model {
static entity = 'users'

@Attr() id!: any
@Str('') name!: string

static creating (model: Model) {
return model.id !== 2
}
}

const createdMethod = vi.spyOn(User, 'created')

useRepo(User).fresh([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Doe' },
])

expect(createdMethod).toHaveBeenCalledTimes(1)

assertState({
users: {
1: { id: 1, name: 'John Doe' },
},
})
})

it('fires no hooks when the raw option is set', () => {
class User extends Model {
static entity = 'users'

@Attr() id!: any
@Str('') name!: string
}

const savingMethod = vi.spyOn(User, 'saving')
const creatingMethod = vi.spyOn(User, 'creating')
const savedMethod = vi.spyOn(User, 'saved')
const createdMethod = vi.spyOn(User, 'created')

useRepo(User).fresh([{ id: 1, name: 'John Doe' }], { raw: true })

expect(savingMethod).not.toHaveBeenCalled()
expect(creatingMethod).not.toHaveBeenCalled()
expect(savedMethod).not.toHaveBeenCalled()
expect(createdMethod).not.toHaveBeenCalled()

assertState({
users: {
1: { id: 1, name: 'John Doe' },
},
})
})
})
Loading