Completed
Push — master ( cf2011...f1da37 )
by Mark
14s queued 11s
created

Model.arrayToObjects   A

Complexity

Conditions 4

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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