Skip to content

Commit 7921879

Browse files
Merge pull request #76 from RobinBuschmann/issue-74
fixes #74
2 parents cd7037c + 98cd435 commit 7921879

File tree

3 files changed

+221
-42
lines changed

3 files changed

+221
-42
lines changed

lib/services/association.ts

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,16 @@ export function addAssociation(target: any,
3535
otherKey?: string): void {
3636

3737
let associations = getAssociations(target);
38+
let throughClassGetter;
39+
let options: Partial<ConcatAssociationOptions> = {};
3840

3941
if (!associations) {
4042
associations = [];
41-
setAssociations(target, associations);
4243
}
43-
44-
let throughClassGetter;
45-
4644
if (typeof through === 'function') {
4745
throughClassGetter = through;
4846
through = undefined;
4947
}
50-
51-
let options: Partial<ConcatAssociationOptions> = {};
52-
5348
if (typeof optionsOrForeignKey === 'string') {
5449
options.foreignKey = {name: optionsOrForeignKey};
5550
} else {
@@ -67,6 +62,7 @@ export function addAssociation(target: any,
6762
as,
6863
options
6964
});
65+
setAssociations(target, associations);
7066
}
7167

7268
/**
@@ -131,18 +127,18 @@ export function getForeignKey(model: typeof Model,
131127
* Returns association meta data from specified class
132128
*/
133129
export function getAssociations(target: any): ISequelizeAssociation[] | undefined {
134-
135-
return Reflect.getMetadata(ASSOCIATIONS_KEY, target);
130+
const associations = Reflect.getMetadata(ASSOCIATIONS_KEY, target);
131+
if (associations) {
132+
return [...associations];
133+
}
136134
}
137135

138136
export function setAssociations(target: any, associations: ISequelizeAssociation[]): void {
139-
140137
Reflect.defineMetadata(ASSOCIATIONS_KEY, associations, target);
141138
}
142139

