Skip to content

Commit 7fdb789

Browse files
committed
feat(models): Add ability to define a default value for model props
1 parent a3bd4ce commit 7fdb789

4 files changed

Lines changed: 79 additions & 26 deletions

File tree

src/classes/model.ts

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,16 @@ export default class Model extends Base {
3636
return that[name]
3737
}
3838
if (
39-
typeof that.state[name] === 'undefined' ||
40-
that.state[name] === null
39+
typeof target.state[name] === 'undefined' ||
40+
target.state[name] === null
4141
) {
4242
return null
4343
}
44-
return Ctor.runTypeHook(name, that.state[name], 'access')
44+
return Ctor.runTypeHook(name, target.state[name], 'access')
4545
},
4646
set(target, name, value) {
4747
if (Ctor.meta.attributes[name]) {
48-
that.state[name] = Ctor.runTypeHook(name, value, 'modify')
48+
target.state[name] = Ctor.runTypeHook(name, value, 'modify')
4949
}
5050
that[name] = value
5151
return true
@@ -109,7 +109,7 @@ export default class Model extends Base {
109109

110110
static attachTypes(types: { [name: string]: Type }) {
111111
Object.keys(this.meta.attributeDefinition).forEach(attr => {
112-
let typeName = this.meta.attributeDefinition[attr]
112+
let typeName = this.meta.attributeDefinition[attr].type
113113
if (types[`${this.modelName}:${typeName}`]) {
114114
typeName = `${this.modelName}:${typeName}`
115115
}
@@ -123,7 +123,7 @@ export default class Model extends Base {
123123
serializers: { default: Serializer; [name: string]: Serializer }
124124
) {
125125
if (!this.meta.attributeDefinition[this.idField]) {
126-
this.meta.attributeDefinition[this.idField] = 'number'
126+
this.meta.attributeDefinition[this.idField] = { type: 'number' }
127127
}
128128
this.attachAdapters(adapters)
129129
this.attachSerializers(serializers)
@@ -182,7 +182,8 @@ export default class Model extends Base {
182182
): Promise<T['prototype'] | null> {
183183
const result = await this.adapter.oneById(this, id, options)
184184
if (!result) return null
185-
return this.hydrate(result)
185+
const instance = this.hydrate(result)
186+
return instance
186187
}
187188

188189
static async oneBySql<T extends typeof Model>(
@@ -235,21 +236,28 @@ export default class Model extends Base {
235236
for (const prop of Reflect.ownKeys(props)) {
236237
if (!Reflect.ownKeys(this.meta.attributes).includes(prop)) {
237238
throw new Error(`
238-
Invalid key '${prop}' defined on 'props' given to 'Model.createOne'.
239-
Included properties must be defined on model class
240-
Valid properties '${Reflect.ownKeys(this.meta.attributes).join(
241-
"', '"
242-
)}'
243-
Instead got '${Reflect.ownKeys(props).join("', '")}'
239+
Invalid key '${prop}' defined on 'props' given to 'Model.createOne'.
240+
Included properties must be defined on model class
241+
Valid properties '${Reflect.ownKeys(this.meta.attributes).join("', '")}'
242+
Instead got '${Reflect.ownKeys(props).join("', '")}'
244243
`)
245244
}
246245
}
247-
const result = await this.adapter.createRecord(this, props)
246+
const defaults: props = {}
247+
for (const [key, value] of Object.entries(this.meta.attributeDefinition)) {
248+
if (value && value.default) {
249+
defaults[key] = value.default
250+
}
251+
}
252+
const data = Object.assign({}, defaults, props)
253+
const result = await this.adapter.createRecord(this, data)
248254
return this.hydrate(result)
249255
}
250256

251257
static async createSome(records: props[]): Promise<number> {
252-
for (const [index, props] of records.entries()) {
258+
// TODO:: clean up code method body as currently both poor readability
259+
// and looping through the data 2 times
260+
for (let [index, props] of records.entries()) {
253261
for (const prop of Reflect.ownKeys(props)) {
254262
if (!Reflect.ownKeys(this.meta.attributes).includes(prop)) {
255263
throw new Error(`
@@ -263,7 +271,20 @@ export default class Model extends Base {
263271
}
264272
}
265273
}
266-
return this.adapter.createSome(this, records)
274+
return this.adapter.createSome(
275+
this,
276+
records.map(record => {
277+
const defaults: props = {}
278+
for (const [key, value] of Object.entries(
279+
this.meta.attributeDefinition
280+
)) {
281+
if (value && value.default) {
282+
defaults[key] = value.default
283+
}
284+
}
285+
return Object.assign({}, defaults, record)
286+
})
287+
)
267288
}
268289

269290
static deleteAll(): Promise<number> {
@@ -453,8 +474,8 @@ export default class Model extends Base {
453474
const json: { [key: string]: any } = {}
454475
const { attributes } = this.ctor.meta
455476
for (const [property, type] of Object.entries(attributes)) {
456-
if (attributes[property] && this[property]) {
457-
json[property] = type.access(this[property])
477+
if (attributes[property] && this.state[property]) {
478+
json[property] = type.access(this.state[property])
458479
}
459480
}
460481
return json
@@ -526,7 +547,9 @@ export type relationship = {
526547
}
527548

528549
export type modelMeta = {
529-
attributeDefinition: { [attrName: string]: string }
550+
attributeDefinition: {
551+
[attrName: string]: { type: string; default?: string }
552+
}
530553
attributes: { [attrName: string]: Type }
531554
relationships: { [relName: string]: relationship }
532555
}

src/decorators/type.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import Model from '../classes/model'
22

33
export default function type(
4-
typeName: string
4+
typeName: string,
5+
options?: { [name: string]: any }
56
): (target: Model, propertyKey: string) => void {
6-
return function typeDecorator(target: Model, propertyKey: string): void {
7+
return function typeDecorator(target: any, propertyKey: string): void {
78
const Ctor = target.constructor as typeof Model
89
const meta = {
910
attributeDefinition: {},
@@ -17,6 +18,9 @@ export default function type(
1718
})
1819
}
1920

20-
Ctor.meta.attributeDefinition[propertyKey] = typeName
21+
Ctor.meta.attributeDefinition[propertyKey] = { type: typeName }
22+
if (options && options.default) {
23+
Ctor.meta.attributeDefinition[propertyKey].default = options.default
24+
}
2125
}
2226
}

tests/integration/working-with-models.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,18 @@ describe('Working with models', () => {
254254
return Cat.adapter.destroy()
255255
})
256256

257+
test('Statically create a single record: defaults used', async () => {
258+
class Cat extends Model {
259+
@type('string', { default: 'Toffee' })
260+
name: string
261+
}
262+
setupModel(Cat)
263+
const cat = await Cat.createOne({})
264+
expect(cat.id).toBeTruthy()
265+
expect(cat.name).toBe('Toffee')
266+
return Cat.adapter.destroy()
267+
})
268+
257269
test('Statically create multiple records', async () => {
258270
class Cat extends Model {
259271
@type('string') name: string
@@ -266,4 +278,18 @@ describe('Working with models', () => {
266278
expect(cats).toBe(2)
267279
return Cat.adapter.destroy()
268280
})
281+
282+
test('Statically create multiple records: defaults used', async () => {
283+
class Cat extends Model {
284+
@type('string', { default: 'Colonel McFluffins' })
285+
name: string
286+
}
287+
setupModel(Cat)
288+
const num = await Cat.createSome([{}, { name: 'Dog' }])
289+
expect(num).toBe(2)
290+
const cats = await Cat.all()
291+
expect(cats[2].name).toBe('Colonel McFluffins')
292+
expect(cats[3].name).toBe('Dog')
293+
return Cat.adapter.destroy()
294+
})
269295
})

tests/unit/decorators/type.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ describe('@type() decorator', () => {
1111
}
1212

1313
expect(Cat.meta.attributeDefinition).toEqual({
14-
str: 'string',
15-
num: 'number',
16-
dat: 'date',
17-
bol: 'boolean'
14+
str: { type: 'string' },
15+
num: { type: 'number' },
16+
dat: { type: 'date' },
17+
bol: { type: 'boolean' }
1818
})
1919

2020
expect(Model.meta.attributeDefinition).toEqual({})

0 commit comments

Comments
 (0)