Completed
Push — master ( d362a9...56faa4 )
by Mark
14s queued 11s
created

Model.aSyncInsert   F

Complexity

Conditions 14

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 5
dl 0
loc 5
rs 3.6
c 0
b 0
f 0

How to fix   Complexity   

Complexity

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