143140
export function getAssociationsByRelation(target: any, relatedClass: any): ISequelizeAssociation[] {
144141
const associations = getAssociations(target);
145-
146142
return (associations || []).filter(association => {
147143
const _relatedClass = association.relatedClassGetter();
148144
return (
@@ -159,18 +155,15 @@ export function getAssociationsByRelation(target: any, relatedClass: any): ISequ
159155
export function addForeignKey(target: any,
160156
relatedClassGetter: ModelClassGetter,
161157
foreignKey: string): void {
162-
163158
let foreignKeys = getForeignKeys(target);
164-
165159
if (!foreignKeys) {
166160
foreignKeys = [];
167-
setForeignKeys(target, foreignKeys);
168161
}
169-
170162
foreignKeys.push({
171163
relatedClassGetter,
172164
foreignKey
173165
});
166+
setForeignKeys(target, foreignKeys);
174167
}
175168

176169
/**
@@ -205,7 +198,6 @@ export function processAssociation(sequelize: BaseSequelize,
205198
let otherKey;
206199

207200
if (association.relation === BELONGS_TO_MANY) {
208-
209201
otherKey = getOtherKey(association);
210202
through = getThroughClass(sequelize, association);
211203
}
@@ -235,7 +227,6 @@ export function processAssociation(sequelize: BaseSequelize,
235227
export function getThroughClass(sequelize: BaseSequelize,
236228
association: ISequelizeAssociation): any {
237229
if (association.through) {
238-
239230
if (!sequelize.thoughMap[association.through]) {
240231
const throughModel = sequelize.getThroughModel(association.through);
241232
sequelize.addModels([throughModel]);
@@ -249,15 +240,16 @@ export function getThroughClass(sequelize: BaseSequelize,
249240
/**
250241
* Returns foreign key meta data from specified class
251242
*/
252-
function getForeignKeys(target: any): ISequelizeForeignKeyConfig[] | undefined {
253-
254-
return Reflect.getMetadata(FOREIGN_KEYS_KEY, target);
243+
export function getForeignKeys(target: any): ISequelizeForeignKeyConfig[] | undefined {
244+
const foreignKeys = Reflect.getMetadata(FOREIGN_KEYS_KEY, target);
245+
if (foreignKeys) {
246+
return [...foreignKeys];
247+
}
255248
}
256249

257250
/**
258251
* Sets foreign key meta data
259252
*/
260253
function setForeignKeys(target: any, foreignKeys: any[]): void {
261-
262254
Reflect.defineMetadata(FOREIGN_KEYS_KEY, foreignKeys, target);
263255
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/* tslint:disable:max-classes-per-file */
2+
import {expect} from 'chai';
3+
import {addAssociation, addForeignKey, getAssociations, getForeignKeys} from "../../../lib/services/association";
4+
import {Model} from "../../../lib/models/Model";
5+
6+
describe('service.association', () => {
7+
8+
describe('addAssociation', () => {
9+
10+
it('should add association to target metadata', () => {
11+
const target = {};
12+
const RELATION = 'hasMany';
13+
const AS_NAME = 'test';
14+
const RELATED_CLASS_GETTER = () => class T extends Model<T> {};
15+
addAssociation(target, RELATION, RELATED_CLASS_GETTER, AS_NAME);
16+
const associations = getAssociations(target);
17+
18+
expect(associations).to.have.property('length', 1);
19+
expect(associations[0]).to.eql({
20+
relation: RELATION,
21+
options: {},
22+
through: undefined,
23+
throughClassGetter: undefined,
24+
as: AS_NAME,
25+
relatedClassGetter: RELATED_CLASS_GETTER,
26+
});
27+
});
28+
29+
it('should add association to target metadata, but not parent', () => {
30+
const parent = {};
31+
const target = Object.create(parent);
32+
const RELATION = 'hasMany';
33+
const PARENT_RELATION = 'belongsToMany';
34+
const AS_NAME = 'test';
35+
const RELATED_CLASS_GETTER = () => class T extends Model<T> {};
36+
addAssociation(parent, PARENT_RELATION, RELATED_CLASS_GETTER, AS_NAME);
37+
addAssociation(target, RELATION, RELATED_CLASS_GETTER, AS_NAME);
38+
39+
const associations = getAssociations(target);
40+
expect(associations).to.have.property('length', 2);
41+
expect(associations[0]).to.eql({
42+
relation: PARENT_RELATION,
43+
options: {},
44+
through: undefined,
45+
throughClassGetter: undefined,
46+
as: AS_NAME,
47+
relatedClassGetter: RELATED_CLASS_GETTER,
48+
});
49+
expect(associations[1]).to.eql({
50+
relation: RELATION,
51+
options: {},
52+
through: undefined,
53+
throughClassGetter: undefined,
54+
as: AS_NAME,
55+
relatedClassGetter: RELATED_CLASS_GETTER,
56+
});
57+
58+
const parentAssociations = getAssociations(parent);
59+
expect(parentAssociations).to.have.property('length', 1);
60+
expect(parentAssociations[0]).to.eql({
61+
relation: PARENT_RELATION,
62+
options: {},
63+
through: undefined,
64+
throughClassGetter: undefined,
65+
as: AS_NAME,
66+
relatedClassGetter: RELATED_CLASS_GETTER,
67+
});
68+
});
69+
70+
});
71+
72+
describe('addForeignKey', () => {
73+
74+
it('should add foreign key to target metadata', () => {
75+
const target = {};
76+
const FOREIGN_KEY = 'testId';
77+
const RELATED_CLASS_GETTER = () => class T extends Model<T> {};
78+
addForeignKey(target, RELATED_CLASS_GETTER, FOREIGN_KEY);
79+
const foreignKeys = getForeignKeys(target);
80+
81+
expect(foreignKeys).to.have.property('length', 1);
82+
expect(foreignKeys[0]).to.eql({
83+
foreignKey: FOREIGN_KEY,
84+
relatedClassGetter: RELATED_CLASS_GETTER,
85+
});
86+
});
87+
88+
it('should add foreign key to target metadata, but not parent', () => {
89+
const parent = {};
90+
const target = Object.create(parent);
91+
const FOREIGN_KEY = 'testId';
92+
const PARENT_FOREIGN_KEY = 'parentTestId';
93+
const RELATED_CLASS_GETTER = () => class T extends Model<T> {};
94+
addForeignKey(parent, RELATED_CLASS_GETTER, PARENT_FOREIGN_KEY);
95+
addForeignKey(target, RELATED_CLASS_GETTER, FOREIGN_KEY);
96+
97+
const foreignKeys = getForeignKeys(target);
98+
expect(foreignKeys).to.have.property('length', 2);
99+
expect(foreignKeys[0]).to.eql({
100+
foreignKey: PARENT_FOREIGN_KEY,
101+
relatedClassGetter: RELATED_CLASS_GETTER,
102+
});
103+
expect(foreignKeys[1]).to.eql({
104+
foreignKey: FOREIGN_KEY,
105+
relatedClassGetter: RELATED_CLASS_GETTER,
106+
});
107+
108+
const parentForeignKeys = getForeignKeys(parent);
109+
expect(parentForeignKeys).to.have.property('length', 1);
110+
expect(parentForeignKeys[0]).to.eql({
111+
foreignKey: PARENT_FOREIGN_KEY,
112+
relatedClassGetter: RELATED_CLASS_GETTER,
113+
});
114+
});
115+
116+
});
117+
118+
});

test/specs/services/models.spec.ts

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,107 @@
11
import {expect} from 'chai';
2-
import {resolveModelGetter} from '../../../lib/services/models';
2+
import {
3+
addAttribute, addAttributeOptions, getAttributes,
4+
resolveModelGetter, setAttributes
5+
} from '../../../lib/services/models';
36
import {Book} from "../../models/Book";
7+
import {DataType} from "../../../lib/enums/DataType";
48

59
/* tslint:disable:max-classes-per-file */
610

7-
describe('services', () => {
11+
describe('services.models', () => {
812

9-
describe('models', () => {
13+
describe('resolveModelGetter', () => {
1014

11-
describe('resolveModelGetter', () => {
15+
const options = {
16+
a: () => Book,
17+
b: () => null,
18+
c: {
19+
c1: () => Book,
20+
c2: () => null
21+
}
22+
};
1223

13-
const options = {
14-
a: () => Book,
15-
b: () => null,
16-
c: {
17-
c1: () => Book,
18-
c2: () => null
19-
}
20-
};
24+
resolveModelGetter(options);
2125

22-
resolveModelGetter(options);
26+
it('should resolve getter', () => {
2327

24-
it('should resolve getter', () => {
28+
expect(options.a).to.be.equal(Book);
29+
expect(options.c.c1).to.be.equal(Book);
30+
});
31+
32+
it('should not resolve other functions', () => {
33+
34+
expect(options.b).to.be.a('function');
35+
expect(options.c.c2).to.be.a('function');
36+
});
37+
});
38+
39+
describe('addAttribute', () => {
2540

26-
expect(options.a).to.be.equal(Book);
27-
expect(options.c.c1).to.be.equal(Book);
28-
});
41+
it('should not throw', () => {
42+
expect(() => addAttribute({}, 'test', {})).to.not.throw();
43+
});
44+
45+
});
2946

30-
it('should not resolve other functions', () => {
47+
describe('getAttributes', () => {
3148

32-
expect(options.b).to.be.a('function');
33-
expect(options.c.c2).to.be.a('function');
34-
});
49+
const target = {};
50+
const ATTRIBUTES = {name: {primaryKey: true}, age: {type: DataType.NUMBER}};
51+
setAttributes(target, ATTRIBUTES);
52+
53+
it('should not return reference but copy of attributes', () => {
54+
const attributes = getAttributes(target);
55+
expect(attributes).to.not.equal(ATTRIBUTES);
3556
});
3657

3758
});
59+
60+
describe('addAttributeOptions', () => {
61+
62+
const target = {};
63+
const PROPERTY_NAME = 'test';
64+
const OPTIONS = {allowNull: true};
65+
addAttribute(target, PROPERTY_NAME, {});
66+
addAttributeOptions(target, PROPERTY_NAME, OPTIONS);
67+
68+
it('should be able to retrieve added attribute options', () => {
69+
const attributes = getAttributes(target);
70+
expect(Object.keys(attributes)).to.have.property('length', 1);
71+
expect(Object.keys(attributes[PROPERTY_NAME])).to.have.property('length', Object.keys(OPTIONS).length);
72+
expect(attributes).to.have.property(PROPERTY_NAME).that.eqls(OPTIONS);
73+
});
74+
75+
it('should be able to retrieve added attribute options of prototype linked object', () => {
76+
const child = Object.create(target);
77+
const attributes = getAttributes(child);
78+
expect(Object.keys(attributes)).to.have.property('length', 1);
79+
expect(Object.keys(attributes[PROPERTY_NAME])).to.have.property('length', Object.keys(OPTIONS).length);
80+
expect(attributes).to.have.property(PROPERTY_NAME).that.eqls(OPTIONS);
81+
});
82+
83+
it('should add new options to child prototype but not parent one', () => {
84+
const child = Object.create(target);
85+
const NEW_OPTIONS = {primaryKey: true};
86+
addAttributeOptions(child, PROPERTY_NAME, NEW_OPTIONS);
87+
88+
// for child
89+
const attributes = getAttributes(child);
90+
expect(Object.keys(attributes)).to.have.property('length', 1);
91+
expect(Object.keys(attributes[PROPERTY_NAME]))
92+
.to.have.property(
93+
'length',
94+
Object.keys(OPTIONS).length + Object.keys(NEW_OPTIONS).length
95+
);
96+
expect(attributes).to.have.property(PROPERTY_NAME).that.eqls({...OPTIONS, ...NEW_OPTIONS});
97+
98+
// for parent
99+
const parentAttributes = getAttributes(target);
100+
expect(Object.keys(parentAttributes)).to.have.property('length', 1);
101+
expect(Object.keys(parentAttributes[PROPERTY_NAME])).to.have.property('length', Object.keys(OPTIONS).length);
102+
expect(parentAttributes).to.have.property(PROPERTY_NAME).that.eqls(OPTIONS);
103+
});
104+
105+
});
106+
38107
});

0 commit comments

Comments
 (0)