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 CHANGED
@@ -3,7 +3,7 @@
3
3
  "velocious": "bin/velocious.js"
4
4
  },
5
5
  "name": "velocious",
6
- "version": "1.0.66",
6
+ "version": "1.0.67",
7
7
  "main": "index.js",
8
8
  "scripts": {
9
9
  "test": "VELOCIOUS_TEST_DIR=../ cd spec/dummy && npx velocious test",
@@ -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
- // It saves a project note
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 relationship = this.getRelationshipByName(relationshipName)
89
- const record = relationship.build(attributes)
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 relationship = this.getRelationshipByName(relationshipName)
123
- const record = relationship.build(attributes)
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._loaded
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
- return this._loaded
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
- getTargetModelClass = () => this.relationship.getTargetModelClass()
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 = () => this.relationship.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
  }