Completed
Push — master ( c68c1b...64b526 )
by Mark
16s queued 12s
created

Model   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 271
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 205
dl 0
loc 271
rs 5.04
c 0
b 0
f 0
wmc 57

22 Functions

Rating   Name   Duplication   Size   Complexity  
F aSyncInsert 0 5 18
A resetDirty 0 4 1
A insert 0 14 3
A fillRelations 0 8 4
A find 0 3 1
A ids 0 6 1
A update 0 13 3
A jsonStringify 0 3 1
A tableSetup 0 3 1
A select 0 9 2
A save 0 18 3
A all 0 3 1
A getIndexByKey 0 6 1
A getInstance 0 9 3
A toJSON 0 3 1
A delete 0 3 1
A setFields 0 4 1
A fill 0 7 4
A registerIndex 0 3 1
A aSyncUpdate 0 5 1
A toObject 0 3 1
A isDirty 0 6 2

How to fix   Complexity   

Complexity

Complex classes like Model often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import HasManyThrough from './Model/Relation/HasManyThrough';
2
import HasMany from "./Model/Relation/HasMany";
3
import BelongsTo from "./Model/Relation/BelongTo";
4
import MorphOne from "./Model/Relation/MorphOne";
5
import HasOne from "./Model/Relation/HasOne";
6
import HasOneThrough from "./Model/Relation/HasOneThrough";
7
import MorphTo from "./Model/Field/MorphTo";
8
import Field from "./Model/Field";
9
import Relation from "./Model/Relation";
10
import ForeignKey from "./Model/Field/ForeignKey";
11
import Index from "./Table/Index";
12
import {ModelInterface, ModelStaticInterface} from "../JeloquentInterfaces";
13
import Collection from "./Collection";
14
import * as Str from "../Util/Str";
15
import * as Obj from "../Util/Obj";
16
import * as ModelSetup from "./Model/Util/Setup";
17
18
class Model implements ModelInterface {
19
20
    private static kebabCaseName: string;
21
22
    private static snakeCaseName: string;
23
24
    _tmpId: string;
25
26
    ['constructor']: ModelStaticInterface;
27
28
    private _originalFields: Array<Field>;
29
30
    private _primaryFields: Array<Field>;
31
32
    private numberOfFields: number;
33
34
    constructor(fields: Array<Field> = []) {
35
        this.setFields(ModelSetup.addRelationFieldsToList(fields));
36
        this._tmpId = `_${++globalThis.Store.numberOfModelCreated}`;
37
    }
38
39
    static get className() : string {
40
        return this.name;
41
    }
42
43
    static get kebabCaseClassName(): string {
44
        return this.kebabCaseName ??= Str.KebabCase(this.className);
45
    }
46
47
    static get snakeCaseClassName(): string {
48
        return this.snakeCaseName ??= Str.SnakeCase(this.className);
49
    }
50
51
    get className(): string {
52
        return this.constructor.className;
53
    }
54
55
    get dirtyFieldNames() {
56
        return this.dirtyFields.map(field => field.name);
57
    }
58
59
    get dirtyFields() {
60
        return this.originalFields.filter(field => field.isDirty);
61
    }
62
63
    get kebabCaseClassName(): string {
64
        return this.constructor.kebabCaseClassName;
65
    }
66
67
    get originalFields(): Array<Field> {
68
        return this._originalFields;
69
    }
70
71
    get originalPrimaryKey() {
72
        return this.primaryFields.reduce((toValue, field, i) => {
73
            if (i > 0) {
74
                return `${toValue}-${field.originalValue}`;
75
            }
76
            return field.originalValue;
77
        }, '') ?? this._tmpId ?? null;
78
    }
79
80
    get originalValues() {
81
        return this.originalFields.reduce((originalValues, field) => {
82
            if (field.originalValue !== undefined) {
83
                originalValues[field.name] = field.originalValue;
84
            }
85
            return originalValues;
86
        }, {});
87
    }
88
89
    get primaryFields():Array<Field> {
90
        return this._primaryFields ??= this.originalFields.filter(field => field.isPrimary);
91
    }
92
93
    get primaryKey(): string|number {
94
        return this.primaryFields.reduce((toValue:string, field:Field, i:number): string|number => {
95
            if (i > 0) {
96
                return `${toValue}-${field.value}`;
97
            }
98
            return field.value as (string|number);
99
        }, '') ?? this._tmpId ?? null;
100
    }
101
102
    get primaryKeyName(): Array<string> {
103
        return this.originalFields.filter(field => field.isPrimary).map(field => field.name);
104
    }
105
106
    get snakeCaseClassName(): string {
107
        return this.constructor.snakeCaseClassName;
108
    }
109
110
    static aSyncInsert(data): Promise<Collection> {
111
        return new Promise((resolve) => {
112
            queueMicrotask(() => {
113
                resolve(this.insert(data));
114
            });
115
        });
116
    }
117
118
    static aSyncUpdate(data): Promise<Collection> {
119
        return new Promise((resolve) => {
120
            queueMicrotask(() => {
121
                resolve(this.update(data));
122
            });
123
        });
124
    }
125
126
    static all(): Collection {
127
        return globalThis.Store.database().all(this.className);
128
    }
129
130
    static delete(id): void {
131
        globalThis.Store.database().delete(this.className, id);
132
    }
133
134
    static find(id) {
135
        return globalThis.Store.database().find(this.className, id);
136
    }
137
138
    static getIndexByKey(indexName) {
139
        return globalThis
140
            .Store
141
            .database()
142
            .getIndexByKey(this.className, indexName);
143
    }
144
145
    static getInstance(): ModelInterface {
146
        const original = globalThis.Store.classInstances[this.className] ?? (globalThis.Store.classInstances[this.className] = new this())
147
        const fieldsClone = original.originalFields.reduce((obj, field) => {
148
            obj.push(Object.assign(Object.create(Object.getPrototypeOf(field)), field));
149
            return obj;
150
        }, [])
151
152
        return Object.create(Object.getPrototypeOf(original)).setFields(fieldsClone);
153
    }
154
155
    static ids() {
156
        return globalThis
157
            .Store
158
            .database()
159
            .ids(this.className);
160
    }
161
162
    static insert(data: object|Array<object>): Collection {
163
        const modelsData = Array.isArray(data) ? data : [data];
164
        const length = modelsData.length;
165
        const models = new Collection();
166
        for (let i = 0; i < length; i++) {
167
            const modelData = modelsData[i];
168
            const model = this.getInstance();
169
            model.fill(modelData);
170
            globalThis.Store.database().insert(this.className, model);
171
            model.fillRelations(modelData);
172
            models.push(model);
173
        }
174
        return models;
175
    }
176
177
    static registerIndex(name: string): void {
178
        Index.register(this.getInstance(), name);
179
    }
180
181
    /**
182
     * @deprecated
183
     */
184
    static select(id) {
185
        try {
186
            return globalThis.Store.database().select(this.className, id);
187
        } catch (e) {
188
            console.error(e);
189
        }
190
    }
191
192
    static update(data: object|Array<object>): Collection {
193
        const modelsData = Array.isArray(data) ? data : [data];
194
        const length = modelsData.length;
195
        const models = new Collection();
196
        for (let i = 0; i < length; i++) {
197
            const model = this.getInstance();
198
            model.fill(data);
199
            globalThis.Store.database().update(this.className, model);
200
            model.fillRelations(data);
201
            models.push(model);
202
        }
203
        return models;
204
    }
205
206
    delete() {
207
        this.constructor.delete(this.primaryKey);
208
    }
209
210
    fill(data) {
211
        for (let i = 0; i < this.numberOfFields; i++) {
212
            if (!(this.originalFields[i] instanceof Relation)) {
213
                const fieldName = this.originalFields[i].name;
214
                if (data[fieldName] !== undefined) {
215
                    this[`_${fieldName}`] = data[fieldName];
216
                }
217
            }
218
        }
219
    }
220
221
    fillRelations(data: object): void {
222
        // insert through relations after model insert;
223
        for (let i = 0; i < this.numberOfFields; i++) {
224
            if ((this.originalFields[i] instanceof Relation)) {
225
                const fieldName = this.originalFields[i].name;
226
                if (data[fieldName] !== undefined) {
227
                    this[`_${fieldName}`] = data[fieldName];
228
                }
229
            }
230
        }
231
    }
232
233
    isDirty(fieldName) {
234
        if (fieldName) {
235
            return this.dirtyFieldNames.includes(fieldName);
236
        }
237
        return this.dirtyFields.length > 0;
238
    }
239
240
    jsonStringify(): string {
241
        return JSON.stringify(this.toObject());
242
    }
243
244
    registerIndex(name) {
245
        Index.register(this, name);
246
    }
247
248
    resetDirty() {
249
        this.originalFields.filter((field) => !(field instanceof Relation)).forEach(field => {
250
            field.resetDirty();
251
        })
252
    }
253
254
    save() {
255
        const className = this.className;
256
        const currentDatabase = globalThis.Store.database();
257
        const tableIds = currentDatabase.ids(className);
258
259
        if (this.primaryKey[0] !== '_' && tableIds.includes(this._tmpId)) {
260
            //todo remove indexes for foreignKey
261
            //                                team_id  this.team_id
262
            Index.removeTmpIdFromIndex(this);
263
            currentDatabase.delete(className, this._tmpId);
264
        }
265
266
        if (tableIds.includes(this.primaryKey)) {
267
            currentDatabase.update(className, this);
268
            return;
269
        }
270
        currentDatabase.insert(className, this);
271
    }
272
273
    setFields(fields) {
274
        ModelSetup.setFields(this, fields);
275
        return this;
276
    }
277
278
    tableSetup(table) {
279
        ModelSetup.setupTable(this, table);
280
    }
281
282
    toJSON(): object {
283
        return this.toObject();
284
    }
285
286
    toObject(fromRelation = false): object {
287
        return Obj.fromModel(this, fromRelation);
288
    }
289
}
290
291
export {
292
    Model,
293
    Field,
294
    Relation,
295
    BelongsTo,
296
    HasOne,
297
    HasOneThrough,
298
    HasMany,
299
    HasManyThrough,
300
    MorphOne,
301
    MorphTo,
302
    ForeignKey,
303
};