masterrecord 0.2.33 → 0.2.34
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/context.js +18 -2
- package/docs/belongsTo-relationships.md +190 -0
- package/package.json +1 -1
package/context.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Version 0.0.
|
|
1
|
+
// Version 0.0.16
|
|
2
2
|
|
|
3
3
|
var modelBuilder = require('./Entity/entityModelBuilder');
|
|
4
4
|
var query = require('masterrecord/QueryLanguage/queryMethods');
|
|
@@ -146,7 +146,23 @@ class context {
|
|
|
146
146
|
const envFileB = path.join(directFolder, `${envType}.json`);
|
|
147
147
|
const picked = fs.existsSync(envFileA) ? envFileA : (fs.existsSync(envFileB) ? envFileB : null);
|
|
148
148
|
if(picked){
|
|
149
|
-
|
|
149
|
+
// Smart root folder detection for plugin paths
|
|
150
|
+
// If the env file is in a bb-plugins/<plugin-name>/config/environments/ structure,
|
|
151
|
+
// we should set rootFolder to the project root, not the plugin's config folder
|
|
152
|
+
let detectedRoot = path.dirname(path.dirname(picked));
|
|
153
|
+
|
|
154
|
+
// Check if we're in a bb-plugins structure
|
|
155
|
+
const pickedParts = picked.split(path.sep);
|
|
156
|
+
const pluginsIndex = pickedParts.findIndex(part => part === 'bb-plugins');
|
|
157
|
+
|
|
158
|
+
if(pluginsIndex !== -1 && pluginsIndex + 3 < pickedParts.length) {
|
|
159
|
+
// We're in bb-plugins/<plugin-name>/config/environments/...
|
|
160
|
+
// Set rootFolder to the project root (parent of bb-plugins)
|
|
161
|
+
const projectRootParts = pickedParts.slice(0, pluginsIndex);
|
|
162
|
+
detectedRoot = projectRootParts.join(path.sep) || path.sep;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
file = { file: picked, rootFolder: detectedRoot };
|
|
150
166
|
}
|
|
151
167
|
}
|
|
152
168
|
if(!file){
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# belongsTo Relationships in MasterRecord
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
When you define a `belongsTo` relationship in MasterRecord, the ORM creates **two separate properties** on your model instances:
|
|
6
|
+
|
|
7
|
+
1. **The foreign key field** (e.g., `document_id`) - the actual database column value
|
|
8
|
+
2. **The relationship property** (e.g., `Document`) - for accessing/setting the related object or ID
|
|
9
|
+
|
|
10
|
+
## How belongsTo Works Internally
|
|
11
|
+
|
|
12
|
+
### Entity Definition
|
|
13
|
+
|
|
14
|
+
```javascript
|
|
15
|
+
// DocumentChunk entity
|
|
16
|
+
DocumentChunk(db) {
|
|
17
|
+
db.integer("id").primary().notNull().autoIncrement();
|
|
18
|
+
db.string("content");
|
|
19
|
+
db.belongsTo("Document", "document_id"); // Creates both Document and document_id properties
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The `belongsTo("Document", "document_id")` call:
|
|
24
|
+
- **Does NOT** create a separate `document_id` field definition
|
|
25
|
+
- Tells MasterRecord that `document_id` is a foreign key to the `Document` entity
|
|
26
|
+
- Creates a relationship property named `Document`
|
|
27
|
+
|
|
28
|
+
### Model Instance Properties
|
|
29
|
+
|
|
30
|
+
When you query records or create new instances, **both properties are available**:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
const chunk = context.DocumentChunk.where("r => r.id == 1").single();
|
|
34
|
+
|
|
35
|
+
// Both of these work:
|
|
36
|
+
console.log(chunk.document_id); // Accesses the foreign key value (e.g., 5)
|
|
37
|
+
console.log(chunk.Document); // Accesses the relationship (object or ID)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage Patterns
|
|
41
|
+
|
|
42
|
+
### Pattern 1: INSERT Operations (Setting Foreign Keys)
|
|
43
|
+
|
|
44
|
+
When **creating new records**, use the **relationship property name**:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
const chunk = new DocumentChunk();
|
|
48
|
+
chunk.content = "Sample content";
|
|
49
|
+
chunk.Document = documentId; // ✅ CORRECT - Use relationship property for INSERT
|
|
50
|
+
|
|
51
|
+
context.DocumentChunk.add(chunk);
|
|
52
|
+
context.saveChanges();
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Why?** The `belongsTo` setter (entityTrackerModel.js:98-105) triggers dirty field tracking and marks the model as modified when you set the relationship property.
|
|
56
|
+
|
|
57
|
+
### Pattern 2: READ Operations (Accessing Foreign Keys)
|
|
58
|
+
|
|
59
|
+
When **reading or filtering records**, use the **foreign key field name**:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
// ✅ CORRECT - Use foreign key for filtering
|
|
63
|
+
const chunks = context.DocumentChunk
|
|
64
|
+
.where(`r => r.document_id == ${docId}`)
|
|
65
|
+
.toList();
|
|
66
|
+
|
|
67
|
+
// ✅ CORRECT - Use foreign key in JavaScript filters
|
|
68
|
+
const filtered = allChunks.filter(c => documentIds.includes(c.document_id));
|
|
69
|
+
|
|
70
|
+
// ✅ CORRECT - Access foreign key value directly
|
|
71
|
+
if (chunk.document_id === targetId) {
|
|
72
|
+
// ...
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Why?** When records are loaded from the database, the raw column data includes `document_id`. The `build` method (entityTrackerModel.js:21-59) creates getters/setters for all non-relationship fields from the database result.
|
|
77
|
+
|
|
78
|
+
### Pattern 3: UPDATE Operations
|
|
79
|
+
|
|
80
|
+
For updates, you can use either property:
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// Using relationship property (preferred for consistency)
|
|
84
|
+
chunk.Document = newDocumentId;
|
|
85
|
+
|
|
86
|
+
// Using foreign key directly (also works)
|
|
87
|
+
chunk.document_id = newDocumentId;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Both will work because:
|
|
91
|
+
- Setting `chunk.Document` triggers the relationship setter (line 98-105)
|
|
92
|
+
- Setting `chunk.document_id` directly modifies the underlying value
|
|
93
|
+
|
|
94
|
+
## Code References
|
|
95
|
+
|
|
96
|
+
The behavior is implemented in `/Entity/entityTrackerModel.js`:
|
|
97
|
+
|
|
98
|
+
### Creating Non-Relationship Properties (lines 21-59)
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
for (const [modelField, modelFieldValue] of modelFields) {
|
|
102
|
+
if(!$that._isRelationship(currentEntity[modelField])){
|
|
103
|
+
// Creates getter/setter for document_id (from database column)
|
|
104
|
+
modelClass["__proto__"]["_" + modelField] = modelFieldValue;
|
|
105
|
+
Object.defineProperty(modelClass, modelField, {
|
|
106
|
+
set: function(value) {
|
|
107
|
+
modelClass.__state = "modified";
|
|
108
|
+
modelClass.__dirtyFields.push(modelField);
|
|
109
|
+
this["__proto__"]["_" + modelField] = value;
|
|
110
|
+
},
|
|
111
|
+
get: function() {
|
|
112
|
+
return this["__proto__"]["_" + modelField];
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
This loop processes fields from the database result (like `document_id`) and makes them accessible as properties.
|
|
120
|
+
|
|
121
|
+
### Creating Relationship Properties (lines 89-149)
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
if($that._isRelationship(currentEntity[entityField])){
|
|
125
|
+
// Creates getter/setter for Document (relationship property)
|
|
126
|
+
Object.defineProperty(modelClass, entityField, {
|
|
127
|
+
set: function(value) {
|
|
128
|
+
if(typeof value === "string" || typeof value === "number" ||
|
|
129
|
+
typeof value === "boolean" || typeof value === "bigint") {
|
|
130
|
+
modelClass.__state = "modified";
|
|
131
|
+
modelClass.__dirtyFields.push(entityField);
|
|
132
|
+
modelClass.__context.__track(modelClass);
|
|
133
|
+
}
|
|
134
|
+
this["__proto__"]["_" + entityField] = value;
|
|
135
|
+
},
|
|
136
|
+
get: function() {
|
|
137
|
+
// Complex getter logic for lazy loading, etc.
|
|
138
|
+
return this["__proto__"]["_" + entityField];
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if(currentEntity[entityField].relationshipType === "belongsTo"){
|
|
143
|
+
// Initialize relationship value from database result
|
|
144
|
+
if(currentModel[entityField]){
|
|
145
|
+
modelClass[entityField] = currentModel[entityField];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This creates the `Document` property for relationship access.
|
|
152
|
+
|
|
153
|
+
## Real-World Example from bookbag-ce
|
|
154
|
+
|
|
155
|
+
In `bookbag-ce`, the `UserChat` entity demonstrates this pattern:
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// Entity definition
|
|
159
|
+
UserChat(db) {
|
|
160
|
+
db.integer("id").primary().notNull().autoIncrement();
|
|
161
|
+
db.belongsTo("Chat", "chat_id"); // Only defines relationship, not chat_id field
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Controller usage (chatController.js:66)
|
|
165
|
+
const messages = context.UserChat
|
|
166
|
+
.where(`r => r.chat_id == ${chatId}`) // ✅ Uses foreign key for filtering
|
|
167
|
+
.toList();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Even though `chat_id` is never explicitly defined as a field, it's accessible because:
|
|
171
|
+
1. The database returns `chat_id` as a column
|
|
172
|
+
2. `entityTrackerModel.build()` creates properties for all columns from the result
|
|
173
|
+
3. The `belongsTo` relationship doesn't prevent the foreign key from being accessible
|
|
174
|
+
|
|
175
|
+
## Summary
|
|
176
|
+
|
|
177
|
+
| Operation | Property to Use | Example |
|
|
178
|
+
|-----------|----------------|---------|
|
|
179
|
+
| **INSERT** (setting FK) | Relationship property | `chunk.Document = docId` |
|
|
180
|
+
| **READ** (accessing FK) | Foreign key field | `chunk.document_id` |
|
|
181
|
+
| **FILTER** (where clauses) | Foreign key field | `r.document_id == 5` |
|
|
182
|
+
| **UPDATE** (changing FK) | Either (prefer relationship) | `chunk.Document = newId` |
|
|
183
|
+
|
|
184
|
+
## Key Takeaways
|
|
185
|
+
|
|
186
|
+
1. **belongsTo creates TWO properties**, not one
|
|
187
|
+
2. The **foreign key column** (`document_id`) is automatically available from database results
|
|
188
|
+
3. The **relationship property** (`Document`) is created by the ORM for setting relationships
|
|
189
|
+
4. **Use the relationship property for INSERT**, foreign key field for READ/FILTER
|
|
190
|
+
5. Both properties reference the same underlying foreign key value in the database
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "masterrecord",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.34",
|
|
4
4
|
"description": "An Object-relational mapping for the Master framework. Master Record connects classes to relational database tables to establish a database with almost zero-configuration ",
|
|
5
5
|
"main": "MasterRecord.js",
|
|
6
6
|
"bin": {
|