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 CHANGED
@@ -1,4 +1,4 @@
1
- // Version 0.0.15
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
- file = { file: picked, rootFolder: path.dirname(path.dirname(picked)) };
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.33",
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": {