Skip to content

Commit be70c51

Browse files
Robin BuschmannRobin Buschmann
authored andcommitted
now throws error when static fn of uninitialized model is called
1 parent d342f05 commit be70c51

File tree

7 files changed

+82
-14
lines changed

7 files changed

+82
-14
lines changed

example/app.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,17 @@ sequelize
7070
)
7171
.then(() => {
7272
const post = new Post({
73-
text: 'hey2',
73+
text: 'hey',
7474
author: {
7575
name: 'jörn'
7676
}
7777
}, {include: [Author]});
7878

79-
return post.save();
79+
return post
80+
.save({validate: true})
81+
.catch(err => {
82+
'stop';
83+
});
8084
})
8185
.then(() => Post.build<Post>({text: 'hey3'}).save())
8286
.then(() => Post

example/models/Post.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Comment} from "./Comment";
66
import {PostTopic} from "./PostTopic";
77
import {Topic} from "./Topic";
88
import {Author} from "./Author";
9+
import {Length} from "../../lib/annotations/validation/Length";
910

1011
@Scopes({
1112
full: {
@@ -28,7 +29,9 @@ export class Post extends Model<Post> {
2829
@Column
2930
id: number;
3031

31-
@Column
32+
@Column({
33+
field: '_text_'
34+
})
3235
text: string;
3336

3437
@HasMany(() => Comment)
@@ -45,3 +48,5 @@ export class Post extends Model<Post> {
4548
author?: Author;
4649

4750
}
51+
52+
Post.beforeCreate(post => {});

lib/models/BaseModel.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import {majorVersion} from "../utils/versioning";
44
import {capitalize} from "../utils/string";
55
import {IAssociationActionOptions} from "../interfaces/IAssociationActionOptions";
66
import {preConformIncludes} from "../services/models";
7+
import {getAllPropertyNames} from "../utils/object";
78

89
const parentPrototype = majorVersion === 3 ? (Instance as any).prototype : (Model as any).prototype;
910

1011
export abstract class BaseModel {
1112

13+
static isInitialized: boolean = false;
14+
1215
/**
1316
* Indicates which static methods of Model has to be proxied,
1417
* to prepare include option to automatically resolve alias;
@@ -47,26 +50,50 @@ export abstract class BaseModel {
4750
.forEach(name => target[name] = this[name])
4851
;
4952

50-
51-
// Creates proxies for pre conforming include options; see "preConformIncludes"
52-
Object
53-
.keys(this.toPreConformIncludeMap)
53+
// Creates proxies for all static methods but forbidden ones
54+
getAllPropertyNames(target)
55+
.filter(key => !isForbiddenKey(key))
5456
.forEach(key => {
5557

58+
if (typeof target[key] !== 'function') return;
59+
5660
const superFn = target[key];
5761

5862
target[key] = function(...args: any[]): any {
5963

60-
const options = args[BaseModel.toPreConformIncludeMap[key]];
64+
if (!this.isInitialized) {
65+
throw new Error(`Model not initialized: "${this.name}" needs to be added to a Sequelize instance ` +
66+
`before "${key}" can be called.`);
67+
}
68+
69+
const optionIndex = BaseModel.toPreConformIncludeMap[key];
70+
71+
if (optionIndex !== void 0) {
6172

62-
if (options) {
73+
const options = args[optionIndex];
6374

64-
args[BaseModel.toPreConformIncludeMap[key]] = preConformIncludes(options, this);
75+
if (options) {
76+
77+
args[optionIndex] = preConformIncludes(options, this);
78+
}
6579
}
6680

6781
return superFn.call(this, ...args);
6882
};
6983
});
84+
85+
function isForbiddenKey(key: string): boolean {
86+
87+
// is private member?
88+
if (key.charAt(0) === '_') {
89+
return true;
90+
}
91+
92+
const forbiddenKeys = ['name', 'constructor', 'length', 'prototype', 'caller', 'arguments', 'apply',
93+
'QueryInterface', 'QueryGenerator', 'init', 'replaceHookAliases', 'refreshAttributes'];
94+
95+
return forbiddenKeys.indexOf(key) !== -1;
96+
}
7097
}
7198

7299
static prepareInstantiationOptions(options: BuildOptions, source: any): BuildOptions {

lib/models/BaseSequelize.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export abstract class BaseSequelize {
6464
const models = getModels(arg);
6565

6666
this.defineModels(models);
67+
models.forEach(model => model.isInitialized = true);
6768
this.associateModels(models);
6869
resolveScopes(models);
6970
}

lib/models/Model.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export declare class Model<T> extends Hooks {
2828

2929
constructor(values?: any, options?: IBuildOptions);
3030

31+
static isInitialized: boolean;
32+
3133
/**
3234
* Remove attribute from model definition
3335
*

lib/utils/object.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* The last source overrides all properties of the previous
44
* ones, if they have the same names
55
*/
6-
export function deepAssign<T, S1, S2, S3>(target: T, source1: S1, source2: S2, source3: S3): T&S1&S2&S3;
7-
export function deepAssign<T, S1, S2>(target: T, source1: S1, source2: S2): T&S1&S2;
8-
export function deepAssign<T, S>(target: T, source: S): T&S;
6+
export function deepAssign<T, S1, S2, S3>(target: T, source1: S1, source2: S2, source3: S3): T & S1 & S2 & S3;
7+
export function deepAssign<T, S1, S2>(target: T, source1: S1, source2: S2): T & S1 & S2;
8+
export function deepAssign<T, S>(target: T, source: S): T & S;
99
export function deepAssign<S>(target: {}, source: S): S;
1010
export function deepAssign(target: any, ...sources: any[]): any {
1111

@@ -19,7 +19,7 @@ export function deepAssign(target: any, ...sources: any[]): any {
1919

2020
return target;
2121

22-
function assign(key: string|number, _target: any, _source: any): void {
22+
function assign(key: string | number, _target: any, _source: any): void {
2323

2424
const sourceValue = _source[key];
2525
if (sourceValue !== void 0) {
@@ -91,3 +91,21 @@ export function cloneRegExp(input: RegExp, injectFlags?: string): RegExp {
9191
return ( new RegExp(pattern, flags) );
9292
}
9393

94+
export function getAllPropertyNames(obj: any): string[] {
95+
const names: string[] = [];
96+
do {
97+
names.push.apply(names, Object.getOwnPropertyNames(obj));
98+
obj = Object.getPrototypeOf(obj);
99+
} while (obj !== Object.prototype);
100+
101+
const exists: {[name: string]: boolean|undefined} = {};
102+
103+
return names.filter(name => {
104+
105+
const isValid = !exists[name] && name !== 'constructor';
106+
107+
exists[name] = true;
108+
109+
return isValid;
110+
});
111+
}

test/specs/model.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ describe('model', () => {
6666
return MainUser.sync({force: true});
6767
});
6868

69+
describe('static functions when not initialized', () => {
70+
71+
it('should throw an error', () => {
72+
@Table
73+
class Test extends Model<Test> {}
74+
75+
expect(() => Test.beforeCreate(test => test)).to.throw(/^Model not initialized/);
76+
});
77+
78+
});
79+
6980
describe('constructor', () => {
7081
it('uses the passed dao name as tablename if freezeTableName', () => {
7182

0 commit comments

Comments
 (0)