velocious 1.0.66 → 1.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/spec/database/record/create-spec.js +42 -1
- package/src/database/query/preloader/has-many.js +2 -2
- package/src/database/record/index.js +51 -5
- package/src/database/record/instance-relationships/base.js +4 -0
- package/src/database/record/instance-relationships/belongs-to.js +1 -3
- package/src/database/record/instance-relationships/has-many.js +32 -4
- package/src/database/record/instance-relationships/has-one.js +2 -4
- package/src/database/record/relationships/base.js +2 -1
- package/src/database/record/relationships/belongs-to.js +24 -0
- package/src/database/record/relationships/has-many.js +21 -0
- package/src/database/record/relationships/has-one.js +21 -0
package/package.json
CHANGED
|
@@ -29,14 +29,55 @@ describe("Record - create", () => {
|
|
|
29
29
|
// 'name' is not a column but rather a column on the translation data model.
|
|
30
30
|
expect(() => project.readColumn("name")).toThrowError("No such attribute or not selected Project#name")
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
// It saves a project note through a has one relationship
|
|
33
34
|
const projectDetail = project.projectDetail()
|
|
34
35
|
|
|
36
|
+
expect(projectDetail.isNewRecord()).toBeFalse()
|
|
37
|
+
expect(projectDetail.isPersisted()).toBeTrue()
|
|
35
38
|
expect(projectDetail.note()).toEqual("Test note")
|
|
36
39
|
expect(projectDetail.projectId()).toEqual(project.id())
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
// It automatically sets the relationship that saved it on a has-one-relationship
|
|
43
|
+
const projectInstanceRelationship = projectDetail.getRelationshipByName("project")
|
|
44
|
+
|
|
45
|
+
expect(projectInstanceRelationship.getPreloaded()).toBeTrue()
|
|
46
|
+
expect(projectDetail.project().id()).toEqual(project.id())
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
// It automatically sets the relationship that saved it on a has-many-relationship
|
|
50
|
+
const tasksRelationship = project.getRelationshipByName("tasks")
|
|
51
|
+
|
|
52
|
+
expect(tasksRelationship.getPreloaded()).toBeTrue()
|
|
53
|
+
|
|
54
|
+
const projectTasksIDs = project.tasks().loaded().map((task) => task.id())
|
|
55
|
+
|
|
56
|
+
expect(projectTasksIDs).toEqual([task.id()])
|
|
37
57
|
})
|
|
38
58
|
})
|
|
39
59
|
|
|
60
|
+
it("sets the inversed relationship on has-many-relationships", async () => {
|
|
61
|
+
const project = new Project({name: "Test project"})
|
|
62
|
+
|
|
63
|
+
project.tasks().build({name: "Test task 1"})
|
|
64
|
+
project.tasks().build({name: "Test task 2"})
|
|
65
|
+
|
|
66
|
+
await project.save()
|
|
67
|
+
|
|
68
|
+
const tasks = project.tasks().loaded()
|
|
69
|
+
const task1 = tasks.find((task) => task.name() == "Test task 1")
|
|
70
|
+
const task2 = tasks.find((task) => task.name() == "Test task 2")
|
|
71
|
+
|
|
72
|
+
expect(tasks.length).toEqual(2)
|
|
73
|
+
|
|
74
|
+
expect(task1.projectId()).toEqual(project.id())
|
|
75
|
+
expect(task1.project().id()).toEqual(project.id())
|
|
76
|
+
|
|
77
|
+
expect(task2.projectId()).toEqual(project.id())
|
|
78
|
+
expect(task2.project().id()).toEqual(project.id())
|
|
79
|
+
})
|
|
80
|
+
|
|
40
81
|
it("creates a new task with an existing project", async () => {
|
|
41
82
|
await Dummy.run(async () => {
|
|
42
83
|
const project = await Project.create({name: "Test project"})
|
|
@@ -46,13 +46,13 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
|
46
46
|
for (const model of modelsByPrimaryKeyValue[modelValue]) {
|
|
47
47
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
48
48
|
|
|
49
|
-
modelRelationship.setPreloaded(true)
|
|
50
|
-
|
|
51
49
|
if (preloadedCollection.length == 0) {
|
|
52
50
|
modelRelationship.setLoaded([])
|
|
53
51
|
} else {
|
|
54
52
|
modelRelationship.addToLoaded(preloadedCollection)
|
|
55
53
|
}
|
|
54
|
+
|
|
55
|
+
modelRelationship.setPreloaded(true)
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -85,8 +85,24 @@ class VelociousDatabaseRecord {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
88
|
-
const
|
|
89
|
-
const record =
|
|
88
|
+
const instanceRelationship = this.getRelationshipByName(relationshipName)
|
|
89
|
+
const record = instanceRelationship.build(attributes)
|
|
90
|
+
|
|
91
|
+
const inverseOf = instanceRelationship.getRelationship().getInverseOf()
|
|
92
|
+
|
|
93
|
+
if (inverseOf) {
|
|
94
|
+
const inverseInstanceRelationship = record.getRelationshipByName(inverseOf)
|
|
95
|
+
|
|
96
|
+
inverseInstanceRelationship.setAutoSave(false)
|
|
97
|
+
|
|
98
|
+
if (inverseInstanceRelationship.getType() == "hasOne") {
|
|
99
|
+
inverseInstanceRelationship.setLoaded(this)
|
|
100
|
+
} else if (inverseInstanceRelationship.getType() == "hasMany") {
|
|
101
|
+
inverseInstanceRelationship.addToLoaded(this)
|
|
102
|
+
} else {
|
|
103
|
+
throw new Error(`Unknown relationship type: ${inverseInstanceRelationship.getType()}`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
90
106
|
|
|
91
107
|
return record
|
|
92
108
|
}
|
|
@@ -119,8 +135,17 @@ class VelociousDatabaseRecord {
|
|
|
119
135
|
}
|
|
120
136
|
|
|
121
137
|
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(attributes) {
|
|
122
|
-
const
|
|
123
|
-
const record =
|
|
138
|
+
const instanceRelationship = this.getRelationshipByName(relationshipName)
|
|
139
|
+
const record = instanceRelationship.build(attributes)
|
|
140
|
+
|
|
141
|
+
const inverseOf = instanceRelationship.getRelationship().getInverseOf()
|
|
142
|
+
|
|
143
|
+
if (inverseOf) {
|
|
144
|
+
const inverseInstanceRelationship = record.getRelationshipByName(inverseOf)
|
|
145
|
+
|
|
146
|
+
inverseInstanceRelationship.setAutoSave(false)
|
|
147
|
+
inverseInstanceRelationship.setLoaded(this)
|
|
148
|
+
}
|
|
124
149
|
|
|
125
150
|
return record
|
|
126
151
|
}
|
|
@@ -151,6 +176,10 @@ class VelociousDatabaseRecord {
|
|
|
151
176
|
return []
|
|
152
177
|
}
|
|
153
178
|
|
|
179
|
+
static getRelationshipNames() {
|
|
180
|
+
return this.getRelationships().map((relationship) => relationship.getRelationshipName())
|
|
181
|
+
}
|
|
182
|
+
|
|
154
183
|
getRelationshipByName(relationshipName) {
|
|
155
184
|
if (!this._instanceRelationships) this._instanceRelationships = {}
|
|
156
185
|
|
|
@@ -435,6 +464,10 @@ class VelociousDatabaseRecord {
|
|
|
435
464
|
continue
|
|
436
465
|
}
|
|
437
466
|
|
|
467
|
+
if (instanceRelationship.getAutoSave() === false) {
|
|
468
|
+
continue
|
|
469
|
+
}
|
|
470
|
+
|
|
438
471
|
const model = instanceRelationship.loaded()
|
|
439
472
|
|
|
440
473
|
if (model?.isChanged()) {
|
|
@@ -443,6 +476,7 @@ class VelociousDatabaseRecord {
|
|
|
443
476
|
const foreignKey = instanceRelationship.getForeignKey()
|
|
444
477
|
|
|
445
478
|
this.setAttribute(foreignKey, model.id())
|
|
479
|
+
|
|
446
480
|
instanceRelationship.setPreloaded(true)
|
|
447
481
|
instanceRelationship.setDirty(false)
|
|
448
482
|
|
|
@@ -463,6 +497,10 @@ class VelociousDatabaseRecord {
|
|
|
463
497
|
continue
|
|
464
498
|
}
|
|
465
499
|
|
|
500
|
+
if (instanceRelationship.getAutoSave() === false) {
|
|
501
|
+
continue
|
|
502
|
+
}
|
|
503
|
+
|
|
466
504
|
let loaded
|
|
467
505
|
|
|
468
506
|
if (instanceRelationship.getType() == "hasOne") {
|
|
@@ -500,7 +538,7 @@ class VelociousDatabaseRecord {
|
|
|
500
538
|
|
|
501
539
|
async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
|
|
502
540
|
for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
|
|
503
|
-
let loaded = instanceRelationship.
|
|
541
|
+
let loaded = instanceRelationship.getLoadedOrNull()
|
|
504
542
|
|
|
505
543
|
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
506
544
|
|
|
@@ -804,6 +842,10 @@ class VelociousDatabaseRecord {
|
|
|
804
842
|
const instanceRelationship = this._instanceRelationships[instanceRelationshipName]
|
|
805
843
|
let loaded = instanceRelationship._loaded
|
|
806
844
|
|
|
845
|
+
if (instanceRelationship.getAutoSave() === false) {
|
|
846
|
+
continue
|
|
847
|
+
}
|
|
848
|
+
|
|
807
849
|
if (!loaded) continue
|
|
808
850
|
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
809
851
|
|
|
@@ -915,6 +957,10 @@ class VelociousDatabaseRecord {
|
|
|
915
957
|
for (const relationship of this.constructor.getRelationships()) {
|
|
916
958
|
const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
|
|
917
959
|
|
|
960
|
+
if (instanceRelationship.getType() == "hasMany" && instanceRelationship.getLoadedOrNull() === null) {
|
|
961
|
+
instanceRelationship.setLoaded([])
|
|
962
|
+
}
|
|
963
|
+
|
|
918
964
|
instanceRelationship.setPreloaded(true)
|
|
919
965
|
}
|
|
920
966
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
2
2
|
constructor({model, relationship}) {
|
|
3
|
+
this._autoSave = null
|
|
3
4
|
this._dirty = false
|
|
4
5
|
this.model = model
|
|
5
6
|
this.relationship = relationship
|
|
6
7
|
}
|
|
7
8
|
|
|
9
|
+
getAutoSave() { return this._autoSave }
|
|
10
|
+
setAutoSave(newAutoSaveValue) { this._autoSave = newAutoSaveValue }
|
|
8
11
|
setDirty(newValue) { this._dirty = newValue }
|
|
9
12
|
getDirty() { return this._dirty }
|
|
10
13
|
isLoaded() { return Boolean(this._loaded) }
|
|
@@ -18,6 +21,7 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
setLoaded(model) { this._loaded = model }
|
|
24
|
+
getPreloaded() { return this._preloaded }
|
|
21
25
|
setPreloaded(preloadedValue) { this._preloaded = preloadedValue }
|
|
22
26
|
getForeignKey() { return this.getRelationship().getForeignKey() }
|
|
23
27
|
getModel() { return this.model }
|
|
@@ -10,9 +10,7 @@ export default class VelociousDatabaseRecordBelongsToInstanceRelationship extend
|
|
|
10
10
|
return newInstance
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
getLoadedOrNull() {
|
|
14
|
-
return this._loaded
|
|
15
|
-
}
|
|
13
|
+
getLoadedOrNull() { return this._loaded }
|
|
16
14
|
|
|
17
15
|
async load() {
|
|
18
16
|
const foreignKey = this.getForeignKey()
|
|
@@ -7,13 +7,29 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
build(data) {
|
|
10
|
+
// Spawn new model of the targeted class
|
|
10
11
|
const targetModelClass = this.getTargetModelClass()
|
|
11
12
|
const newInstance = new targetModelClass(data)
|
|
12
13
|
|
|
14
|
+
|
|
15
|
+
// Add it to the loaded models of this relationship
|
|
13
16
|
if (this._loaded === null) this._loaded = []
|
|
14
17
|
|
|
15
18
|
this._loaded.push(newInstance)
|
|
16
19
|
|
|
20
|
+
|
|
21
|
+
// Set loaded on the models inversed relationship
|
|
22
|
+
const inverseOf = this.getRelationship().getInverseOf()
|
|
23
|
+
|
|
24
|
+
if (inverseOf) {
|
|
25
|
+
const inverseInstanceRelationship = newInstance.getRelationshipByName(inverseOf)
|
|
26
|
+
|
|
27
|
+
inverseInstanceRelationship.setAutoSave(false)
|
|
28
|
+
inverseInstanceRelationship.setLoaded(this.getModel())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Return the new contructed model
|
|
17
33
|
return newInstance
|
|
18
34
|
}
|
|
19
35
|
|
|
@@ -38,13 +54,15 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
38
54
|
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
39
55
|
}
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
if (this._loaded === null && this.model.isNewRecord()) {
|
|
58
|
+
return []
|
|
59
|
+
}
|
|
43
60
|
|
|
44
|
-
getLoadedOrNull() {
|
|
45
61
|
return this._loaded
|
|
46
62
|
}
|
|
47
63
|
|
|
64
|
+
getLoadedOrNull() { return this._loaded }
|
|
65
|
+
|
|
48
66
|
addToLoaded(models) {
|
|
49
67
|
if (Array.isArray(models)) {
|
|
50
68
|
for (const model of models) {
|
|
@@ -53,6 +71,8 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
53
71
|
this._loaded.push(model)
|
|
54
72
|
}
|
|
55
73
|
} else {
|
|
74
|
+
if (this._loaded === null) this._loaded = []
|
|
75
|
+
|
|
56
76
|
this._loaded.push(models)
|
|
57
77
|
}
|
|
58
78
|
}
|
|
@@ -63,5 +83,13 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
63
83
|
this._loaded = models
|
|
64
84
|
}
|
|
65
85
|
|
|
66
|
-
|
|
86
|
+
setPreloaded(preloaded) {
|
|
87
|
+
if (preloaded && !Array.isArray(this._loaded)) {
|
|
88
|
+
throw new Error("Trying to set preloaded without a loaded value")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this._preloaded = preloaded
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getTargetModelClass() { return this.relationship.getTargetModelClass() }
|
|
67
95
|
}
|
|
@@ -39,9 +39,7 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
39
39
|
return this._loaded
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
getLoadedOrNull() {
|
|
43
|
-
return this._loaded
|
|
44
|
-
}
|
|
42
|
+
getLoadedOrNull() { return this._loaded }
|
|
45
43
|
|
|
46
44
|
setLoaded(model) {
|
|
47
45
|
if (Array.isArray(model)) throw new Error(`Argument given to setLoaded was an array: ${typeof model}`)
|
|
@@ -49,5 +47,5 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
49
47
|
this._loaded = model
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
getTargetModelClass
|
|
50
|
+
getTargetModelClass() { return this.relationship.getTargetModelClass() }
|
|
53
51
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
2
2
|
|
|
3
3
|
export default class VelociousDatabaseRecordBaseRelationship {
|
|
4
|
-
constructor({className, configuration, dependent, foreignKey, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) {
|
|
4
|
+
constructor({className, configuration, dependent, foreignKey, inverseOf, klass, modelClass, primaryKey = "id", relationshipName, through, type, ...restArgs}) {
|
|
5
5
|
restArgsError(restArgs)
|
|
6
6
|
|
|
7
7
|
if (!modelClass) throw new Error(`'modelClass' wasn't given for ${relationshipName}`)
|
|
@@ -11,6 +11,7 @@ export default class VelociousDatabaseRecordBaseRelationship {
|
|
|
11
11
|
this.configuration = configuration
|
|
12
12
|
this._dependent = dependent
|
|
13
13
|
this.foreignKey = foreignKey
|
|
14
|
+
this._inverseOf
|
|
14
15
|
this.klass = klass
|
|
15
16
|
this.modelClass = modelClass
|
|
16
17
|
this._primaryKey = primaryKey
|
|
@@ -9,4 +9,28 @@ export default class VelociousDatabaseRecordBelongsToRelationship extends BaseRe
|
|
|
9
9
|
|
|
10
10
|
return this.foreignKey
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
getInverseOf() {
|
|
14
|
+
if (!this._inverseOf && !this._autoGenerateInverseOfAttempted) {
|
|
15
|
+
this._autoGenerateInverseOfAttempted = true
|
|
16
|
+
|
|
17
|
+
// Only make auto-inverse-of if the relationships name matches the target model class's name
|
|
18
|
+
const targetClassSimpleName = `${this.getRelationshipName().substring(0, 1).toUpperCase()}${this.getRelationshipName().substring(1, this.getRelationshipName().length)}`
|
|
19
|
+
|
|
20
|
+
if (targetClassSimpleName == this.getTargetModelClass().name) {
|
|
21
|
+
// Only make auto-inverse-of if the expected relationship exist in a has-one or has-many form
|
|
22
|
+
const targetClassRelationshipNames = this.getTargetModelClass().getRelationshipNames()
|
|
23
|
+
const autoGeneratedHasOneInverseOfName = `${this.modelClass.name.substring(0, 1).toLowerCase()}${this.modelClass.name.substring(1, this.modelClass.name.length)}`
|
|
24
|
+
const autoGeneratedHasManyInverseOfName = inflection.pluralize(autoGeneratedHasOneInverseOfName)
|
|
25
|
+
|
|
26
|
+
if (targetClassRelationshipNames.includes(autoGeneratedHasOneInverseOfName)) {
|
|
27
|
+
this._inverseOf = autoGeneratedHasOneInverseOfName
|
|
28
|
+
} else if (targetClassRelationshipNames.includes(autoGeneratedHasManyInverseOfName)) {
|
|
29
|
+
this._inverseOf = autoGeneratedHasManyInverseOfName
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return this._inverseOf
|
|
35
|
+
}
|
|
12
36
|
}
|
|
@@ -9,4 +9,25 @@ export default class VelociousDatabaseRecordHasManyRelationship extends BaseRela
|
|
|
9
9
|
|
|
10
10
|
return this.foreignKey
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
getInverseOf() {
|
|
14
|
+
if (!this._inverseOf && !this._autoGenerateInverseOfAttempted) {
|
|
15
|
+
this._autoGenerateInverseOfAttempted = true
|
|
16
|
+
|
|
17
|
+
// Only make auto-inverse-of if the relationships name matches the target model class's name
|
|
18
|
+
const targetClassSimpleName = inflection.singularize(`${this.getRelationshipName().substring(0, 1).toUpperCase()}${this.getRelationshipName().substring(1, this.getRelationshipName().length)}`)
|
|
19
|
+
|
|
20
|
+
if (targetClassSimpleName == this.getTargetModelClass().name) {
|
|
21
|
+
// Only make auto-inverse-of if the expected relationship exist in a has-one or has-many form
|
|
22
|
+
const targetClassRelationshipNames = this.getTargetModelClass().getRelationshipNames()
|
|
23
|
+
const autoGeneratedBelongsToInverseOfName = `${this.modelClass.name.substring(0, 1).toLowerCase()}${this.modelClass.name.substring(1, this.modelClass.name.length)}`
|
|
24
|
+
|
|
25
|
+
if (targetClassRelationshipNames.includes(autoGeneratedBelongsToInverseOfName)) {
|
|
26
|
+
this._inverseOf = autoGeneratedBelongsToInverseOfName
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this._inverseOf
|
|
32
|
+
}
|
|
12
33
|
}
|
|
@@ -9,4 +9,25 @@ export default class VelociousDatabaseRecordHasOneRelationship extends BaseRelat
|
|
|
9
9
|
|
|
10
10
|
return this.foreignKey
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
getInverseOf() {
|
|
14
|
+
if (!this._inverseOf && !this._autoGenerateInverseOfAttempted) {
|
|
15
|
+
this._autoGenerateInverseOfAttempted = true
|
|
16
|
+
|
|
17
|
+
// Only make auto-inverse-of if the relationships name matches the target model class's name
|
|
18
|
+
const targetClassSimpleName = `${this.getRelationshipName().substring(0, 1).toUpperCase()}${this.getRelationshipName().substring(1, this.getRelationshipName().length)}`
|
|
19
|
+
|
|
20
|
+
if (targetClassSimpleName == this.getTargetModelClass().name) {
|
|
21
|
+
// Only make auto-inverse-of if the expected relationship exist in a has-one or has-many form
|
|
22
|
+
const targetClassRelationshipNames = this.getTargetModelClass().getRelationshipNames()
|
|
23
|
+
const autoGeneratedBelongsToInverseOfName = `${this.modelClass.name.substring(0, 1).toLowerCase()}${this.modelClass.name.substring(1, this.modelClass.name.length)}`
|
|
24
|
+
|
|
25
|
+
if (targetClassRelationshipNames.includes(autoGeneratedBelongsToInverseOfName)) {
|
|
26
|
+
this._inverseOf = autoGeneratedBelongsToInverseOfName
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this._inverseOf
|
|
32
|
+
}
|
|
12
33
|
}
|