Commit 16765d8c authored by nanahira's avatar nanahira

add field mixin

parent 6746196e
import { Context, Keys, Model } from 'koishi'; import { Context, Dict, Flatten, Keys, Model, Tables } from 'koishi';
import { ModelClassType } from './def'; import { ModelClassType } from './def';
import { reflector } from './meta/meta'; import { reflector } from './meta/meta';
import _ from 'lodash';
class ModelRegistrar<T = any> { class ModelRegistrar<T = any> {
constructor(private cls: ModelClassType<T>, private prefix = '') {} constructor(private cls: ModelClassType<T>, private prefix = '') {}
...@@ -170,3 +171,23 @@ export function registerModel( ...@@ -170,3 +171,23 @@ export function registerModel(
ctx.model.extend(tableName, ...registrar.getModelResult()); ctx.model.extend(tableName, ...registrar.getModelResult());
Object.assign(ctx.model.config[tableName].internal, registrar.getInternal()); Object.assign(ctx.model.config[tableName].internal, registrar.getInternal());
} }
export function mixinModel<K extends Keys<Tables>>(
ctx: Context,
tableName: K,
classDict: {
[F in Keys<Tables[K]>]?: ModelClassType<Flatten<Tables[K][F]>>;
},
) {
for (const _key in classDict) {
const key = _key as Keys<Tables[K]>;
const cls = classDict[key];
const registrar = new ModelRegistrar<any>(cls, key + '.');
const result = registrar.getModelResult();
ctx.model.extend(tableName, ...result);
Object.assign(
ctx.model.config[tableName].internal,
registrar.getInternal(),
);
}
}
import { ChildModel, DefineModel, ModelField } from '../src/decorators'; import { ChildModel, DefineModel, ModelField } from '../src/decorators';
import { App } from 'koishi'; import { App } from 'koishi';
import { registerModel } from '../src/register'; import { mixinModel, registerModel } from '../src/register';
import MemoryDatabase from '@koishijs/plugin-database-memory'; import MemoryDatabase from '@koishijs/plugin-database-memory';
import exp from 'constants';
declare module 'koishi' { declare module 'koishi' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface // eslint-disable-next-line @typescript-eslint/no-empty-interface
...@@ -11,30 +12,38 @@ declare module 'koishi' { ...@@ -11,30 +12,38 @@ declare module 'koishi' {
} }
} }
class Dress { class WearingPreference {
@ModelField('string(8)') @ModelField('string(8)')
color: string; color: string;
@ModelField('integer(7)') @ModelField('string(12)')
size: string; shape: string;
format() {
return `${this.color} ${this.shape}`;
}
} }
class Shirt { class Wearing {
@ModelField('string(8)') @ModelField('string(3)')
color: string;
@ModelField('integer(7)')
size: string; size: string;
}
@DefineModel('user') getSize() {
class UserMixin1 { return this.size;
}
@ChildModel() @ChildModel()
dress: Dress; preference: WearingPreference;
} }
@DefineModel('user') class Dress extends Wearing {
class UserMixin2 { getDressSize() {
@ChildModel() return this.getSize();
shirt: Shirt; }
}
class Shirt extends Wearing {
getShirtSize() {
return this.getSize();
}
} }
describe('Model test', () => { describe('Model test', () => {
...@@ -43,22 +52,63 @@ describe('Model test', () => { ...@@ -43,22 +52,63 @@ describe('Model test', () => {
beforeEach(async () => { beforeEach(async () => {
app = new App(); app = new App();
app.plugin(MemoryDatabase); app.plugin(MemoryDatabase);
mixinModel(app, 'user', { dress: Dress, shirt: Shirt });
await app.start(); await app.start();
}); });
it('should register model fields', () => { it('should register model fields', () => {
registerModel(app, UserMixin1);
registerModel(app, UserMixin2);
const { user } = app.model.config; const { user } = app.model.config;
expect(user.fields['dress.color'].type).toBe('string'); expect(user.fields['dress.size'].type).toBe('string');
expect(user.fields['dress.size'].type).toBe('integer'); expect(user.fields['dress.size'].length).toBe(3);
expect(user.fields['dress.color'].length).toBe(8); expect(user.fields['shirt.size'].type).toBe('string');
expect(user.fields['dress.size'].length).toBe(7); expect(user.fields['shirt.size'].length).toBe(3);
expect(user.fields['shirt.color'].type).toBe('string'); expect(user.fields['dress.preference.color'].type).toBe('string');
expect(user.fields['shirt.size'].type).toBe('integer'); expect(user.fields['dress.preference.color'].length).toBe(8);
expect(user.fields['shirt.color'].length).toBe(8); expect(user.fields['shirt.preference.color'].type).toBe('string');
expect(user.fields['shirt.size'].length).toBe(7); expect(user.fields['shirt.preference.color'].length).toBe(8);
expect(user.fields['dress.preference.shape'].type).toBe('string');
expect(user.fields['dress.preference.shape'].length).toBe(12);
expect(user.fields['shirt.preference.shape'].type).toBe('string');
expect(user.fields['shirt.preference.shape'].length).toBe(12);
expect(user.internal['dress.']).toEqual(Dress.prototype); expect(user.internal['dress.']).toEqual(Dress.prototype);
expect(user.internal['shirt.']).toEqual(Shirt.prototype); expect(user.internal['shirt.']).toEqual(Shirt.prototype);
expect(user.internal['dress.preference.']).toEqual(
WearingPreference.prototype,
);
expect(user.internal['shirt.preference.']).toEqual(
WearingPreference.prototype,
);
});
it('should make class instance', async () => {
await app.database.upsert('user', [
{
id: '111111',
dress: {
size: 'S',
preference: {
color: 'red',
shape: 'round',
},
},
shirt: {
size: 'M',
preference: {
color: 'blue',
shape: 'square',
},
},
},
]);
const [user] = await app.database.get('user', { id: '111111' });
expect(user.dress).toBeInstanceOf(Dress);
expect(user.dress.getDressSize()).toBe('S');
expect(user.dress.preference).toBeInstanceOf(WearingPreference);
expect(user.dress.preference.format()).toBe('red round');
expect(user.shirt).toBeInstanceOf(Shirt);
expect(user.shirt.getShirtSize()).toBe('M');
expect(user.shirt.preference).toBeInstanceOf(WearingPreference);
expect(user.shirt.preference.format()).toBe('blue square');
}); });
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment