velocious 1.0.65 → 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/README.md +25 -0
- package/package.json +1 -1
- package/run-tests.sh +1 -1
- package/spec/database/record/create-spec.js +42 -1
- package/spec/database/record/instance-relationships/belongs-to-relationship-spec.js +21 -0
- package/spec/database/record/instance-relationships/has-many-relationship-spec.js +24 -0
- package/spec/database/record/instance-relationships/has-one-relationship-spec.js +24 -0
- package/spec/database/record/preloader/belongs-to-spec.js +15 -0
- package/spec/database/record/preloader/has-many-spec.js +29 -0
- package/spec/database/record/preloader/has-one-spec.js +15 -0
- package/spec/database/record/query-spec.js +24 -0
- package/spec/dummy/src/database/migrations/20230728075328-create-projects.js +3 -2
- package/spec/dummy/src/database/migrations/20250912183605-create-users.js +1 -0
- package/spec/dummy/src/models/account.js +8 -0
- package/spec/dummy/src/models/project.js +1 -0
- package/spec/dummy/src/models/user.js +3 -0
- package/src/database/initializer-from-require-context.js +2 -0
- package/src/database/query/preloader/belongs-to.js +6 -10
- package/src/database/query/preloader/has-many.js +20 -14
- package/src/database/query/preloader/has-one.js +14 -13
- package/src/database/query/where-hash.js +25 -10
- package/src/database/record/index.js +91 -33
- package/src/database/record/instance-relationships/base.js +9 -12
- package/src/database/record/instance-relationships/belongs-to.js +11 -6
- package/src/database/record/instance-relationships/has-many.js +53 -5
- package/src/database/record/instance-relationships/has-one.js +18 -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/src/http-server/worker-handler/index.js +2 -2
- package/src/testing/test-runner.js +6 -3
package/README.md
CHANGED
|
@@ -37,6 +37,31 @@ Task.validates("name", {presence: true, uniqueness: true})
|
|
|
37
37
|
export default Task
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## Preloading relationships
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const tasks = await Task.preload({project: {translations: true}}).toArray()
|
|
44
|
+
const projectNames = tasks.map((task) => task.project().name())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Load a relationship after init
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
const task = await Task.find(5)
|
|
51
|
+
|
|
52
|
+
await task.loadProject()
|
|
53
|
+
|
|
54
|
+
const projects = task.project()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
const project = await Project.find(4)
|
|
59
|
+
|
|
60
|
+
await project.loadTasks()
|
|
61
|
+
|
|
62
|
+
const tasks = project.tasks().loaded()
|
|
63
|
+
```
|
|
64
|
+
|
|
40
65
|
# Migrations
|
|
41
66
|
|
|
42
67
|
Make a new migration from a template like this:
|
package/package.json
CHANGED
package/run-tests.sh
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"})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import Task from "../../../dummy/src/models/task.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - instance relationships - belongs to relationship", () => {
|
|
6
|
+
it("loads a relationship", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create()
|
|
9
|
+
const task = await Task.create({name: "Test task", project})
|
|
10
|
+
const foundTask = await Task.find(task.id())
|
|
11
|
+
const projectInstanceRelationship = foundTask.getRelationshipByName("project")
|
|
12
|
+
|
|
13
|
+
expect(projectInstanceRelationship.isLoaded()).toBeFalse()
|
|
14
|
+
|
|
15
|
+
await foundTask.loadProject()
|
|
16
|
+
|
|
17
|
+
expect(projectInstanceRelationship.isLoaded()).toBeTrue()
|
|
18
|
+
expect(foundTask.project().id()).toEqual(project.id())
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import Task from "../../../dummy/src/models/task.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - instance relationships - has many relationship", () => {
|
|
6
|
+
it("loads a relationship", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create()
|
|
9
|
+
const task = await Task.create({name: "Test task", project})
|
|
10
|
+
const foundProject = await Project.find(project.id())
|
|
11
|
+
const tasksInstanceRelationship = foundProject.getRelationshipByName("tasks")
|
|
12
|
+
|
|
13
|
+
expect(tasksInstanceRelationship.isLoaded()).toBeFalse()
|
|
14
|
+
|
|
15
|
+
await foundProject.loadTasks()
|
|
16
|
+
|
|
17
|
+
expect(tasksInstanceRelationship.isLoaded()).toBeTrue()
|
|
18
|
+
|
|
19
|
+
const taskIDs = foundProject.tasks().loaded().map((task) => task.id())
|
|
20
|
+
|
|
21
|
+
expect(taskIDs).toEqual([task.id()])
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import ProjectDetail from "../../../dummy/src/models/project-detail.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - instance relationships - has one relationship", () => {
|
|
6
|
+
it("loads a relationship", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create()
|
|
9
|
+
const projectDetail = await ProjectDetail.create({note: "Test project", project})
|
|
10
|
+
const foundProject = await Project.find(project.id())
|
|
11
|
+
const projectDetailInstanceRelationship = foundProject.getRelationshipByName("projectDetail")
|
|
12
|
+
|
|
13
|
+
expect(projectDetailInstanceRelationship.isLoaded()).toBeFalse()
|
|
14
|
+
|
|
15
|
+
await foundProject.loadProjectDetail()
|
|
16
|
+
|
|
17
|
+
expect(projectDetailInstanceRelationship.isLoaded()).toBeTrue()
|
|
18
|
+
|
|
19
|
+
const projectsLoadedProjectDetail = foundProject.projectDetail()
|
|
20
|
+
|
|
21
|
+
expect(projectsLoadedProjectDetail.id()).toEqual(projectDetail.id())
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import User from "../../../dummy/src/models/user.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - preloader - belongs to", () => {
|
|
6
|
+
it("loads with custom primary key and foreign key", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create({creating_user_reference: "User-65"})
|
|
9
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
10
|
+
const foundProject = await Project.preload({creatingUser: true}).find(project.id())
|
|
11
|
+
|
|
12
|
+
expect(foundProject.creatingUser().id()).toEqual(user.id())
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import User from "../../../dummy/src/models/user.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - preloader - has many", () => {
|
|
6
|
+
it("loads with custom primary key and foreign key", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create({creating_user_reference: "User-65"})
|
|
9
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
10
|
+
const foundUser = await User.preload({createdProjects: true}).find(user.id())
|
|
11
|
+
const createdProjectsIDs = foundUser.createdProjects().loaded().map((createdProject) => createdProject.id())
|
|
12
|
+
|
|
13
|
+
expect(createdProjectsIDs).toEqual([project.id()])
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("preloads an empty array if nothing is found", async () => {
|
|
18
|
+
await Dummy.run(async () => {
|
|
19
|
+
// Differenre reference because nothing should be found as a kind of smoke test
|
|
20
|
+
await Project.create({creating_user_reference: "User-69"})
|
|
21
|
+
|
|
22
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
23
|
+
const foundUser = await User.preload({createdProjects: true}).find(user.id())
|
|
24
|
+
const createdProjectsIDs = foundUser.createdProjects().loaded().map((createdProject) => createdProject.id())
|
|
25
|
+
|
|
26
|
+
expect(createdProjectsIDs).toEqual([])
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Dummy from "../../../dummy/index.js"
|
|
2
|
+
import Project from "../../../dummy/src/models/project.js"
|
|
3
|
+
import User from "../../../dummy/src/models/user.js"
|
|
4
|
+
|
|
5
|
+
describe("Record - preloader - has one", () => {
|
|
6
|
+
it("loads with custom primary key and foreign key", async () => {
|
|
7
|
+
await Dummy.run(async () => {
|
|
8
|
+
const project = await Project.create({creating_user_reference: "User-65"})
|
|
9
|
+
const user = await User.create({email: "user@example.com", encrypted_password: "password", reference: "User-65"})
|
|
10
|
+
const foundUser = await User.preload({createdProject: true}).find(user.id())
|
|
11
|
+
|
|
12
|
+
expect(foundUser.createdProject().id()).toEqual([project.id()])
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
})
|
|
@@ -75,6 +75,30 @@ describe("Record - query", () => {
|
|
|
75
75
|
})
|
|
76
76
|
})
|
|
77
77
|
|
|
78
|
+
it("finds the record with joins and where hashes", async () => {
|
|
79
|
+
await Dummy.run(async () => {
|
|
80
|
+
const project1 = await Project.create({name: "Test project 1"})
|
|
81
|
+
const project2 = await Project.create({name: "Test project 2"})
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < 5; i++) {
|
|
84
|
+
await Task.create({name: `Task 1-${i}`, project: project1})
|
|
85
|
+
await Task.create({name: `Task 2-${i}`, project: project2})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const tasks = await Task
|
|
89
|
+
.joins({project: {translations: true}})
|
|
90
|
+
.where({tasks: {name: "Task 2-2"}, project_translations: {name: "Test project 2"}})
|
|
91
|
+
.preload({project: {translations: true}})
|
|
92
|
+
.toArray()
|
|
93
|
+
|
|
94
|
+
const task = tasks[0]
|
|
95
|
+
|
|
96
|
+
expect(tasks.length).toEqual(1)
|
|
97
|
+
expect(task.name()).toEqual("Task 2-2")
|
|
98
|
+
expect(task.project().name()).toEqual("Test project 2")
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
|
|
78
102
|
it("counts the records", async () => {
|
|
79
103
|
await Dummy.run(async () => {
|
|
80
104
|
const taskIDs = []
|
|
@@ -2,8 +2,9 @@ import Migration from "../../../../../src/database/migration/index.js"
|
|
|
2
2
|
|
|
3
3
|
export default class CreateProjects extends Migration {
|
|
4
4
|
async change() {
|
|
5
|
-
await this.createTable("projects", (
|
|
6
|
-
|
|
5
|
+
await this.createTable("projects", (t) => {
|
|
6
|
+
t.string("creating_user_reference")
|
|
7
|
+
t.timestamps()
|
|
7
8
|
})
|
|
8
9
|
}
|
|
9
10
|
}
|
|
@@ -3,6 +3,7 @@ import DatabaseRecord from "../../../../src/database/record/index.js"
|
|
|
3
3
|
class Project extends DatabaseRecord {
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
Project.belongsTo("creatingUser", {className: "User", foreignKey: "creating_user_reference", primaryKey: "reference"})
|
|
6
7
|
Project.hasMany("tasks")
|
|
7
8
|
Project.hasOne("projectDetail")
|
|
8
9
|
Project.translates("name")
|
|
@@ -4,7 +4,10 @@ import UserModule from "../../../../src/database/record/user-module.js"
|
|
|
4
4
|
class User extends Record {
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
User.hasOne("createdProject", {className: "Project", foreignKey: "creating_user_reference", primaryKey: "reference"})
|
|
8
|
+
|
|
7
9
|
User.hasMany("authenticationTokens" , {dependent: "destroy"})
|
|
10
|
+
User.hasMany("createdProjects", {className: "Project", foreignKey: "creating_user_reference", primaryKey: "reference"})
|
|
8
11
|
|
|
9
12
|
const userModule = new UserModule({
|
|
10
13
|
secretKey: "02e383b7-aad1-437c-b1e1-17c0240ad851"
|
|
@@ -15,6 +15,8 @@ export default class VelociousDatabaseInitializerFromRequireContext {
|
|
|
15
15
|
|
|
16
16
|
const modelClass = modelClassImport.default
|
|
17
17
|
|
|
18
|
+
if (!modelClass) throw new Error(`Model wasn't exported from: ${fileName}`)
|
|
19
|
+
|
|
18
20
|
await modelClass.initializeRecord({configuration})
|
|
19
21
|
|
|
20
22
|
if (await modelClass.hasTranslationsTable()) {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as inflection from "inflection"
|
|
2
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
2
|
|
|
4
3
|
export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
@@ -11,17 +10,12 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
11
10
|
|
|
12
11
|
async run() {
|
|
13
12
|
const foreignKeyValues = []
|
|
14
|
-
const modelsById = {}
|
|
15
13
|
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
-
const foreignKeyCamelized = inflection.camelize(foreignKey, true)
|
|
17
|
-
const preloadCollections = {}
|
|
18
14
|
|
|
19
15
|
for (const model of this.models) {
|
|
20
|
-
const foreignKeyValue = model
|
|
16
|
+
const foreignKeyValue = model.readColumn(foreignKey)
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
foreignKeyValues.push(foreignKeyValue)
|
|
24
|
-
modelsById[model.id()] = model
|
|
18
|
+
if (!foreignKeyValues.includes(foreignKeyValue)) foreignKeyValues.push(foreignKeyValue)
|
|
25
19
|
}
|
|
26
20
|
|
|
27
21
|
const whereArgs = {}
|
|
@@ -34,12 +28,14 @@ export default class VelociousDatabaseQueryPreloaderBelongsTo {
|
|
|
34
28
|
const targetModelsById = {}
|
|
35
29
|
|
|
36
30
|
for (const targetModel of targetModels) {
|
|
37
|
-
|
|
31
|
+
const primaryKeyValue = targetModel.readColumn(this.relationship.getPrimaryKey())
|
|
32
|
+
|
|
33
|
+
targetModelsById[primaryKeyValue] = targetModel
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
// Set the target preloaded models on the given models
|
|
41
37
|
for (const model of this.models) {
|
|
42
|
-
const foreignKeyValue = model
|
|
38
|
+
const foreignKeyValue = model.readColumn(foreignKey)
|
|
43
39
|
const targetModel = targetModelsById[foreignKeyValue]
|
|
44
40
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
45
41
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as inflection from "inflection"
|
|
2
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
2
|
|
|
4
3
|
export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
@@ -10,43 +9,50 @@ export default class VelociousDatabaseQueryPreloaderHasMany {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
async run() {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const modelsPrimaryKeyValues = []
|
|
13
|
+
const modelsByPrimaryKeyValue = {}
|
|
15
14
|
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
-
const
|
|
15
|
+
const primaryKey = this.relationship.getPrimaryKey()
|
|
17
16
|
const preloadCollections = {}
|
|
18
17
|
|
|
19
18
|
for (const model of this.models) {
|
|
20
|
-
|
|
21
|
-
modelIds.push(model.id())
|
|
19
|
+
const primaryKeyValue = model.readColumn(primaryKey)
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
preloadCollections[primaryKeyValue] = []
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
if (!modelsPrimaryKeyValues.includes(primaryKeyValue)) modelsPrimaryKeyValues.push(primaryKeyValue)
|
|
24
|
+
if (!(primaryKeyValue in modelsByPrimaryKeyValue)) modelsByPrimaryKeyValue[primaryKeyValue] = []
|
|
25
|
+
|
|
26
|
+
modelsByPrimaryKeyValue[primaryKeyValue].push(model)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const whereArgs = {}
|
|
29
30
|
|
|
30
|
-
whereArgs[foreignKey] =
|
|
31
|
+
whereArgs[foreignKey] = modelsPrimaryKeyValues
|
|
31
32
|
|
|
32
33
|
// Load target models to be preloaded on the given models
|
|
33
34
|
const targetModels = await this.relationship.getTargetModelClass().where(whereArgs).toArray()
|
|
34
35
|
|
|
35
36
|
for (const targetModel of targetModels) {
|
|
36
|
-
const foreignKeyValue = targetModel
|
|
37
|
+
const foreignKeyValue = targetModel.readColumn(foreignKey)
|
|
37
38
|
|
|
38
39
|
preloadCollections[foreignKeyValue].push(targetModel)
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Set the target preloaded models on the given models
|
|
42
|
-
for (const
|
|
43
|
-
const preloadedCollection = preloadCollections[
|
|
43
|
+
for (const modelValue in preloadCollections) {
|
|
44
|
+
const preloadedCollection = preloadCollections[modelValue]
|
|
44
45
|
|
|
45
|
-
for (const model of
|
|
46
|
+
for (const model of modelsByPrimaryKeyValue[modelValue]) {
|
|
46
47
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
47
48
|
|
|
49
|
+
if (preloadedCollection.length == 0) {
|
|
50
|
+
modelRelationship.setLoaded([])
|
|
51
|
+
} else {
|
|
52
|
+
modelRelationship.addToLoaded(preloadedCollection)
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
modelRelationship.setPreloaded(true)
|
|
49
|
-
modelRelationship.addToLoaded(preloadedCollection)
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as inflection from "inflection"
|
|
2
1
|
import restArgsError from "../../../utils/rest-args-error.js"
|
|
3
2
|
|
|
4
3
|
export default class VelociousDatabaseQueryPreloaderHasOne {
|
|
@@ -10,39 +9,41 @@ export default class VelociousDatabaseQueryPreloaderHasOne {
|
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
async run() {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
12
|
+
const modelsPrimaryKeyValues = []
|
|
13
|
+
const modelsByPrimaryKeyValue = {}
|
|
15
14
|
const foreignKey = this.relationship.getForeignKey()
|
|
16
|
-
const
|
|
15
|
+
const primaryKey = this.relationship.getPrimaryKey()
|
|
17
16
|
const preloadCollections = {}
|
|
18
17
|
|
|
19
18
|
for (const model of this.models) {
|
|
20
|
-
|
|
21
|
-
modelIds.push(model.id())
|
|
19
|
+
const primaryKeyValue = model.readColumn(primaryKey)
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
preloadCollections[primaryKeyValue] = null
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
if (!modelsPrimaryKeyValues.includes(primaryKeyValue)) modelsPrimaryKeyValues.push(primaryKeyValue)
|
|
24
|
+
if (!(primaryKeyValue in modelsByPrimaryKeyValue)) modelsByPrimaryKeyValue[primaryKeyValue] = []
|
|
25
|
+
|
|
26
|
+
modelsByPrimaryKeyValue[primaryKeyValue].push(model)
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
const whereArgs = {}
|
|
29
30
|
|
|
30
|
-
whereArgs[foreignKey] =
|
|
31
|
+
whereArgs[foreignKey] = modelsPrimaryKeyValues
|
|
31
32
|
|
|
32
33
|
// Load target models to be preloaded on the given models
|
|
33
34
|
const targetModels = await this.relationship.getTargetModelClass().where(whereArgs).toArray()
|
|
34
35
|
|
|
35
36
|
for (const targetModel of targetModels) {
|
|
36
|
-
const foreignKeyValue = targetModel
|
|
37
|
+
const foreignKeyValue = targetModel.readColumn(foreignKey)
|
|
37
38
|
|
|
38
39
|
preloadCollections[foreignKeyValue] = targetModel
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// Set the target preloaded models on the given models
|
|
42
|
-
for (const
|
|
43
|
-
const preloadedModel = preloadCollections[
|
|
43
|
+
for (const modelValue in preloadCollections) {
|
|
44
|
+
const preloadedModel = preloadCollections[modelValue]
|
|
44
45
|
|
|
45
|
-
for (const model of
|
|
46
|
+
for (const model of modelsByPrimaryKeyValue[modelValue]) {
|
|
46
47
|
const modelRelationship = model.getRelationshipByName(this.relationship.getRelationshipName())
|
|
47
48
|
|
|
48
49
|
modelRelationship.setPreloaded(true)
|
|
@@ -8,28 +8,43 @@ export default class VelociousDatabaseQueryWhereHash extends WhereBase {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
toSql() {
|
|
11
|
-
const options = this.getOptions()
|
|
12
11
|
let sql = "("
|
|
12
|
+
|
|
13
|
+
sql += this._whereSQLFromHash(this.hash)
|
|
14
|
+
sql += ")"
|
|
15
|
+
|
|
16
|
+
return sql
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_whereSQLFromHash(hash, tableName) {
|
|
20
|
+
const options = this.getOptions()
|
|
21
|
+
let sql = ""
|
|
13
22
|
let index = 0
|
|
14
23
|
|
|
15
|
-
for (const whereKey in
|
|
16
|
-
const whereValue =
|
|
24
|
+
for (const whereKey in hash) {
|
|
25
|
+
const whereValue = hash[whereKey]
|
|
17
26
|
|
|
18
27
|
if (index > 0) sql += " AND "
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (Array.isArray(whereValue)) {
|
|
23
|
-
sql += ` IN (${whereValue.map((value) => options.quote(value)).join(", ")})`
|
|
29
|
+
if (!Array.isArray(whereValue) && typeof whereValue == "object") {
|
|
30
|
+
sql += this._whereSQLFromHash(whereValue, whereKey)
|
|
24
31
|
} else {
|
|
25
|
-
|
|
32
|
+
if (tableName) {
|
|
33
|
+
sql += `${options.quoteTableName(tableName)}.`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
sql += `${options.quoteColumnName(whereKey)}`
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(whereValue)) {
|
|
39
|
+
sql += ` IN (${whereValue.map((value) => options.quote(value)).join(", ")})`
|
|
40
|
+
} else {
|
|
41
|
+
sql += ` = ${options.quote(whereValue)}`
|
|
42
|
+
}
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
index++
|
|
29
46
|
}
|
|
30
47
|
|
|
31
|
-
sql += ")"
|
|
32
|
-
|
|
33
48
|
return sql
|
|
34
49
|
}
|
|
35
50
|
}
|
|
@@ -78,23 +78,40 @@ class VelociousDatabaseRecord {
|
|
|
78
78
|
if (actualData.type == "belongsTo") {
|
|
79
79
|
relationship = new BelongsToRelationship(actualData)
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
const setMethodName = `set${inflection.camelize(relationshipName)}`
|
|
83
|
-
|
|
84
|
-
this.prototype[relationshipName] = function () {
|
|
81
|
+
this.prototype[relationshipName] = function() {
|
|
85
82
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
86
83
|
|
|
87
84
|
return relationship.loaded()
|
|
88
85
|
}
|
|
89
86
|
|
|
90
|
-
this.prototype[
|
|
91
|
-
const
|
|
92
|
-
const record =
|
|
87
|
+
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(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
|
+
}
|
|
93
106
|
|
|
94
107
|
return record
|
|
95
108
|
}
|
|
96
109
|
|
|
97
|
-
this.prototype[
|
|
110
|
+
this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
111
|
+
await this.getRelationshipByName(relationshipName).load()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.prototype[`set${inflection.camelize(relationshipName)}`] = function(model) {
|
|
98
115
|
const relationship = this.getRelationshipByName(relationshipName)
|
|
99
116
|
|
|
100
117
|
relationship.setLoaded(model)
|
|
@@ -103,24 +120,39 @@ class VelociousDatabaseRecord {
|
|
|
103
120
|
} else if (actualData.type == "hasMany") {
|
|
104
121
|
relationship = new HasManyRelationship(actualData)
|
|
105
122
|
|
|
106
|
-
this.prototype[relationshipName] = function
|
|
123
|
+
this.prototype[relationshipName] = function() {
|
|
107
124
|
return this.getRelationshipByName(relationshipName)
|
|
108
125
|
}
|
|
109
|
-
} else if (actualData.type == "hasOne") {
|
|
110
|
-
const buildMethodName = `build${inflection.camelize(relationshipName)}`
|
|
111
126
|
|
|
127
|
+
this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
128
|
+
await this.getRelationshipByName(relationshipName).load()
|
|
129
|
+
}
|
|
130
|
+
} else if (actualData.type == "hasOne") {
|
|
112
131
|
relationship = new HasOneRelationship(actualData)
|
|
113
132
|
|
|
114
|
-
this.prototype[relationshipName] = function
|
|
133
|
+
this.prototype[relationshipName] = function() {
|
|
115
134
|
return this.getRelationshipByName(relationshipName).loaded()
|
|
116
135
|
}
|
|
117
136
|
|
|
118
|
-
this.prototype[
|
|
119
|
-
const
|
|
120
|
-
const record =
|
|
137
|
+
this.prototype[`build${inflection.camelize(relationshipName)}`] = function(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
|
+
}
|
|
121
149
|
|
|
122
150
|
return record
|
|
123
151
|
}
|
|
152
|
+
|
|
153
|
+
this.prototype[`load${inflection.camelize(relationshipName)}`] = async function() {
|
|
154
|
+
await this.getRelationshipByName(relationshipName).load()
|
|
155
|
+
}
|
|
124
156
|
} else {
|
|
125
157
|
throw new Error(`Unknown relationship type: ${actualData.type}`)
|
|
126
158
|
}
|
|
@@ -144,6 +176,10 @@ class VelociousDatabaseRecord {
|
|
|
144
176
|
return []
|
|
145
177
|
}
|
|
146
178
|
|
|
179
|
+
static getRelationshipNames() {
|
|
180
|
+
return this.getRelationships().map((relationship) => relationship.getRelationshipName())
|
|
181
|
+
}
|
|
182
|
+
|
|
147
183
|
getRelationshipByName(relationshipName) {
|
|
148
184
|
if (!this._instanceRelationships) this._instanceRelationships = {}
|
|
149
185
|
|
|
@@ -168,8 +204,8 @@ class VelociousDatabaseRecord {
|
|
|
168
204
|
return this._instanceRelationships[relationshipName]
|
|
169
205
|
}
|
|
170
206
|
|
|
171
|
-
static belongsTo(relationshipName) {
|
|
172
|
-
this._defineRelationship(relationshipName, {type: "belongsTo"})
|
|
207
|
+
static belongsTo(relationshipName, options) {
|
|
208
|
+
this._defineRelationship(relationshipName, Object.assign({type: "belongsTo"}, options))
|
|
173
209
|
}
|
|
174
210
|
|
|
175
211
|
static connection() {
|
|
@@ -219,11 +255,14 @@ class VelociousDatabaseRecord {
|
|
|
219
255
|
return this._getConfiguration().getTranslator()(`velocious.database.record.attributes.${modelNameKey}.${attributeName}`, {defaultValue: inflection.camelize(attributeName)})
|
|
220
256
|
}
|
|
221
257
|
|
|
258
|
+
static getDatabaseType() { return this._databaseType }
|
|
259
|
+
|
|
222
260
|
static async initializeRecord({configuration}) {
|
|
223
261
|
if (!configuration) throw new Error(`No configuration given for ${this.name}`)
|
|
224
262
|
|
|
225
263
|
this._configuration = configuration
|
|
226
264
|
this._configuration.registerModelClass(this)
|
|
265
|
+
this._databaseType = this.connection().getType()
|
|
227
266
|
|
|
228
267
|
this._table = await this.connection().getTableByName(this.tableName())
|
|
229
268
|
this._columns = await this._getTable().getColumns()
|
|
@@ -285,13 +324,13 @@ class VelociousDatabaseRecord {
|
|
|
285
324
|
const nameCamelized = inflection.camelize(name)
|
|
286
325
|
const setterMethodName = `set${nameCamelized}`
|
|
287
326
|
|
|
288
|
-
this.prototype[name] = function
|
|
327
|
+
this.prototype[name] = function() {
|
|
289
328
|
const locale = this._getConfiguration().getLocale()
|
|
290
329
|
|
|
291
330
|
return this._getTranslatedAttributeWithFallback(name, locale)
|
|
292
331
|
}
|
|
293
332
|
|
|
294
|
-
this.prototype[setterMethodName] = function
|
|
333
|
+
this.prototype[setterMethodName] = function(newValue) {
|
|
295
334
|
const locale = this._getConfiguration().getLocale()
|
|
296
335
|
|
|
297
336
|
return this._setTranslatedAttribute(name, locale, newValue)
|
|
@@ -302,11 +341,11 @@ class VelociousDatabaseRecord {
|
|
|
302
341
|
const getterMethodNameLocalized = `${name}${localeCamelized}`
|
|
303
342
|
const setterMethodNameLocalized = `${setterMethodName}${localeCamelized}`
|
|
304
343
|
|
|
305
|
-
this.prototype[getterMethodNameLocalized] = function
|
|
344
|
+
this.prototype[getterMethodNameLocalized] = function() {
|
|
306
345
|
return this._getTranslatedAttribute(name, locale)
|
|
307
346
|
}
|
|
308
347
|
|
|
309
|
-
this.prototype[setterMethodNameLocalized] = function
|
|
348
|
+
this.prototype[setterMethodNameLocalized] = function(newValue) {
|
|
310
349
|
return this._setTranslatedAttribute(name, locale, newValue)
|
|
311
350
|
}
|
|
312
351
|
}
|
|
@@ -425,6 +464,10 @@ class VelociousDatabaseRecord {
|
|
|
425
464
|
continue
|
|
426
465
|
}
|
|
427
466
|
|
|
467
|
+
if (instanceRelationship.getAutoSave() === false) {
|
|
468
|
+
continue
|
|
469
|
+
}
|
|
470
|
+
|
|
428
471
|
const model = instanceRelationship.loaded()
|
|
429
472
|
|
|
430
473
|
if (model?.isChanged()) {
|
|
@@ -433,6 +476,7 @@ class VelociousDatabaseRecord {
|
|
|
433
476
|
const foreignKey = instanceRelationship.getForeignKey()
|
|
434
477
|
|
|
435
478
|
this.setAttribute(foreignKey, model.id())
|
|
479
|
+
|
|
436
480
|
instanceRelationship.setPreloaded(true)
|
|
437
481
|
instanceRelationship.setDirty(false)
|
|
438
482
|
|
|
@@ -453,6 +497,10 @@ class VelociousDatabaseRecord {
|
|
|
453
497
|
continue
|
|
454
498
|
}
|
|
455
499
|
|
|
500
|
+
if (instanceRelationship.getAutoSave() === false) {
|
|
501
|
+
continue
|
|
502
|
+
}
|
|
503
|
+
|
|
456
504
|
let loaded
|
|
457
505
|
|
|
458
506
|
if (instanceRelationship.getType() == "hasOne") {
|
|
@@ -469,14 +517,16 @@ class VelociousDatabaseRecord {
|
|
|
469
517
|
|
|
470
518
|
let useRelationship = false
|
|
471
519
|
|
|
472
|
-
|
|
473
|
-
const
|
|
520
|
+
if (loaded) {
|
|
521
|
+
for (const model of loaded) {
|
|
522
|
+
const foreignKey = instanceRelationship.getForeignKey()
|
|
474
523
|
|
|
475
|
-
|
|
524
|
+
model.setAttribute(foreignKey, this.id())
|
|
476
525
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
526
|
+
if (model.isChanged()) {
|
|
527
|
+
useRelationship = true
|
|
528
|
+
continue
|
|
529
|
+
}
|
|
480
530
|
}
|
|
481
531
|
}
|
|
482
532
|
|
|
@@ -488,7 +538,7 @@ class VelociousDatabaseRecord {
|
|
|
488
538
|
|
|
489
539
|
async _autoSaveHasManyAndHasOneRelationships({isNewRecord}) {
|
|
490
540
|
for (const instanceRelationship of this._autoSaveHasManyAndHasOneRelationshipsToSave()) {
|
|
491
|
-
let loaded = instanceRelationship.
|
|
541
|
+
let loaded = instanceRelationship.getLoadedOrNull()
|
|
492
542
|
|
|
493
543
|
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
494
544
|
|
|
@@ -792,6 +842,10 @@ class VelociousDatabaseRecord {
|
|
|
792
842
|
const instanceRelationship = this._instanceRelationships[instanceRelationshipName]
|
|
793
843
|
let loaded = instanceRelationship._loaded
|
|
794
844
|
|
|
845
|
+
if (instanceRelationship.getAutoSave() === false) {
|
|
846
|
+
continue
|
|
847
|
+
}
|
|
848
|
+
|
|
795
849
|
if (!loaded) continue
|
|
796
850
|
if (!Array.isArray(loaded)) loaded = [loaded]
|
|
797
851
|
|
|
@@ -827,7 +881,7 @@ class VelociousDatabaseRecord {
|
|
|
827
881
|
readAttribute(attributeName) {
|
|
828
882
|
const columnName = this.constructor._attributeNameToColumnName[attributeName]
|
|
829
883
|
|
|
830
|
-
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName}`)
|
|
884
|
+
if (!columnName) throw new Error(`Couldn't figure out column name for attribute: ${attributeName} from these mappings: ${Object.keys(this.constructor._attributeNameToColumnName)}`)
|
|
831
885
|
|
|
832
886
|
return this.readColumn(columnName)
|
|
833
887
|
}
|
|
@@ -844,7 +898,7 @@ class VelociousDatabaseRecord {
|
|
|
844
898
|
throw new Error(`No such attribute or not selected ${this.constructor.name}#${attributeName}`)
|
|
845
899
|
}
|
|
846
900
|
|
|
847
|
-
if (column && this.constructor.
|
|
901
|
+
if (column && this.constructor.getDatabaseType() == "sqlite") {
|
|
848
902
|
if (column.getType() == "date" || column.getType() == "datetime") {
|
|
849
903
|
result = new Date(Date.parse(result))
|
|
850
904
|
}
|
|
@@ -903,6 +957,10 @@ class VelociousDatabaseRecord {
|
|
|
903
957
|
for (const relationship of this.constructor.getRelationships()) {
|
|
904
958
|
const instanceRelationship = this.getRelationshipByName(relationship.getRelationshipName())
|
|
905
959
|
|
|
960
|
+
if (instanceRelationship.getType() == "hasMany" && instanceRelationship.getLoadedOrNull() === null) {
|
|
961
|
+
instanceRelationship.setLoaded([])
|
|
962
|
+
}
|
|
963
|
+
|
|
906
964
|
instanceRelationship.setPreloaded(true)
|
|
907
965
|
}
|
|
908
966
|
}
|
|
@@ -929,9 +987,9 @@ class VelociousDatabaseRecord {
|
|
|
929
987
|
}
|
|
930
988
|
}
|
|
931
989
|
|
|
932
|
-
id
|
|
933
|
-
isPersisted
|
|
934
|
-
isNewRecord
|
|
990
|
+
id() { return this.readAttribute(this.constructor._columnNameToAttributeName[this.constructor.primaryKey()]) }
|
|
991
|
+
isPersisted() { return !this._isNewRecord }
|
|
992
|
+
isNewRecord() { return this._isNewRecord }
|
|
935
993
|
|
|
936
994
|
setIsNewRecord(newIsNewRecord) {
|
|
937
995
|
this._isNewRecord = newIsNewRecord
|
|
@@ -1,15 +1,16 @@
|
|
|
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
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
9
|
+
getAutoSave() { return this._autoSave }
|
|
10
|
+
setAutoSave(newAutoSaveValue) { this._autoSave = newAutoSaveValue }
|
|
11
|
+
setDirty(newValue) { this._dirty = newValue }
|
|
12
12
|
getDirty() { return this._dirty }
|
|
13
|
+
isLoaded() { return Boolean(this._loaded) }
|
|
13
14
|
|
|
14
15
|
loaded() {
|
|
15
16
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
@@ -19,15 +20,11 @@ export default class VelociousDatabaseRecordBaseInstanceRelationship {
|
|
|
19
20
|
return this._loaded
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
setLoaded(model) {
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
setPreloaded(preloadedValue) {
|
|
27
|
-
this._preloaded = preloadedValue
|
|
28
|
-
}
|
|
29
|
-
|
|
23
|
+
setLoaded(model) { this._loaded = model }
|
|
24
|
+
getPreloaded() { return this._preloaded }
|
|
25
|
+
setPreloaded(preloadedValue) { this._preloaded = preloadedValue }
|
|
30
26
|
getForeignKey() { return this.getRelationship().getForeignKey() }
|
|
27
|
+
getModel() { return this.model }
|
|
31
28
|
getPrimaryKey() { return this.getRelationship().getPrimaryKey() }
|
|
32
29
|
getRelationship() { return this.relationship }
|
|
33
30
|
getTargetModelClass() { return this.getRelationship().getTargetModelClass() }
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import BaseInstanceRelationship from "./base.js"
|
|
2
2
|
|
|
3
3
|
export default class VelociousDatabaseRecordBelongsToInstanceRelationship extends BaseInstanceRelationship {
|
|
4
|
-
constructor(args) {
|
|
5
|
-
super(args)
|
|
6
|
-
}
|
|
7
|
-
|
|
8
4
|
build(data) {
|
|
9
5
|
const targetModelClass = this.getTargetModelClass()
|
|
10
6
|
const newInstance = new targetModelClass(data)
|
|
@@ -14,7 +10,16 @@ export default class VelociousDatabaseRecordBelongsToInstanceRelationship extend
|
|
|
14
10
|
return newInstance
|
|
15
11
|
}
|
|
16
12
|
|
|
17
|
-
getLoadedOrNull() {
|
|
18
|
-
|
|
13
|
+
getLoadedOrNull() { return this._loaded }
|
|
14
|
+
|
|
15
|
+
async load() {
|
|
16
|
+
const foreignKey = this.getForeignKey()
|
|
17
|
+
const foreignModelID = this.getModel().readColumn(foreignKey)
|
|
18
|
+
const TargetModelClass = this.getTargetModelClass()
|
|
19
|
+
const foreignModel = await TargetModelClass.find(foreignModelID)
|
|
20
|
+
|
|
21
|
+
this.setLoaded(foreignModel)
|
|
22
|
+
this.setDirty(false)
|
|
23
|
+
this.setPreloaded(true)
|
|
19
24
|
}
|
|
20
25
|
}
|
|
@@ -3,36 +3,76 @@ import BaseInstanceRelationship from "./base.js"
|
|
|
3
3
|
export default class VelociousDatabaseRecordHasManyInstanceRelationship extends BaseInstanceRelationship {
|
|
4
4
|
constructor(args) {
|
|
5
5
|
super(args)
|
|
6
|
-
this._loaded =
|
|
6
|
+
this._loaded = null
|
|
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
|
|
16
|
+
if (this._loaded === null) this._loaded = []
|
|
17
|
+
|
|
13
18
|
this._loaded.push(newInstance)
|
|
14
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
|
|
15
33
|
return newInstance
|
|
16
34
|
}
|
|
17
35
|
|
|
36
|
+
async load() {
|
|
37
|
+
const foreignKey = this.getForeignKey()
|
|
38
|
+
const primaryKey = this.getPrimaryKey()
|
|
39
|
+
const primaryModelID = this.getModel().readColumn(primaryKey)
|
|
40
|
+
const TargetModelClass = this.getTargetModelClass()
|
|
41
|
+
const whereArgs = {}
|
|
42
|
+
|
|
43
|
+
whereArgs[foreignKey] = primaryModelID
|
|
44
|
+
|
|
45
|
+
const foreignModels = await TargetModelClass.where(whereArgs).toArray()
|
|
46
|
+
|
|
47
|
+
this.setLoaded(foreignModels)
|
|
48
|
+
this.setDirty(false)
|
|
49
|
+
this.setPreloaded(true)
|
|
50
|
+
}
|
|
51
|
+
|
|
18
52
|
loaded() {
|
|
19
53
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
20
54
|
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
21
55
|
}
|
|
22
56
|
|
|
23
|
-
|
|
24
|
-
|
|
57
|
+
if (this._loaded === null && this.model.isNewRecord()) {
|
|
58
|
+
return []
|
|
59
|
+
}
|
|
25
60
|
|
|
26
|
-
getLoadedOrNull() {
|
|
27
61
|
return this._loaded
|
|
28
62
|
}
|
|
29
63
|
|
|
64
|
+
getLoadedOrNull() { return this._loaded }
|
|
65
|
+
|
|
30
66
|
addToLoaded(models) {
|
|
31
67
|
if (Array.isArray(models)) {
|
|
32
68
|
for (const model of models) {
|
|
69
|
+
if (this._loaded === null) this._loaded = []
|
|
70
|
+
|
|
33
71
|
this._loaded.push(model)
|
|
34
72
|
}
|
|
35
73
|
} else {
|
|
74
|
+
if (this._loaded === null) this._loaded = []
|
|
75
|
+
|
|
36
76
|
this._loaded.push(models)
|
|
37
77
|
}
|
|
38
78
|
}
|
|
@@ -43,5 +83,13 @@ export default class VelociousDatabaseRecordHasManyInstanceRelationship extends
|
|
|
43
83
|
this._loaded = models
|
|
44
84
|
}
|
|
45
85
|
|
|
46
|
-
|
|
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() }
|
|
47
95
|
}
|
|
@@ -15,6 +15,22 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
15
15
|
return newInstance
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
async load() {
|
|
19
|
+
const foreignKey = this.getForeignKey()
|
|
20
|
+
const primaryKey = this.getPrimaryKey()
|
|
21
|
+
const primaryModelID = this.getModel().readColumn(primaryKey)
|
|
22
|
+
const TargetModelClass = this.getTargetModelClass()
|
|
23
|
+
const whereArgs = {}
|
|
24
|
+
|
|
25
|
+
whereArgs[foreignKey] = primaryModelID
|
|
26
|
+
|
|
27
|
+
const foreignModel = await TargetModelClass.where(whereArgs).first()
|
|
28
|
+
|
|
29
|
+
this.setLoaded(foreignModel)
|
|
30
|
+
this.setDirty(false)
|
|
31
|
+
this.setPreloaded(true)
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
loaded() {
|
|
19
35
|
if (!this._preloaded && this.model.isPersisted()) {
|
|
20
36
|
throw new Error(`${this.model.constructor.name}#${this.relationship.getRelationshipName()} hasn't been preloaded`)
|
|
@@ -23,9 +39,7 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
23
39
|
return this._loaded
|
|
24
40
|
}
|
|
25
41
|
|
|
26
|
-
getLoadedOrNull() {
|
|
27
|
-
return this._loaded
|
|
28
|
-
}
|
|
42
|
+
getLoadedOrNull() { return this._loaded }
|
|
29
43
|
|
|
30
44
|
setLoaded(model) {
|
|
31
45
|
if (Array.isArray(model)) throw new Error(`Argument given to setLoaded was an array: ${typeof model}`)
|
|
@@ -33,5 +47,5 @@ export default class VelociousDatabaseRecordHasOneInstanceRelationship extends B
|
|
|
33
47
|
this._loaded = model
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
getTargetModelClass
|
|
50
|
+
getTargetModelClass() { return this.relationship.getTargetModelClass() }
|
|
37
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
|
}
|
|
@@ -74,11 +74,11 @@ export default class VelociousHttpServerWorker {
|
|
|
74
74
|
|
|
75
75
|
const {clientCount, output} = digs(data, "clientCount", "output")
|
|
76
76
|
|
|
77
|
-
this.clients[clientCount]
|
|
77
|
+
this.clients[clientCount]?.send(output)
|
|
78
78
|
} else if (command == "clientClose") {
|
|
79
79
|
const {clientCount} = digs(data, "clientCount")
|
|
80
80
|
|
|
81
|
-
this.clients[clientCount]
|
|
81
|
+
this.clients[clientCount]?.close()
|
|
82
82
|
} else {
|
|
83
83
|
throw new Error(`Unknown command: ${command}`)
|
|
84
84
|
}
|
|
@@ -138,8 +138,11 @@ export default class TestRunner {
|
|
|
138
138
|
|
|
139
139
|
if (this._onlyFocussed && !testArgs.focus) continue
|
|
140
140
|
|
|
141
|
-
if (testArgs.type == "request") {
|
|
141
|
+
if (testArgs.type == "model" || testArgs.type == "request") {
|
|
142
142
|
testArgs.application = await this.application()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (testArgs.type == "request") {
|
|
143
146
|
testArgs.client = await this.requestClient()
|
|
144
147
|
}
|
|
145
148
|
|
|
@@ -147,7 +150,7 @@ export default class TestRunner {
|
|
|
147
150
|
|
|
148
151
|
try {
|
|
149
152
|
for (const beforeEachData of newBeforeEaches) {
|
|
150
|
-
await beforeEachData.callback({testArgs, testData})
|
|
153
|
+
await beforeEachData.callback({configuration: this.configuration, testArgs, testData})
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
await testData.function(testArgs)
|
|
@@ -167,7 +170,7 @@ export default class TestRunner {
|
|
|
167
170
|
}
|
|
168
171
|
} finally {
|
|
169
172
|
for (const afterEachData of newAfterEaches) {
|
|
170
|
-
await afterEachData.callback({testArgs, testData})
|
|
173
|
+
await afterEachData.callback({configuration: this.configuration, testArgs, testData})
|
|
171
174
|
}
|
|
172
175
|
}
|
|
173
176
|
}
|