mythix-orm 1.0.4 → 1.1.0
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/.biblorc.js +29 -0
- package/docs/Associations.md +482 -0
- package/docs/Home.md +1 -0
- package/lib/model.js +1 -1
- package/lib/types/helpers/default-helpers.js +2 -0
- package/lib/types/virtual/model-type.js +4 -0
- package/lib/types/virtual/models-type.js +7 -1
- package/package.json +1 -1
package/.biblorc.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* global __dirname */
|
|
4
|
+
|
|
5
|
+
const Path = require('path');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
rootDir: __dirname,
|
|
9
|
+
inputDir: Path.resolve(__dirname),
|
|
10
|
+
outputDir: Path.resolve(__dirname, '..', 'mythix-orm.wiki'),
|
|
11
|
+
files: [
|
|
12
|
+
{
|
|
13
|
+
include: /\/lib\/.*\.js$/,
|
|
14
|
+
parser: 'typescript',
|
|
15
|
+
compiler: 'typescript',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
include: /\/docs\/.*\.md$/,
|
|
19
|
+
parser: 'markdown',
|
|
20
|
+
compiler: 'markdown',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
exclude: [
|
|
24
|
+
/node_modules|\/spec\//
|
|
25
|
+
],
|
|
26
|
+
generatorOptions: {
|
|
27
|
+
baseURL: './',
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# Mythix ORM associations
|
|
2
|
+
|
|
3
|
+
Mythix ORM makes associations really easy.
|
|
4
|
+
|
|
5
|
+
Fields in a Mythix ORM model can be either "virtual" or "concrete".
|
|
6
|
+
|
|
7
|
+
Concrete fields are backed by storage (the database), and will have a direct value they can be associated with.
|
|
8
|
+
|
|
9
|
+
Virtual fields are not backed by storage (at least not directly), and instead will dynamically fetch their value.
|
|
10
|
+
|
|
11
|
+
A field can be virtual in Mythix ORM simply because of its type. For example, the types `Types.Model` and `Types.Models` will by nature define a virtual field. These types define relations to other tables, and so don't have a 1x1 concrete field in the DB themselves, and instead will pull data based on the relationship defined by the type.
|
|
12
|
+
|
|
13
|
+
Before we get started, there are a few things to keep in mind:
|
|
14
|
+
|
|
15
|
+
1. `Types.Model` is used for a 1x1 relationship
|
|
16
|
+
2. `Types.Models` is used for a one-to-many, or a many-to-many relationship
|
|
17
|
+
3. These two types specify field relationships
|
|
18
|
+
4. In field relationships, it is common to specify a target model, which is done by prefixing the field with the model name plus a single colon (i.e. `User:id`)
|
|
19
|
+
5. In field relationships, it is common to specify a target field (which optionally comes after the model name and colon)
|
|
20
|
+
6. In field relationships, if a model isn't defined, then `Self` ("this model") is assumed
|
|
21
|
+
7. In field relationships, if a field isn't defined, then the target model's primary key is assumed
|
|
22
|
+
8. Field relationships work by defining a "target", and a "value provider" (where to get the value to match against the target)
|
|
23
|
+
9. All concrete fields must be manually defined on all models. There is no "automagic" or "hidden" fields that the relationships themselves will create. Said another way, **you must manually define ALL table columns, always, without exception**... none will be auto-defined for you. If you want auto-defined fields, such as "createdAt", and "updatedAt" for all your models, then the recommendation is to define a BaseModel that all of your other models inherit from.
|
|
24
|
+
|
|
25
|
+
To properly understand how Mythix ORM creates table relationships/associations, it helps to view the situation from the perspective of "this model instance". Don't think of "tables", or "many" when you are thinking of the relationship. Think only of "this model instance".
|
|
26
|
+
|
|
27
|
+
## Example #1 - Defining a 1x1 relationship
|
|
28
|
+
|
|
29
|
+
Let's say you want to have a Users table, and have each user have a single Role.
|
|
30
|
+
|
|
31
|
+
This is a 1x1 relationship, with one User to one Role.
|
|
32
|
+
|
|
33
|
+
You could define the association like so:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
class User extends Model {
|
|
37
|
+
static fields = {
|
|
38
|
+
'id': {
|
|
39
|
+
type: Types.UUIDV4,
|
|
40
|
+
allowNull: false,
|
|
41
|
+
primaryKey: true,
|
|
42
|
+
},
|
|
43
|
+
'firstName': {
|
|
44
|
+
type: Types.STRING(64),
|
|
45
|
+
allowNull: true,
|
|
46
|
+
index: true,
|
|
47
|
+
},
|
|
48
|
+
'lastName': {
|
|
49
|
+
type: Types.STRING(64),
|
|
50
|
+
allowNull: true,
|
|
51
|
+
index: true,
|
|
52
|
+
},
|
|
53
|
+
'roleID': {
|
|
54
|
+
type: Types.UUIDV4,
|
|
55
|
+
allowNull: false,
|
|
56
|
+
index: true,
|
|
57
|
+
},
|
|
58
|
+
// This defines a "virtual" field,
|
|
59
|
+
// that will be used to define
|
|
60
|
+
// methods to interact with the role
|
|
61
|
+
'role': {
|
|
62
|
+
// Remember "target field" and "value provider"
|
|
63
|
+
// If no model is specified, then "Self" is assumed
|
|
64
|
+
// so 'roleID', could be expanded to 'User:roleID'
|
|
65
|
+
//
|
|
66
|
+
// target value provider
|
|
67
|
+
type: Types.Model('Role:id', 'roleID'),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class Role extends Model {
|
|
73
|
+
static fields = {
|
|
74
|
+
'id': {
|
|
75
|
+
type: Types.UUIDV4,
|
|
76
|
+
allowNull: false,
|
|
77
|
+
primaryKey: true,
|
|
78
|
+
},
|
|
79
|
+
'name': {
|
|
80
|
+
type: Types.STRING(64),
|
|
81
|
+
allowNull: false,
|
|
82
|
+
index: true,
|
|
83
|
+
},
|
|
84
|
+
// This defines a "virtual" field,
|
|
85
|
+
// that will be used to define
|
|
86
|
+
// methods to interact with the user
|
|
87
|
+
'user': {
|
|
88
|
+
// Second argument could be omitted here,
|
|
89
|
+
// since the model's PK is assumed if not
|
|
90
|
+
// specified
|
|
91
|
+
type: Types.Model('User:roleID', 'id'),
|
|
92
|
+
|
|
93
|
+
// Equivalent
|
|
94
|
+
// type: Types.Model('User:roleID'),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Let's look at the `User` model `role` field first, which is of the type:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
type: Types.Model('Role:id', 'roleID'),
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This may seem confusing at first, until you understand the pattern here. The pattern is fairly simple. The `Types.Model` is singular (`Models` on the other hand, if used, defines a one-to-many, or many-to-many relationship), so right away we know this is going to fetch a single model, and not many models. It is a 1x1 relationship. Next, we have the two arguments we provide. It helps a lot to think of these as "target", and "value provider"... as in, "What is the target field we are matching on, and who/what is providing the value to be matched against?" It also helps quite a bit to think of this from the perspective of "this model instance", instead of "this table", or "this relationship". The relationship is always from an instance of a model... a single instance (or a single row in the DB, if that helps instead).
|
|
107
|
+
|
|
108
|
+
With this in mind, the above model definitions should make a little more sense:
|
|
109
|
+
|
|
110
|
+
1. From the perspective of this specific user, what are we targeting?
|
|
111
|
+
2. We are targeting the `Role` model, and its `id` field
|
|
112
|
+
3. Great! Now, who is providing the value to match against the `Role:id` field?
|
|
113
|
+
4. Oh, that is easy, it is me, this specific user, and the field to pull the `Role:id` from is `User:roleID`... as in, this very user's `roleID` ("self.roleID").
|
|
114
|
+
|
|
115
|
+
Simple!
|
|
116
|
+
|
|
117
|
+
From the perspective of the Role it is very similar, except the "value provider" has switched to the `User` model, where the `roleID` field exists.
|
|
118
|
+
|
|
119
|
+
Let's look at it from the `Role` perspective now.
|
|
120
|
+
|
|
121
|
+
We have:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
type: Types.Model('User:roleID', 'id'),
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Remember, this could also be written as:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
type: Types.Model('User:roleID'),
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
When no model is specified in the "target", or the "value provider" arguments, then `Self` is implied. So when we say `'id'`, what is really being specified is `Role:id`. The PK of a model is assumed as the default if no "value provider" is given. So, `Role:id`, `id`, and empty are all equivalent in this situation.
|
|
134
|
+
|
|
135
|
+
So now, let's take a look at the process here:
|
|
136
|
+
|
|
137
|
+
1. From the perspective of this specific role, what are we targeting?
|
|
138
|
+
2. We are targeting the `User` model, and its `roleID` field
|
|
139
|
+
3. Great! Now, who is providing the value to match against the `User:roleID` field?
|
|
140
|
+
4. Oh, that is easy, it is me, this specific role, and the field to pull the `User:roleID` value from is `Role:id`... as in, this very roles's primary key `id`
|
|
141
|
+
|
|
142
|
+
One thing that is important to know is that the model name defined in the *first* argument will be the actual model type fetched. So, from the perspective of the user, we specify `Role` as the model in the first argument, so this is the role model type that will be fetched. From the perspective of the role, we specify `User` as the model in the first argument, so this is the model type that will be fetched.
|
|
143
|
+
|
|
144
|
+
## Example #2 - Defining a One To Many relationship
|
|
145
|
+
|
|
146
|
+
Okay, so your boss comes in on Monday morning, and yells at you for implementing the previous example, because users only being able to have a single role is "just plain stupid... what were you thinking?"
|
|
147
|
+
|
|
148
|
+
Well, it is hard to argue with the boss, especially since he is correct, users only being able to have a single role is a just a *wee* bit limiting. So, lets update this so a user can have many roles. To do so, we will need to define a one-to-many relationship.
|
|
149
|
+
|
|
150
|
+
Let's jump right into the code this time:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
class User extends Model {
|
|
154
|
+
static fields = {
|
|
155
|
+
'id': {
|
|
156
|
+
type: Types.UUIDV4,
|
|
157
|
+
allowNull: false,
|
|
158
|
+
primaryKey: true,
|
|
159
|
+
},
|
|
160
|
+
'firstName': {
|
|
161
|
+
type: Types.STRING(64),
|
|
162
|
+
allowNull: true,
|
|
163
|
+
index: true,
|
|
164
|
+
},
|
|
165
|
+
'lastName': {
|
|
166
|
+
type: Types.STRING(64),
|
|
167
|
+
allowNull: true,
|
|
168
|
+
index: true,
|
|
169
|
+
},
|
|
170
|
+
// Let's drop this column, because now it makes
|
|
171
|
+
// no sense to have it live on the User table
|
|
172
|
+
// 'roleID': {
|
|
173
|
+
// type: Types.UUIDV4,
|
|
174
|
+
// allowNull: false,
|
|
175
|
+
// index: true,
|
|
176
|
+
// },
|
|
177
|
+
//
|
|
178
|
+
// Let's change "role" to "roles", because
|
|
179
|
+
// now it will be plural (one to many)
|
|
180
|
+
'roles': {
|
|
181
|
+
// Here we don't need to define a second argument
|
|
182
|
+
// because "Self" and "PK" are assumed, so this
|
|
183
|
+
// works fine. We want the User.id to match against
|
|
184
|
+
// the Role.userID, so we can just leave off the
|
|
185
|
+
// second argument
|
|
186
|
+
//
|
|
187
|
+
// Notice the use of "plural" "Models" here
|
|
188
|
+
type: Types.Models('Role:userID'),
|
|
189
|
+
|
|
190
|
+
// Equivalent:
|
|
191
|
+
// type: Types.Models('Role:userID', 'User:id'),
|
|
192
|
+
//
|
|
193
|
+
// or:
|
|
194
|
+
//
|
|
195
|
+
// type: Types.Models('Role:userID', 'id'),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
class Role extends Model {
|
|
201
|
+
static fields = {
|
|
202
|
+
'id': {
|
|
203
|
+
type: Types.UUIDV4,
|
|
204
|
+
allowNull: false,
|
|
205
|
+
primaryKey: true,
|
|
206
|
+
},
|
|
207
|
+
'name': {
|
|
208
|
+
type: Types.STRING(64),
|
|
209
|
+
allowNull: false,
|
|
210
|
+
index: true,
|
|
211
|
+
},
|
|
212
|
+
// Now we need to define the "userID"
|
|
213
|
+
// for each role
|
|
214
|
+
'userID': {
|
|
215
|
+
type: Types.UUIDV4,
|
|
216
|
+
allowNull: false,
|
|
217
|
+
index: true,
|
|
218
|
+
},
|
|
219
|
+
// This is still correctly named, as
|
|
220
|
+
// each role will only link back to
|
|
221
|
+
// a single user
|
|
222
|
+
'user': {
|
|
223
|
+
// Now we update this, flipping the
|
|
224
|
+
// relationship. If we store a "userID"
|
|
225
|
+
// on the Role table, then a user can
|
|
226
|
+
// have many roles.
|
|
227
|
+
type: Types.Model('User', 'userID'),
|
|
228
|
+
|
|
229
|
+
// Equivalent
|
|
230
|
+
// type: Types.Model('User:id', 'userID'),
|
|
231
|
+
//
|
|
232
|
+
// or:
|
|
233
|
+
//
|
|
234
|
+
// type: Types.Model('User:id', 'Role:userID'),
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Hopefully this makes sense. We simply removed `roleID` from the User model, and moved it over to instead be `userID` on the other side of the relationship. Now a user can have many roles, because instead of each user only defining a single `roleID`, now Roles define a `userID`, and since many roles can define the same `userID`, a user can have many roles.
|
|
241
|
+
|
|
242
|
+
## Example #3 - Foreign Keys
|
|
243
|
+
|
|
244
|
+
Great! So far so good. Hopefully my reader is still following me. What we have done so far will work, but there is a better way. We can have the database assist us in our relationships. If you don't know what "foreign keys" are, or don't understand the concept behind them, I suggest you go take a moment to read about them. In short, "foreign keys" simultaneously define an index, and one or more constraints. This means the database will disallow certain things. For example, if you specify that a relationship between a user and a role MUST exist, and MUST be valid, then the database will throw an error if you try to add a role without a user.
|
|
245
|
+
|
|
246
|
+
Let's go ahead and do that now. After all, we really shouldn't have a `Role` that points to a `NULL` user.
|
|
247
|
+
|
|
248
|
+
So, let's make the following minor changes:
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
class User extends Model {
|
|
252
|
+
static fields = {
|
|
253
|
+
'id': {
|
|
254
|
+
type: Types.UUIDV4,
|
|
255
|
+
allowNull: false,
|
|
256
|
+
primaryKey: true,
|
|
257
|
+
},
|
|
258
|
+
'firstName': {
|
|
259
|
+
type: Types.STRING(64),
|
|
260
|
+
allowNull: true,
|
|
261
|
+
index: true,
|
|
262
|
+
},
|
|
263
|
+
'lastName': {
|
|
264
|
+
type: Types.STRING(64),
|
|
265
|
+
allowNull: true,
|
|
266
|
+
index: true,
|
|
267
|
+
},
|
|
268
|
+
'roles': {
|
|
269
|
+
type: Types.Models('Role:userID'),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
class Role extends Model {
|
|
275
|
+
static fields = {
|
|
276
|
+
'id': {
|
|
277
|
+
type: Types.UUIDV4,
|
|
278
|
+
allowNull: false,
|
|
279
|
+
primaryKey: true,
|
|
280
|
+
},
|
|
281
|
+
'name': {
|
|
282
|
+
type: Types.STRING(64),
|
|
283
|
+
allowNull: false,
|
|
284
|
+
index: true,
|
|
285
|
+
},
|
|
286
|
+
'userID': {
|
|
287
|
+
type: Types.ForeignKey('User:id', {
|
|
288
|
+
// if User is deleted, also delete all roles belonging to the user
|
|
289
|
+
onDelete: 'CASCADE',
|
|
290
|
+
}),
|
|
291
|
+
|
|
292
|
+
// Disallow `userID` from ever being `NULL`
|
|
293
|
+
allowNull: false,
|
|
294
|
+
|
|
295
|
+
// "index" is no longer needed (but it also won't hurt).
|
|
296
|
+
// ForeignKeys are indexed by default.
|
|
297
|
+
// index: true,
|
|
298
|
+
},
|
|
299
|
+
'user': {
|
|
300
|
+
type: Types.Model('User', 'userID'),
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
So this change was fairly simple and straight forward. As you can see, we simply changed the type of the `userID` column on the `Roles` table. Instead of just being a `UUIDV4` type, we instead changed it to be a `ForeignKey` type. The `ForeignKey` type will look up the target field, and pull its type and other options from the target field. So this `ForeignKey('User:id')` will go look-up the `User` model, find its `id` field, and use that to define the `userID` field. Plus, it also defines a foreign-key relationship in the database simultaneously.
|
|
307
|
+
|
|
308
|
+
## Example #4 - Using a "through" table in relationships
|
|
309
|
+
|
|
310
|
+
So far we have just defined relationships between two different tables, our `User` and `Role` tables. What if we instead want to define more that two tables in our relationship? What if, for example, our boss comes back to us on Friday morning and says "Dang, Shayla, okay, well, this situation is slightly better. Now users can have more than one role, but we need to define role information ON THE RELATIONSHIP itself.".
|
|
311
|
+
|
|
312
|
+
Back to work! Now the boss wants extra information defined ON THE RELATIONSHIP, not on users, and not on roles, but rather on the relationship between the two. The boss wants us to define if a user, with a specific role, is able to use the front-door of the office, the back-door of the office, or both.
|
|
313
|
+
|
|
314
|
+
Okay, strange request boss... but then again, bosses generally don't do the thinking very well, which is why they hired you, right? Great! Let's get r' done.
|
|
315
|
+
|
|
316
|
+
For this to work, we now need to define a third table. We will call this table `UserRole`. This will define the user id, the role id, and which door the user is allowed to use. This will also mean that we need to remove the `userID` from the `Role` table.
|
|
317
|
+
|
|
318
|
+
So to sum up, we need to do the following:
|
|
319
|
+
|
|
320
|
+
1. Drop the `userID` from the `Role` table
|
|
321
|
+
2. Create a new table called `UserRole`
|
|
322
|
+
3. Adjust the relationships between the fields
|
|
323
|
+
|
|
324
|
+
Let's dig in:
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
class User extends Model {
|
|
328
|
+
static fields = {
|
|
329
|
+
'id': {
|
|
330
|
+
type: Types.UUIDV4,
|
|
331
|
+
allowNull: false,
|
|
332
|
+
primaryKey: true,
|
|
333
|
+
},
|
|
334
|
+
'firstName': {
|
|
335
|
+
type: Types.STRING(64),
|
|
336
|
+
allowNull: true,
|
|
337
|
+
index: true,
|
|
338
|
+
},
|
|
339
|
+
'lastName': {
|
|
340
|
+
type: Types.STRING(64),
|
|
341
|
+
allowNull: true,
|
|
342
|
+
index: true,
|
|
343
|
+
},
|
|
344
|
+
'roles': {
|
|
345
|
+
// Notice here how we specify the virtual field 'UserRole:role'
|
|
346
|
+
// this will be used to collect all the information
|
|
347
|
+
// necessary to join the tables.
|
|
348
|
+
//
|
|
349
|
+
// Mythix ORM is smart enough to follow all fields
|
|
350
|
+
// until it can build the full relationship.
|
|
351
|
+
// So Mythix ORM will first lookup `UserRole:role`,
|
|
352
|
+
// find that it is a virtual field that targets `roleID`,
|
|
353
|
+
// which it will then look-up, and find that this is a
|
|
354
|
+
// foreign key that points to `Role.id`.
|
|
355
|
+
// So now it knows how to get to the role.
|
|
356
|
+
//
|
|
357
|
+
// But... how do we get to the proper UserRole based
|
|
358
|
+
// on "this instance" of a User? This is why the
|
|
359
|
+
// "value provider" is also targeting a virtual
|
|
360
|
+
// field. When Mythix ORM does a look-up on the field
|
|
361
|
+
// it will notice that it is a virtual field, targeting
|
|
362
|
+
// the "User" model, and so it now knows all relationships.
|
|
363
|
+
type: Types.Models(
|
|
364
|
+
// Target field (will be fully resolved)
|
|
365
|
+
'UserRole:role',
|
|
366
|
+
// Value provider (will be fully resolved)
|
|
367
|
+
'UserRole:user',
|
|
368
|
+
),
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
class Role extends Model {
|
|
374
|
+
static fields = {
|
|
375
|
+
'id': {
|
|
376
|
+
type: Types.UUIDV4,
|
|
377
|
+
allowNull: false,
|
|
378
|
+
primaryKey: true,
|
|
379
|
+
},
|
|
380
|
+
'name': {
|
|
381
|
+
type: Types.STRING(64),
|
|
382
|
+
allowNull: false,
|
|
383
|
+
index: true,
|
|
384
|
+
},
|
|
385
|
+
'user': {
|
|
386
|
+
type: Types.Model(
|
|
387
|
+
// Target field (will be fully resolved)
|
|
388
|
+
'UserRole:user',
|
|
389
|
+
// Value provider (will be fully resolved)
|
|
390
|
+
'UserRole:role',
|
|
391
|
+
),
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
class UserRole extends Model {
|
|
397
|
+
static fields = {
|
|
398
|
+
'id': {
|
|
399
|
+
type: Types.UUIDV4,
|
|
400
|
+
allowNull: false,
|
|
401
|
+
primaryKey: true,
|
|
402
|
+
},
|
|
403
|
+
// The new data point our boss wanted us to add
|
|
404
|
+
'doorUsage': {
|
|
405
|
+
type: Types.STRING,
|
|
406
|
+
allowNull: false,
|
|
407
|
+
index: true,
|
|
408
|
+
},
|
|
409
|
+
'userID': {
|
|
410
|
+
type: Types.ForeignKey('User:id', {
|
|
411
|
+
// if User is deleted, also delete all UserRole belonging to the user
|
|
412
|
+
onDelete: 'CASCADE',
|
|
413
|
+
}),
|
|
414
|
+
allowNull: false,
|
|
415
|
+
},
|
|
416
|
+
'roleID': {
|
|
417
|
+
type: Types.ForeignKey('Role:id', {
|
|
418
|
+
// if Role is deleted, also delete all UserRole belonging to the role
|
|
419
|
+
onDelete: 'CASCADE',
|
|
420
|
+
}),
|
|
421
|
+
allowNull: false,
|
|
422
|
+
},
|
|
423
|
+
'role': {
|
|
424
|
+
// 'Role:id' is assumed
|
|
425
|
+
type: Types.Model('Role', 'roleID')
|
|
426
|
+
},
|
|
427
|
+
'user': {
|
|
428
|
+
// 'User:id' is assumed
|
|
429
|
+
type: Types.Model('User', 'userID'),
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
This is fantastic! But... we have one problem. This through-table relationship will indeed do a three-way join to collect the information requested from all tables, but what about that new `doorUsage` field we added to the `UserRole` table? Nowhere have we specified where that should be used... We could just inject it onto `User` models on load... but that doesn't make sense, because the user could have multiple roles, and each role link could have a different `doorUsage` value. Instead, it would be better if we injected this `doorUsage` value onto the `Roles` that are loaded. That would make more sense, as the `doorUsage` is based on the role, and not based on user.
|
|
436
|
+
|
|
437
|
+
Okay, that is all gravy... but *how* exactly do we request Mythix ORM inject this field on the Role? Well, Mythix ORM will automatically include the through table model on the **loaded** models by default. This means if you load from the perspective of the user:
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
let roles = await thisUser.getRoles();
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Then every `role` in the `roles` array will also have a `userRole` key, defining the row from the through table relationship.
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
let roles = await thisUser.getRoles();
|
|
447
|
+
// roles[0].userRole = UserRole { userID, roleID, id, doorUsage }
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
If instead you load from the perspective of a role, then `userRole` will be added to the user:
|
|
451
|
+
|
|
452
|
+
```javascript
|
|
453
|
+
let user = await role.getUser();
|
|
454
|
+
// user.userRole = UserRole { userID, roleID, id, doorUsage }
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
*NOTE:
|
|
458
|
+
If this was a many-to-many relationship, then `userRole` would be added to **both-sides** of the relation, being added to both loaded `User` models, and also to loaded `Role` models.*
|
|
459
|
+
|
|
460
|
+
Great! Now we can easily access this and get the job done as the boss asked:
|
|
461
|
+
|
|
462
|
+
```javascript
|
|
463
|
+
let role = await pseudoCodeToFetchRoleBasedOnDoorBeingUsed(doorID);
|
|
464
|
+
let user = await role.getUser();
|
|
465
|
+
|
|
466
|
+
if (user.userRole.doorUsage === 'back') {
|
|
467
|
+
// User is only allowed to use the back door
|
|
468
|
+
} else if (user.userRole.doorUsage === 'front') {
|
|
469
|
+
// User is only allowed to use the front door
|
|
470
|
+
} else {
|
|
471
|
+
// User can use either door... the boss has conveniently
|
|
472
|
+
// ensured that this case applies to him
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## Final Notes
|
|
477
|
+
|
|
478
|
+
1. I would like to bring it to the attention of the reader that concrete types are defined all *UPPERCASE*, whereas virtual types are defined as *CamelCase*.
|
|
479
|
+
2. "But, what if I am using two, three, or more through-tables?" you ask. Well, you are in luck! Nothing described above changes. You simply target virtual fields as your "target" and "value provider" in the relationships you define, and Mythix ORM will be smart enough to recursively walk all fields, understand all relationships, and join as many tables as it needs to to get the job done.
|
|
480
|
+
3. Mythix forces you to manually define all table fields. Yes, this is extra overhead, but it comes with the benefit of not needing to painstakingly manually define all *relationships*. This design pattern was also decided upon so that there aren't any table columns that are "hidden" from the user, or ambiguous. By forcing the user to always define all columns manually, it simplifies seeing what fields exist on the table, removes down-stream dependencies, and prevents the user from needing to go look-up documentation to understand how things are working and why. Feel free to write your own helper methods that will automatically inject fields into your schema for you!
|
|
481
|
+
|
|
482
|
+
Happy coding!
|
package/docs/Home.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Welcome to the mythix-orm wiki!
|
package/lib/model.js
CHANGED
|
@@ -1267,7 +1267,7 @@ class Model {
|
|
|
1267
1267
|
async save(_options) {
|
|
1268
1268
|
let options = _options || {};
|
|
1269
1269
|
|
|
1270
|
-
if (options.force !== true && Nife.isEmpty(this.changes)) {
|
|
1270
|
+
if (this.isPersisted() && options.force !== true && Nife.isEmpty(this.changes)) {
|
|
1271
1271
|
return false;
|
|
1272
1272
|
} else if (options.force) {
|
|
1273
1273
|
// Mark all fields as dirty
|
|
@@ -95,6 +95,8 @@ const AUTO_INCREMENT = defaultValueFlags(function(context) {
|
|
|
95
95
|
return context.connection.getDefaultFieldValue('AUTO_INCREMENT', context);
|
|
96
96
|
}, { literal: true, remote: true });
|
|
97
97
|
|
|
98
|
+
AUTO_INCREMENT._mythixIsAutoIncrement = true;
|
|
99
|
+
|
|
98
100
|
const DATETIME_NOW = generatePermutations(function(context) {
|
|
99
101
|
return context.connection.getDefaultFieldValue('DATETIME_NOW', context);
|
|
100
102
|
}, { literal: true, remote: true });
|
|
@@ -116,6 +116,10 @@ const TYPE_OPERATIONS = {
|
|
|
116
116
|
let query = await type.prepareQuery({ connection: null, self: this, field, options }, args);
|
|
117
117
|
return await query.exists(options);
|
|
118
118
|
},
|
|
119
|
+
'pluck': async function({ field, type }, fields, options, ...args) {
|
|
120
|
+
let query = await type.prepareQuery({ connection: null, self: this, field, options }, args);
|
|
121
|
+
return await query.pluck(fields, options);
|
|
122
|
+
},
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
class ModelType extends RelationalTypeBase {
|
|
@@ -106,7 +106,9 @@ const TYPE_OPERATIONS = {
|
|
|
106
106
|
// might be persisted, the through table record
|
|
107
107
|
// might still be created, which might blow up
|
|
108
108
|
// if a constraint fails because the records
|
|
109
|
-
// already exist
|
|
109
|
+
// already exist. The opposite might also be true,
|
|
110
|
+
// where the target models already exist, and
|
|
111
|
+
// so shouldn't be created.
|
|
110
112
|
return this.getConnection(options && options.connection).transaction(async (connection) => {
|
|
111
113
|
let currentModels = this[field.fieldName];
|
|
112
114
|
if (Nife.isEmpty(currentModels))
|
|
@@ -227,6 +229,10 @@ const TYPE_OPERATIONS = {
|
|
|
227
229
|
let query = await type.prepareQuery({ connection: null, self: this, field, userQuery, options }, args);
|
|
228
230
|
return await query.count(null, options);
|
|
229
231
|
},
|
|
232
|
+
'pluck': async function({ field, type }, userQuery, fields, options, ...args) {
|
|
233
|
+
let query = await type.prepareQuery({ connection: null, self: this, field, fields, userQuery, options }, args);
|
|
234
|
+
return await query.pluck(fields, options);
|
|
235
|
+
},
|
|
230
236
|
'has': async function({ count }, userQuery, options, ...args) {
|
|
231
237
|
let itemCount = await count.call(this, userQuery, options, ...args);
|
|
232
238
|
return (itemCount > 0);
|