Skip to content

Commit

Permalink
feat(pinia-orm): add mutators (#84)
Browse files Browse the repository at this point in the history
* feat(pinia-orm): add mutators

* refactor(pinia-orm): linting

* test(pinia-orm): fix mutator test

* test(pinia-orm): update test name correctly

* feat(pinia-orm): add 'mutate' decorator

* refactor(pinia-orm): remove console.log

* docs(pinia-orm): adding mutators

* docs(pinia-orm): fix broken links

* docs(pinia-orm): fix two links

* docs(pinia-orm): missing comma
  • Loading branch information
CodeDredd authored Jul 13, 2022
1 parent 686a9cf commit 5757f97
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 27 deletions.
75 changes: 74 additions & 1 deletion docs/content/2.model/2.accessors.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
---
title: Accessors
title: Accessors & Mutators
description: ''
---

## Defining Accessors

Model accessors are computed properties which have access to the model instance and all it's properties and methods. You can define accessors on models to create virtual properties using methods and ES6 getters. Accessors are exempt from persistence and are hidden from model serialization.

The following example defines an accessor on a User model as `fullName`, using ES6 getters, which combines the `firstName` and `lastName` attributes from the model schema, and also a prefix method to allow customizing the `firstName`.
Expand Down Expand Up @@ -38,3 +40,74 @@ const user = store.$repo(User).make({
console.log(user.fullName) // 'John Doe'
console.log(user.prefix('Sir.')) // 'Sir. John Doe'
```

## Defining Mutators

Pina ORM lets you define mutators which are going to modify the specific field when instantiating the Model.
The difference between accessors and mutators is that mutators are going to modify the field itself.
You can define a setter, getter or both for a model.

### directly via method

Using directly a method is like using the `get` attribute. The getter will always be called before
displaying the data but will never change the value in the store.

````ts{60-66}
import { Attr, Model } from 'pinia-orm'
class User extends Model {
static entity = 'users'
@Attr('') name!: string
static mutators() {
return {
name(value: any) {
return value.toUpperCase()
},
}
}
}
console.log(new User({ name: 'john doe' }).name) // 'JOHN DOE'
````

## via `set` and `get`

Now with this approach you can also now apply an setter which will mutate
the value before it is saved in the store.

````ts
import {Attr, Model, useRepo} from 'pinia-orm'
import { getActivePinia } from 'pinia';

class User extends Model {
static entity = 'users'

@Attr(0) id!: number
@Attr('') name!: string

static mutators() {
return {
name: {
get: (value: any) => value.toLowerCase(),
set: (value: any) => value.toUpperCase(),
},
}
}
}

const userRepo = useRepo(User)
userRepo.save({
id: 1,
name: 'John Doe',
})

console.log(getActivePinia().state.value)
// users: {
// 1: {id: 1, name: 'JOHN DOE'},
// }

console.log(userRepo.find(1)?.name) // 'JOHN DOE'
````

If you like decorators you can also use the [`Mutate` decorator](/model/decorators#mutator) to apply a mutation to an value
44 changes: 31 additions & 13 deletions docs/content/2.model/3.decorators.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class User extends Model {
static fields () {
return {
id: this.attr(),
name: this.string('')
name: this.string(''),
posts: this.hasMany(Post, 'userId')
}
}
Expand Down Expand Up @@ -73,7 +73,7 @@ export class User extends Model {

### `@Str`

Marks a property on the model as a [string attribute](1.getting-started.md#string-type) type. For example:
Marks a property on the model as a [string attribute](./getting-started#string-type) type. For example:

```ts
import { Model, Attr, Str } from 'pinia-orm'
Expand All @@ -91,7 +91,7 @@ export class User extends Model {

### `@Num`

Marks a property on the model as a [number attribute](1.getting-started.md#string-type) type. For example:
Marks a property on the model as a [number attribute](./getting-started#string-type) type. For example:

```ts
import { Model, Num } from 'pinia-orm'
Expand All @@ -109,7 +109,7 @@ export class User extends Model {

### `@Bool`

Marks a property on the model as a [boolean attribute](1.getting-started.md#boolean-type) type. For example:
Marks a property on the model as a [boolean attribute](./getting-started#boolean-type) type. For example:

```ts
import { Model, Bool } from 'pinia-orm'
Expand All @@ -127,7 +127,7 @@ export class User extends Model {

### `@Uid`

Marks a property on the model as a [Uid attribute](1.getting-started.md#uid-type) type. For example:
Marks a property on the model as a [Uid attribute](./getting-started#uid-type) type. For example:

```ts
import { Model, Uid } from 'pinia-orm'
Expand All @@ -140,13 +140,31 @@ class User extends Model {
}
```

## Mutator

Adds a [mutator](/model/accessors#defining-mutators) to a property on the model. For example:

````ts
import { Attr, Mutate, Model } from 'pinia-orm'

class User extends Model {
static entity = 'users'

@Mutate((value: any) => value.toUpperCase())
@Attr('')
name!: string
}

console.log(new User({ name: 'john doe' }).name) // 'JOHN DOE'
````

## Relationships

Decorators on relation properties accept the same argument signature as their corresponding field attribute type with the exception that model references should be defined as a closure that return the model constructor (to avoid circular dependencies).

### `@HasOne`

Marks a property on the model as a [hasOne attribute](../relationships/1.getting-started.md) type. For example:
Marks a property on the model as a [hasOne attribute](../relationships/getting-started) type. For example:

```ts
import { Model, HasOne } from 'pinia-orm'
Expand All @@ -162,7 +180,7 @@ class User extends Model {

### `@BelongsTo`

Marks a property on the model as a [belongsTo attribute](../relationships/1.getting-started.md) type. For example:
Marks a property on the model as a [belongsTo attribute](../relationships/getting-started) type. For example:

```ts
import { Model, Attr, BelongsTo } from 'pinia-orm'
Expand All @@ -181,7 +199,7 @@ class Post extends Model {

### `@HasMany`

Marks a property on the model as a [hasMany attribute](../3.relationships/1.getting-started.md) type. For example:
Marks a property on the model as a [hasMany attribute](../relationships/getting-started) type. For example:

```ts
import { Model, HasMany } from 'pinia-orm'
Expand All @@ -197,7 +215,7 @@ class User extends Model {

### `@HasManyBy`

Marks a property on the model as a [hasManyBy attribute](../3.relationships/3.one-to-many) type. For example:
Marks a property on the model as a [hasManyBy attribute](../relationships/one-to-many) type. For example:

```ts
import { Model, HasManyBy } from 'pinia-orm'
Expand All @@ -216,7 +234,7 @@ class Cluster extends Model {

### `@BelongsToMany`

Marks a property on the model as a [belongsToMany attribute](../3.relationships/4.many-to-many) type. For example:
Marks a property on the model as a [belongsToMany attribute](../relationships/many-to-many) type. For example:

```ts
import { Model, HasManyBy } from 'pinia-orm'
Expand All @@ -232,7 +250,7 @@ class User extends Model {

### `@MorphOne`

Marks a property on the model as a [morphOne attribute](../3.relationships/5.polymorphic) type. For example:
Marks a property on the model as a [morphOne attribute](../relationships/polymorphic) type. For example:

```ts
import { Model, MorphOne } from 'pinia-orm'
Expand All @@ -248,7 +266,7 @@ class User extends Model {

### `@MorphTo`

Marks a property on the model as a [morphTo attribute](../3.relationships/5.polymorphic) type. For example:
Marks a property on the model as a [morphTo attribute](../relationships/polymorphic) type. For example:

```ts
import { Model, MorphTo } from 'pinia-orm'
Expand All @@ -271,7 +289,7 @@ class Image extends Model {

### `@MorphMany`

Marks a property on the model as a [morphMany attribute](../3.relationships/5.polymorphic) type. For example:
Marks a property on the model as a [morphMany attribute](../relationships/polymorphic) type. For example:

```ts
import { Model, MorphMany } from 'pinia-orm'
Expand Down
2 changes: 2 additions & 0 deletions packages/pinia-orm/src/index.cjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { mapRepos } from './helpers/Helpers'
import { Database } from './database/Database'
import { Schema } from './schema/Schema'
import { Model } from './model/Model'
import { Mutate } from './model/decorators/Mutate'
import { Attr } from './model/decorators/attributes/types/Attr'
import { Str } from './model/decorators/attributes/types/Str'
import { Num } from './model/decorators/attributes/types/Num'
Expand Down Expand Up @@ -49,6 +50,7 @@ export default {
Database,
Schema,
Model,
Mutate,
Attr,
Str,
Num,
Expand Down
1 change: 1 addition & 0 deletions packages/pinia-orm/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ 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/Mutate'
export * from './model/decorators/NonEnumerable'
export * from './model/attributes/Attribute'
export * from './model/attributes/types/Type'
Expand Down
59 changes: 58 additions & 1 deletion packages/pinia-orm/src/model/Model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assert, isArray, isNullish } from '../support/Utils'
import type { Collection, Element, Item } from '../data/Data'
import type { MutatorFunctions, Mutators } from '../types'
import type { Attribute } from './attributes/Attribute'
import { Attr } from './attributes/types/Attr'
import { String as Str } from './attributes/types/String'
Expand All @@ -24,6 +25,7 @@ export type ModelRegistry = Record<string, () => Attribute>
export interface ModelOptions {
fill?: boolean
relations?: boolean
mutator?: 'set' | 'get' | 'none'
}

export class Model {
Expand Down Expand Up @@ -51,8 +53,18 @@ export class Model {
*/
protected static registries: ModelRegistries = {}

/**
* The pinia options for the model. It can contain options which will passed
* to the 'defineStore' function of pinia.
*/
protected static piniaOptions: any = {}

/**
* The mutators for the model. It contains all mutators
* to the 'defineStore' function of pinia.
*/
protected static fieldMutators: Mutators = {}

/**
* The array of booted models.
*/
Expand Down Expand Up @@ -111,12 +123,26 @@ export class Model {
return this
}

/**
* Set an mutator for a field
*/
static setMutator<M extends typeof Model>(
this: M,
key: string,
mutator: MutatorFunctions<any>,
): M {
this.fieldMutators[key] = mutator

return this
}

/**
* Clear the list of booted models so they can be re-booted.
*/
static clearBootedModels(): void {
this.booted = {}
this.schemas = {}
this.fieldMutators = {}
}

/**
Expand Down Expand Up @@ -313,6 +339,13 @@ export class Model {
return new MorphMany(model, related.newRawInstance(), id, type, localKey)
}

/**
* Mutators to mutate matching fields when instantiating the model.
*/
static mutators(): Mutators {
return {}
}

/**
* Get the constructor for this model.
*/
Expand Down Expand Up @@ -384,14 +417,31 @@ export class Model {
$fill(attributes: Element = {}, options: ModelOptions = {}): this {
const fields = this.$fields()
const fillRelation = options.relations ?? true
const useMutator = options.mutator ?? 'get'
const mutators: Mutators = {
...this.$getMutators(),
...this.$self().fieldMutators,
}

for (const key in fields) {
const attr = fields[key]
const value = attributes[key]
let value = attributes[key]

if (attr instanceof Relation && !fillRelation)
continue

if (useMutator !== 'none') {
const mutator = mutators[key]
if (mutator && useMutator === 'get') {
value = typeof mutator === 'function'
? mutator(value)
: typeof mutator.get === 'function' ? mutator.get(value) : value
}

if (mutator && typeof mutator !== 'function' && useMutator === 'set' && mutator.set)
value = mutator.set(value)
}

this.$fillField(key, attr, value)
}

Expand Down Expand Up @@ -526,6 +576,13 @@ export class Model {
return this
}

/**
* Get the mutators of the model
*/
$getMutators(): Mutators {
return this.$self().mutators()
}

/**
* Get the serialized model attributes.
*/
Expand Down
12 changes: 12 additions & 0 deletions packages/pinia-orm/src/model/decorators/Mutate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Mutator } from '../../types'
import type { PropertyDecorator } from './Contracts'

/**
* Create an Mutate attribute property decorator.
*/
export function Mutate(get?: Mutator<any>, set?: Mutator<any>): PropertyDecorator {
return (target, propertyKey) => {
const self = target.$self()
self.setMutator(propertyKey, { get, set })
}
}
Loading

0 comments on commit 5757f97

Please sign in to comment.