mythix-orm 1.8.1 → 1.8.3
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/lib/connection/connection-base.js +2237 -7
- package/lib/connection/query-generator-base.js +3 -0
- package/lib/model.js +17 -3
- package/lib/query-engine/query-engine-base.js +4 -4
- package/lib/types/concrete/date-type.js +2 -2
- package/lib/types/concrete/datetime-type.js +2 -2
- package/lib/types/concrete/foreign-key-type.js +2 -2
- package/lib/types/type.js +1 -1
- package/lib/types/virtual/model-type.js +151 -0
- package/lib/types/virtual/models-type.js +147 -0
- package/lib/types/virtual/relational-type-base.js +0 -10
- package/lib/utils/model-utils.js +44 -0
- package/lib/utils/query-utils.js +2 -2
- package/package.json +1 -1
|
@@ -7,6 +7,9 @@ const { Model: ModelBase } = require('../model');
|
|
|
7
7
|
const LiteralBase = Literals.LiteralBase;
|
|
8
8
|
const LITERAL_IS_DISTINCT_RE = (/^DISTINCT[\s(]/i);
|
|
9
9
|
|
|
10
|
+
/// The base query generator class.
|
|
11
|
+
///
|
|
12
|
+
/// Alias: QueryGenerator
|
|
10
13
|
class QueryGeneratorBase {
|
|
11
14
|
constructor(connection) {
|
|
12
15
|
Object.defineProperties(this, {
|
package/lib/model.js
CHANGED
|
@@ -133,6 +133,8 @@ function bindStaticWhereToModelClass(ModelClass) {
|
|
|
133
133
|
/// unless you know exactly what you are doing.
|
|
134
134
|
/// It also implies that there is another method
|
|
135
135
|
/// that can and should be overloaded instead.
|
|
136
|
+
///
|
|
137
|
+
/// Alias: Models
|
|
136
138
|
class Model {
|
|
137
139
|
/// This property assists with type checking
|
|
138
140
|
///
|
|
@@ -1386,7 +1388,7 @@ class Model {
|
|
|
1386
1388
|
/// sorted?: boolean = false
|
|
1387
1389
|
/// If `true`, then sort the model fields before iterating. If `false`,
|
|
1388
1390
|
/// simply iterate the model's fields in their defined order.
|
|
1389
|
-
static iterateFields(callback, _fields, sorted) {
|
|
1391
|
+
static iterateFields(callback, _fields, sorted, stopOnTruthy) {
|
|
1390
1392
|
if (typeof callback !== 'function')
|
|
1391
1393
|
return [];
|
|
1392
1394
|
|
|
@@ -1394,12 +1396,24 @@ class Model {
|
|
|
1394
1396
|
if (!fields)
|
|
1395
1397
|
fields = (sorted !== false) ? this.getSortedFields() : this.getFields();
|
|
1396
1398
|
|
|
1397
|
-
|
|
1399
|
+
let finalValue;
|
|
1400
|
+
|
|
1401
|
+
let finalResult = Nife.iterate(fields, ({ value: field, index, context, stop, isStopped }) => {
|
|
1398
1402
|
let result = callback({ field, fieldName: field.fieldName, fields, index, stop, isStopped });
|
|
1403
|
+
if (stopOnTruthy === true && result) {
|
|
1404
|
+
finalValue = field;
|
|
1405
|
+
stop();
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1399
1408
|
|
|
1400
1409
|
if (!isStopped())
|
|
1401
1410
|
context.push(result);
|
|
1402
1411
|
}, []);
|
|
1412
|
+
|
|
1413
|
+
if (stopOnTruthy)
|
|
1414
|
+
return finalValue;
|
|
1415
|
+
|
|
1416
|
+
return finalResult;
|
|
1403
1417
|
}
|
|
1404
1418
|
|
|
1405
1419
|
iterateFields(callback, _fields, sorted) {
|
|
@@ -2226,7 +2240,7 @@ class Model {
|
|
|
2226
2240
|
/// <see>Connection</see> then it will behave a little differently.
|
|
2227
2241
|
///
|
|
2228
2242
|
/// The first difference is that if a model field is not marked
|
|
2229
|
-
/// as dirty, then it will be passed through <see>
|
|
2243
|
+
/// as dirty, then it will be passed through <see>Connection.dirtyFieldHelper</see>.
|
|
2230
2244
|
/// If this method returns any value other than `undefined`, then
|
|
2231
2245
|
/// that value will be used as the "dirty" value for the field, and
|
|
2232
2246
|
/// the field will be marked as dirty. This can be used when you
|
|
@@ -107,10 +107,10 @@ class QueryEngineBase extends ProxyClass {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
_newQueryEngineScope() {
|
|
110
|
-
const
|
|
111
|
-
let context
|
|
112
|
-
let newContext
|
|
113
|
-
let newScope
|
|
110
|
+
const QueryEngineClass = this.getQueryEngineClass();
|
|
111
|
+
let context = this.currentContext;
|
|
112
|
+
let newContext = this._inheritContext(context, 'queryEngine');
|
|
113
|
+
let newScope = new QueryEngineClass(newContext);
|
|
114
114
|
|
|
115
115
|
return newScope;
|
|
116
116
|
}
|
|
@@ -179,7 +179,7 @@ class DateType extends Type {
|
|
|
179
179
|
/// needs to be serialized. If a `connection` argument
|
|
180
180
|
/// is provided, then this method will assume that the
|
|
181
181
|
/// connection is serializing it for storage in the database.
|
|
182
|
-
/// In this case, <see>
|
|
182
|
+
/// In this case, <see>Connection.convertDateToDBTime</see> is
|
|
183
183
|
/// called and provided the `value` for the connection to turn
|
|
184
184
|
/// the date into a proper value for the underlying database.
|
|
185
185
|
///
|
|
@@ -196,7 +196,7 @@ class DateType extends Type {
|
|
|
196
196
|
/// connection?: <see>Connection</see>
|
|
197
197
|
/// A connection instance, which if provided, will
|
|
198
198
|
/// proxy serialization to the underlying database connection
|
|
199
|
-
/// via the <see>
|
|
199
|
+
/// via the <see>Connection.convertDateToDBTime</see> method.
|
|
200
200
|
serialize(_value, connection) {
|
|
201
201
|
let value = _value;
|
|
202
202
|
if (value == null)
|
|
@@ -181,7 +181,7 @@ class DateTimeType extends Type {
|
|
|
181
181
|
/// needs to be serialized. If a `connection` argument
|
|
182
182
|
/// is provided, then this method will assume that the
|
|
183
183
|
/// connection is serializing it for storage in the database.
|
|
184
|
-
/// In this case, <see>
|
|
184
|
+
/// In this case, <see>Connection.convertDateToDBTime</see> is
|
|
185
185
|
/// called and provided the `value` for the connection to turn
|
|
186
186
|
/// the date into a proper value for the underlying database.
|
|
187
187
|
///
|
|
@@ -198,7 +198,7 @@ class DateTimeType extends Type {
|
|
|
198
198
|
/// connection?: <see>Connection</see>
|
|
199
199
|
/// A connection instance, which if provided, will
|
|
200
200
|
/// proxy serialization to the underlying database connection
|
|
201
|
-
/// via the <see>
|
|
201
|
+
/// via the <see>Connection.convertDateToDBTime</see> method.
|
|
202
202
|
serialize(_value, connection) {
|
|
203
203
|
let value = _value;
|
|
204
204
|
if (value == null)
|
|
@@ -12,7 +12,7 @@ const ModelUtils = require('../../utils/model-utils');
|
|
|
12
12
|
/// type of integer.
|
|
13
13
|
///
|
|
14
14
|
/// Foreign keys are one of the ways Mythix ORM knows that models
|
|
15
|
-
/// are related, along with the
|
|
15
|
+
/// are related, along with the <see>ModelType</see> and <see>ModelsType</see> virtual types.
|
|
16
16
|
/// The "target" field must be a fully qualified field name. A
|
|
17
17
|
/// fully qualified field name is a name that is also prefixed by
|
|
18
18
|
/// the model that owns it. For example, `'User:firstName'` would
|
|
@@ -284,7 +284,7 @@ class ForeignKeyType extends Type {
|
|
|
284
284
|
/// across all model instances. `initialize` is still
|
|
285
285
|
/// called for every model instance that is created however,
|
|
286
286
|
/// because the type class can modify the model it
|
|
287
|
-
/// exists on. For example, the
|
|
287
|
+
/// exists on. For example, the <see>ModelType</see> and <see>ModelsType</see> types
|
|
288
288
|
/// inject custom relational methods onto each model instance.
|
|
289
289
|
///
|
|
290
290
|
/// This specific `initialize` for the `ForeignKeyType`
|
package/lib/types/type.js
CHANGED
|
@@ -244,7 +244,7 @@ class Type {
|
|
|
244
244
|
/// across all model instances. `initialize` is still
|
|
245
245
|
/// called for every model instance that is created however,
|
|
246
246
|
/// because the type class can modify the model it
|
|
247
|
-
/// exists on. For example, the
|
|
247
|
+
/// exists on. For example, the <see>ModelType</see> and <see>ModelsType</see> types
|
|
248
248
|
/// inject custom relational methods onto each model instance.
|
|
249
249
|
///
|
|
250
250
|
/// Return: undefined
|
|
@@ -122,11 +122,150 @@ const TYPE_OPERATIONS = {
|
|
|
122
122
|
},
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
+
/// A "virtual" field `type` that defines a one to one
|
|
126
|
+
/// relationship with another model.
|
|
127
|
+
///
|
|
128
|
+
/// Relationships (also known as "associations") in Mythix ORM
|
|
129
|
+
/// are defined by virtual fields that return a "relationship query"
|
|
130
|
+
/// from a method known as a "query generator" that is defined along
|
|
131
|
+
/// with the type definition.
|
|
132
|
+
/// This "relationship query" itself defines the model relationship, no matter
|
|
133
|
+
/// how complicated. Other ORMs require that you define relationships
|
|
134
|
+
/// via their "through table", matching foreign key, type, etc...
|
|
135
|
+
/// In Mythix ORM, everything is simply defined as a query for the
|
|
136
|
+
/// relationship.
|
|
137
|
+
///
|
|
138
|
+
/// This field `type` can be used to define a virtual field
|
|
139
|
+
/// that defines a relationship to another model. When your
|
|
140
|
+
/// model is instantiated into an instance, then this type will
|
|
141
|
+
/// inject the following methods into your model instance to
|
|
142
|
+
/// assist you in interacting with the relationship. The "injected methods"
|
|
143
|
+
/// listed below are "prefixes". The full names of these methods are
|
|
144
|
+
/// always postfixed with the actual name of your field that defines
|
|
145
|
+
/// the relationship. For example, if you had a `primaryRole` field
|
|
146
|
+
/// that defined this type of relationship, then the methods injected
|
|
147
|
+
/// would be `model.getPrimaryRole`, `model.destroyPrimaryRole`, `model.pluckPrimaryRole`, etc...
|
|
148
|
+
/// | Injected Method Prefix | Full Method Name | Signature | Description |
|
|
149
|
+
/// | ---------------------- | ---------------- | ----------- | --------- |
|
|
150
|
+
/// | `create` | `create${fieldName}` | `create(modelInstanceOrAttributes: Model \| object, options?: object, ...args: Array<any>): Promise<Model>` | Create the model defined by the relationship. Returns the newly created model. |
|
|
151
|
+
/// | `destroy` | `destroy${fieldName}` | `destroy(options?: object, ...args: Array<any>): Promise<number>` | Destroy the model defined by the relationship. Returns the number of models destroyed. |
|
|
152
|
+
/// | `get` | `get${fieldName}` | `get(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<Model>` | Get the model specified by the relationship, if one is found. Returned the related model (if there is any). |
|
|
153
|
+
/// | `has` | `has${fieldName}` | `has(options?: object, ...args: Array<any>): Promise<boolean>` | Check if the model defined by the relationship exists. |
|
|
154
|
+
/// | `pluck` | `pluck${fieldName}` | `pluck(fields: Field \| string \| Array<Field \| string>, options?: object, ...args: Array<any>): Promise<Array<any>>` | Pluck specific fields from the related model. |
|
|
155
|
+
/// | `queryFor` | `queryFor${fieldName}` | `queryFor(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<QueryEngine>` | Get the raw relationship query defined by this field type (returned by the "query generator"). |
|
|
156
|
+
/// | `update` | `update${fieldName}` | `update(modelInstanceOrAttributes: Model \| object, options?: object, ...args: Array<any>): Promise<Model>` | Update the model specified by the relationship. Return the updated model. |
|
|
157
|
+
///
|
|
158
|
+
/// The `...args` provided for each injected method are pass-through args for the user, that allow
|
|
159
|
+
/// the user to pass any arguments they desire to the "relational query generator" method defined
|
|
160
|
+
/// by the field (the `...userArgs` as seen in the example below). One minor exception to this is
|
|
161
|
+
/// any method that has a `userQuery` argument. The `userQuery` argument will always also be passed as the
|
|
162
|
+
/// first argument to the `...userArgs` of the "query generator" method. The `userQuery` argument
|
|
163
|
+
/// is to allow the user to define an extra query to merge into the primary/root query of the relationship.
|
|
164
|
+
/// For example, in the example provided below, we could call `await user.getPrimaryRole(Role.where.name.EQ('admin'))`
|
|
165
|
+
/// to only get the primary role if it also had a `name` attribute with the value `'admin'`.
|
|
166
|
+
///
|
|
167
|
+
/// This type also injects duplicates of each of these relational methods with an underscore prefix, i.e.
|
|
168
|
+
/// `_getPrimaryRole`. These are known as "root methods". They are provided so the user can overload the
|
|
169
|
+
/// default implementations. For example, you could define a `getPrimaryRole` method on your model class,
|
|
170
|
+
/// and from that call the "super" method simply by calling `this._getPrimaryRole`. This is required because
|
|
171
|
+
/// with method injection `super` doesn't actually exist, because the method was injected, instead of being
|
|
172
|
+
/// defined on the model class's prototype.
|
|
173
|
+
///
|
|
174
|
+
/// Example:
|
|
175
|
+
/// class User extends Model {
|
|
176
|
+
/// static fields = {
|
|
177
|
+
/// ...,
|
|
178
|
+
/// // Target primary role ID
|
|
179
|
+
/// // as a foreign key
|
|
180
|
+
/// primaryRoleID: {
|
|
181
|
+
/// type: Types.FOREIGN_KEY('Role:id', {
|
|
182
|
+
/// // When the role is deleted then
|
|
183
|
+
/// // clear this attribute by setting it to NULL
|
|
184
|
+
/// onDelete: 'SET NULL',
|
|
185
|
+
/// onUpdate: 'CASCADE',
|
|
186
|
+
/// }),
|
|
187
|
+
/// allowNull: true,
|
|
188
|
+
/// index: true,
|
|
189
|
+
/// },
|
|
190
|
+
/// // Add a virtual relationship field, which creates a
|
|
191
|
+
/// // one to one relationship with the Role model.
|
|
192
|
+
/// //
|
|
193
|
+
/// // Notice how a "type" can always be used directly as
|
|
194
|
+
/// // the field definition, instead of defining an object
|
|
195
|
+
/// // with a "type" property.
|
|
196
|
+
/// primaryRole: Types.Model(
|
|
197
|
+
/// // Specify the target/root model.
|
|
198
|
+
/// 'Role',
|
|
199
|
+
/// // Define our "query generator" method that
|
|
200
|
+
/// // will return our "relationship query".
|
|
201
|
+
/// (context, connectionModels, ...userArgs) => {
|
|
202
|
+
/// // Get the model we need from the connection,
|
|
203
|
+
/// // which is conveniently passed to us as the
|
|
204
|
+
/// // `connectionModels` argument here.
|
|
205
|
+
/// let { Role } = connectionModels;
|
|
206
|
+
///
|
|
207
|
+
/// // Get the "self", which is the model instance
|
|
208
|
+
/// // calling this method
|
|
209
|
+
/// // (i.e. with `model.getPrimaryRole()`, "self" would be "model")
|
|
210
|
+
/// let { self } = context;
|
|
211
|
+
///
|
|
212
|
+
/// // Now return a relationship query
|
|
213
|
+
/// return Role.where
|
|
214
|
+
/// .id
|
|
215
|
+
/// .EQ(self.primaryRoleID)
|
|
216
|
+
/// .AND
|
|
217
|
+
/// .MERGE(userArgs[0]); // Apply the `userQuery` (will do nothing if nullish)
|
|
218
|
+
/// },
|
|
219
|
+
/// ),
|
|
220
|
+
/// };
|
|
221
|
+
/// }
|
|
222
|
+
///
|
|
223
|
+
/// // ... later on
|
|
224
|
+
/// // get the "primary role" for the first user in the database
|
|
225
|
+
/// let user = await User.first();
|
|
226
|
+
/// // Call relationship method injected by the `Types.Model` type.
|
|
227
|
+
/// let primaryRole = await user.getPrimaryRole();
|
|
228
|
+
///
|
|
229
|
+
/// Note:
|
|
230
|
+
/// See the [Associations](./Associations) article for a more in-depth discussion
|
|
231
|
+
/// of Mythix ORM model relationship/associations.
|
|
232
|
+
///
|
|
233
|
+
/// Note:
|
|
234
|
+
/// The "query generator" method defined with the type can be an asynchronous method.
|
|
235
|
+
///
|
|
236
|
+
/// Note:
|
|
237
|
+
/// Relational methods **will not** be injected into the model instance if a method of
|
|
238
|
+
/// the same name already exists on the model instance. For example, if you define a
|
|
239
|
+
/// `getPrimaryRole` method on your model class, then the default `get` relational method
|
|
240
|
+
/// (`getPrimaryRole` in our example) won't be injected. However, the "root method" `_getPrimaryRole`
|
|
241
|
+
/// will still be injected, allowing you to call that as the `super` method instead.
|
|
242
|
+
///
|
|
243
|
+
/// Note:
|
|
244
|
+
/// Behind the scenes the relationship methods provided always apply a `LIMIT(1)` to
|
|
245
|
+
/// any query used. This is because this relationship type is singular, so it is expected
|
|
246
|
+
/// that only a single model can ever be returned.
|
|
125
247
|
class ModelType extends RelationalTypeBase {
|
|
126
248
|
isManyRelation() {
|
|
127
249
|
return false;
|
|
128
250
|
}
|
|
129
251
|
|
|
252
|
+
/// Internal method used to generate injected method names.
|
|
253
|
+
///
|
|
254
|
+
/// This method likely should never be called by the user.
|
|
255
|
+
/// It is used to generate the injected method names.
|
|
256
|
+
///
|
|
257
|
+
/// Return: string
|
|
258
|
+
/// The method name to inject into the model instance.
|
|
259
|
+
///
|
|
260
|
+
/// Arguments:
|
|
261
|
+
/// field: <see>Field</see>
|
|
262
|
+
/// The parent field defining this relationship.
|
|
263
|
+
/// operation: string
|
|
264
|
+
/// The operation being injected into the model instance, which is one of
|
|
265
|
+
/// the prefixes listed above, i.e. `update`, `get`, `destroy`, etc...
|
|
266
|
+
/// rootMethod: boolean
|
|
267
|
+
/// If `true`, then return a generated "root method" name, instead of a "user method".
|
|
268
|
+
/// The only difference is that the "root method" name is prefixed with an underscore `_`.
|
|
130
269
|
fieldNameToOperationName(field, operation, rootMethod) {
|
|
131
270
|
if (rootMethod)
|
|
132
271
|
return `_${operation}${Nife.capitalize(field.fieldName)}`;
|
|
@@ -134,6 +273,18 @@ class ModelType extends RelationalTypeBase {
|
|
|
134
273
|
return `${operation}${Nife.capitalize(field.fieldName)}`;
|
|
135
274
|
}
|
|
136
275
|
|
|
276
|
+
/// Inject type methods into the model instance.
|
|
277
|
+
///
|
|
278
|
+
/// The <see>Type.initialize</see> method is always called
|
|
279
|
+
/// for each model that is instantiated. For this type, this
|
|
280
|
+
/// is used to inject the relational methods into the model
|
|
281
|
+
/// instance.
|
|
282
|
+
///
|
|
283
|
+
/// Arguments:
|
|
284
|
+
/// connection: <see>Connection</see>
|
|
285
|
+
/// The connection for the model being instantiated.
|
|
286
|
+
/// self: <see>Model</see>
|
|
287
|
+
/// The model instance the type is being initialized for.
|
|
137
288
|
initialize(connection, self) {
|
|
138
289
|
return super.initialize(connection, self, TYPE_OPERATIONS);
|
|
139
290
|
}
|
|
@@ -239,6 +239,124 @@ const TYPE_OPERATIONS = {
|
|
|
239
239
|
},
|
|
240
240
|
};
|
|
241
241
|
|
|
242
|
+
/// A "virtual" field `type` that defines a one to many,
|
|
243
|
+
/// or many to many relationship with other model(s).
|
|
244
|
+
///
|
|
245
|
+
/// Many to many relationships interact with "sets", which in
|
|
246
|
+
/// Mythix ORM is the term used to define an "array of models",
|
|
247
|
+
/// in our N to many relationship.
|
|
248
|
+
///
|
|
249
|
+
/// Relationships (also known as "associations") in Mythix ORM
|
|
250
|
+
/// are defined by virtual fields that return a "relationship query"
|
|
251
|
+
/// from a method known as a "query generator" that is defined along
|
|
252
|
+
/// with the type definition.
|
|
253
|
+
/// This "relationship query" itself defines the model relationship, no matter
|
|
254
|
+
/// how complicated. Other ORMs require that you define relationships
|
|
255
|
+
/// via their "through table", matching foreign key, type, etc...
|
|
256
|
+
/// In Mythix ORM, everything is simply defined as a query for the
|
|
257
|
+
/// relationship.
|
|
258
|
+
///
|
|
259
|
+
/// This field `type` can be used to define a virtual field
|
|
260
|
+
/// that defines a relationship to other models. When your
|
|
261
|
+
/// model is instantiated into an instance, then this type will
|
|
262
|
+
/// inject the following methods into your model instance to
|
|
263
|
+
/// assist you in interacting with the relationship set. The "injected methods"
|
|
264
|
+
/// listed below are "prefixes". The full names of these methods are
|
|
265
|
+
/// always postfixed with the actual name of your field that defines
|
|
266
|
+
/// the relationship. For example, if you had a `roles` field
|
|
267
|
+
/// that defined this type of relationship, then the methods injected
|
|
268
|
+
/// would be `model.getRoles`, `model.destroyRoles`, `model.pluckRoles`, etc...
|
|
269
|
+
/// | Injected Method Prefix | Full Method Name | Signature | Description |
|
|
270
|
+
/// | ---------------------- | ---------------- | ----------- | --------- |
|
|
271
|
+
/// | `addTo` | `addTo${fieldName}` | `addTo(models: Array<Model \| object>, options?: object): Promise<Array<Model>>` | Create the models defined by the relationship, and add them to the set. Returns the newly created models. |
|
|
272
|
+
/// | `count` | `count${fieldName}` | `count(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<number>` | Count the number of models in the set. |
|
|
273
|
+
/// | `destroy` | `destroy${fieldName}` | `destroy(options?: object, ...args: Array<any>): Promise<number>` | Destroy the models defined by the relationship set. Returns the number of models destroyed. Note that this not only destroys the target models, but also any through-table relationships involved. |
|
|
274
|
+
/// | `get` | `get${fieldName}` | `get(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<Array<Model>>` | Get the models specified by the relationship set. Return the entire model set. |
|
|
275
|
+
/// | `has` | `has${fieldName}` | `has(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<boolean>` | Check if the relationship set contains any models. |
|
|
276
|
+
/// | `pluck` | `pluck${fieldName}` | `pluck(userQuery?: QueryEngine, fields: Field \| string \| Array<Field \| string>, options?: object, ...args: Array<any>): Promise<Array<any>>` | Pluck specific fields from the related models (set). |
|
|
277
|
+
/// | `queryFor` | `queryFor${fieldName}` | `queryFor(userQuery?: QueryEngine, options?: object, ...args: Array<any>): Promise<QueryEngine>` | Get the raw relationship query defined by this field type (returned by the "query generator"). |
|
|
278
|
+
/// | `removeFrom` | `removeFrom${fieldName}` | `removeFrom(models: Array<Model>, options?: object): Promise<number>` | Remove the specified models from the relationship set without destroying the target models (destroy only the through-table linking models). Returns the number of models remaining in the set after the operation. |
|
|
279
|
+
/// | `set` | `set${fieldName}` | `set(models: Array<Model \| object>, options?: object): Promise<Array<Model>>` | Overwrite the model set with the models specified, destroying models outside the specified set of models. Returns the new models. |
|
|
280
|
+
///
|
|
281
|
+
/// The `...args` provided for each injected method are pass-through args for the user, that allow
|
|
282
|
+
/// the user to pass any arguments they desire to the "relational query generator" method defined
|
|
283
|
+
/// by the field (the `...userArgs` as seen in the example below). One minor exception to this is
|
|
284
|
+
/// any method that has a `userQuery` argument. The `userQuery` argument will always also be passed as the
|
|
285
|
+
/// first argument to the `...userArgs` of the "query generator" method. The `userQuery` argument
|
|
286
|
+
/// is to allow the user to define an extra query to merge into the primary/root query of the relationship.
|
|
287
|
+
/// For example, in the example provided below, we could call `await user.getRoles(Role.where.name.EQ('admin'))`
|
|
288
|
+
/// to only get the related roles where each role also has a `name` attribute with the value `'admin'`.
|
|
289
|
+
///
|
|
290
|
+
/// This type also injects duplicates of each of these relational methods with an underscore prefix, i.e.
|
|
291
|
+
/// `_getRoles`. These are known as "root methods". They are provided so the user can overload the
|
|
292
|
+
/// default implementations. For example, you could define a `getRoles` method on your model class,
|
|
293
|
+
/// and from that call the "super" method simply by calling `this._getRoles`. This is required because
|
|
294
|
+
/// with method injection `super` doesn't actually exist, because the method was injected, instead of being
|
|
295
|
+
/// defined on the model class's prototype.
|
|
296
|
+
///
|
|
297
|
+
/// Example:
|
|
298
|
+
/// class User extends Model {
|
|
299
|
+
/// static fields = {
|
|
300
|
+
/// ...,
|
|
301
|
+
/// // Add a virtual relationship field, which creates a
|
|
302
|
+
/// // one to many relationship with the Role model.
|
|
303
|
+
/// //
|
|
304
|
+
/// // Notice how a "type" can always be used directly as
|
|
305
|
+
/// // the field definition, instead of defining an object
|
|
306
|
+
/// // with a "type" property.
|
|
307
|
+
/// roles: Types.Models(
|
|
308
|
+
/// // Specify the target/root model.
|
|
309
|
+
/// 'Role',
|
|
310
|
+
/// // Define our "query generator" method that
|
|
311
|
+
/// // will return our "relationship query".
|
|
312
|
+
/// (context, connectionModels, ...userArgs) => {
|
|
313
|
+
/// // Get the model we need from the connection,
|
|
314
|
+
/// // which is conveniently passed to us as the
|
|
315
|
+
/// // `connectionModels` argument here.
|
|
316
|
+
/// let { Role } = connectionModels;
|
|
317
|
+
///
|
|
318
|
+
/// // Get the "self", which is the model instance
|
|
319
|
+
/// // calling this method
|
|
320
|
+
/// // (i.e. with `model.getRoles()`, "self" would be "model")
|
|
321
|
+
/// let { self } = context;
|
|
322
|
+
///
|
|
323
|
+
/// // Now return a relationship query
|
|
324
|
+
/// return Role.where
|
|
325
|
+
/// .userID
|
|
326
|
+
/// .EQ(self.id)
|
|
327
|
+
/// .AND
|
|
328
|
+
/// .MERGE(userArgs[0]); // Apply the `userQuery` (will do nothing if nullish)
|
|
329
|
+
/// },
|
|
330
|
+
/// ),
|
|
331
|
+
/// };
|
|
332
|
+
/// }
|
|
333
|
+
///
|
|
334
|
+
/// // ... later on
|
|
335
|
+
/// // get the "roles" for the first user in the database
|
|
336
|
+
/// let user = await User.first();
|
|
337
|
+
/// // Call relationship method injected by the `Types.Models` type.
|
|
338
|
+
/// let allUserRoles = await user.getRoles();
|
|
339
|
+
///
|
|
340
|
+
/// Note:
|
|
341
|
+
/// See the [Associations](./Associations) article for a more in-depth discussion
|
|
342
|
+
/// of Mythix ORM model relationship/associations.
|
|
343
|
+
///
|
|
344
|
+
/// Note:
|
|
345
|
+
/// The "query generator" method defined with the type can be an asynchronous method.
|
|
346
|
+
///
|
|
347
|
+
/// Note:
|
|
348
|
+
/// Relational methods **will not** be injected into the model instance if a method of
|
|
349
|
+
/// the same name already exists on the model instance. For example, if you define a
|
|
350
|
+
/// `getRoles` method on your model class, then the default `get` relational method
|
|
351
|
+
/// (`getRoles` in our example) won't be injected. However, the "root method" `_getRoles`
|
|
352
|
+
/// will still be injected, allowing you to call that as the `super` method instead.
|
|
353
|
+
///
|
|
354
|
+
/// Note:
|
|
355
|
+
/// This field type will not be "exposed" to models. This means that your model will not
|
|
356
|
+
/// have an attribute with the name provided by the field defining this type. The only
|
|
357
|
+
/// thing that will exist on your model will be the relational methods injected by this type.
|
|
358
|
+
/// Said another way, and using the examples provided, there will be no "roles" attribute
|
|
359
|
+
/// on your `User` model.
|
|
242
360
|
class ModelsType extends RelationalTypeBase {
|
|
243
361
|
static exposeToModel() {
|
|
244
362
|
return false;
|
|
@@ -248,6 +366,23 @@ class ModelsType extends RelationalTypeBase {
|
|
|
248
366
|
return true;
|
|
249
367
|
}
|
|
250
368
|
|
|
369
|
+
/// Internal method used to generate injected method names.
|
|
370
|
+
///
|
|
371
|
+
/// This method likely should never be called by the user.
|
|
372
|
+
/// It is used to generate the injected method names.
|
|
373
|
+
///
|
|
374
|
+
/// Return: string
|
|
375
|
+
/// The method name to inject into the model instance.
|
|
376
|
+
///
|
|
377
|
+
/// Arguments:
|
|
378
|
+
/// field: <see>Field</see>
|
|
379
|
+
/// The parent field defining this relationship.
|
|
380
|
+
/// operation: string
|
|
381
|
+
/// The operation being injected into the model instance, which is one of
|
|
382
|
+
/// the prefixes listed above, i.e. `addTo`, `get`, `destroy`, etc...
|
|
383
|
+
/// rootMethod: boolean
|
|
384
|
+
/// If `true`, then return a generated "root method" name, instead of a "user method".
|
|
385
|
+
/// The only difference is that the "root method" name is prefixed with an underscore `_`.
|
|
251
386
|
fieldNameToOperationName(field, operation, rootMethod) {
|
|
252
387
|
let fieldName = field.pluralName;
|
|
253
388
|
if (!fieldName)
|
|
@@ -259,6 +394,18 @@ class ModelsType extends RelationalTypeBase {
|
|
|
259
394
|
return `${operation}${fieldName}`;
|
|
260
395
|
}
|
|
261
396
|
|
|
397
|
+
/// Inject type methods into the model instance.
|
|
398
|
+
///
|
|
399
|
+
/// The <see>Type.initialize</see> method is always called
|
|
400
|
+
/// for each model that is instantiated. For this type, this
|
|
401
|
+
/// is used to inject the relational methods into the model
|
|
402
|
+
/// instance.
|
|
403
|
+
///
|
|
404
|
+
/// Arguments:
|
|
405
|
+
/// connection: <see>Connection</see>
|
|
406
|
+
/// The connection for the model being instantiated.
|
|
407
|
+
/// self: <see>Model</see>
|
|
408
|
+
/// The model instance the type is being initialized for.
|
|
262
409
|
initialize(connection, self) {
|
|
263
410
|
return super.initialize(connection, self, TYPE_OPERATIONS);
|
|
264
411
|
}
|
|
@@ -17,16 +17,6 @@ class RelationalTypeBase extends Type {
|
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// Model types work by specifying a "target"
|
|
21
|
-
// and a "value provider" (source).
|
|
22
|
-
// These are fully qualified names, meaning
|
|
23
|
-
// they also point to the model as well as the field.
|
|
24
|
-
// If no model is specified, then it always defaults to
|
|
25
|
-
// "this" model. If no field is specified, then it always
|
|
26
|
-
// defaults to "this PK" of the model.
|
|
27
|
-
// Mythix ORM will recursively walk all models and fields
|
|
28
|
-
// defined until it has the full relationships between
|
|
29
|
-
// all fields.
|
|
30
20
|
constructor(_targetModelName, queryFactory, _options) {
|
|
31
21
|
let options = _options || {};
|
|
32
22
|
|
package/lib/utils/model-utils.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
///! DocScope: ModelUtils
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const Nife = require('nife');
|
|
@@ -11,6 +13,48 @@ function sanitizeFieldString(str) {
|
|
|
11
13
|
return ('' + str).replace(/[^\w:.]+/g, '').replace(/([:.])+/g, '$1');
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
/// Parse a fully qualified field name.
|
|
17
|
+
///
|
|
18
|
+
/// A fully qualified field name is a field name that
|
|
19
|
+
/// is prefixed by its parent model. For example,
|
|
20
|
+
/// `'User:email'` is a fully qualified field name,
|
|
21
|
+
/// whereas just `'email'` is not.
|
|
22
|
+
///
|
|
23
|
+
/// This method will actually parse both formats however.
|
|
24
|
+
/// It will parse fully qualified field names, and short-hand
|
|
25
|
+
/// field names (just the field name). If just a field name
|
|
26
|
+
/// is provided with no model name prefix, then the resulting
|
|
27
|
+
/// `modelName` property of the returned object will be `undefined`.
|
|
28
|
+
///
|
|
29
|
+
/// Field names themselves can be deeply nested. For example,
|
|
30
|
+
/// `'User:metadata.ipAddress'`. Field nesting was designed into
|
|
31
|
+
/// the Mythix ORM framework, and is partially supported in some
|
|
32
|
+
/// areas of the framework. However, field nesting likely won't work
|
|
33
|
+
/// in many places without further support being added. This feature
|
|
34
|
+
/// was left in-place for now, because it will likely be expanded upon
|
|
35
|
+
/// in the future.
|
|
36
|
+
/// Because of this "field nesting", instead of just one field name being
|
|
37
|
+
/// returned, an array of field names is *always* returned. Generally,
|
|
38
|
+
/// to get the field name you are looking for you would just get the first
|
|
39
|
+
/// index (`fieldNames[0]`) on the returned object.
|
|
40
|
+
///
|
|
41
|
+
/// This method can and will also parse just a model name, so long as the provided
|
|
42
|
+
/// name starts with an upper-case letter. If no colon is found (meaning no model
|
|
43
|
+
/// prefix is present), then the provided value will be assumed to be a model name
|
|
44
|
+
/// only if it starts with an upper-case letter. Otherwise, it is treated as a field
|
|
45
|
+
/// name, and returned in the `fieldNames` array.
|
|
46
|
+
///
|
|
47
|
+
/// Arguments:
|
|
48
|
+
/// fieldName: string
|
|
49
|
+
/// A model name, field name, or fully qualified field name to parse.
|
|
50
|
+
///
|
|
51
|
+
/// Return: object
|
|
52
|
+
/// An object with the shape `{ modelName: string | undefined; fieldNames: Array<string> }`.
|
|
53
|
+
/// The `modelName` property will contain the name of the model for the field, if it is known,
|
|
54
|
+
/// which will only be the case for a fully qualified name (i.e. `'User:email'`), or a
|
|
55
|
+
/// model name alone (i.e. `'User'`). `fieldNames` will always be an array of field names
|
|
56
|
+
/// parsed, which will generally only be one. It can be empty if no field name was provided,
|
|
57
|
+
/// for example if only a model name was provided (i.e. `'User'`).
|
|
14
58
|
function parseQualifiedName(str) {
|
|
15
59
|
let fieldNames = [];
|
|
16
60
|
let modelName;
|
package/lib/utils/query-utils.js
CHANGED
|
@@ -62,11 +62,11 @@ const FILTER_OPERATORS = {
|
|
|
62
62
|
},
|
|
63
63
|
// Like
|
|
64
64
|
'*': (Model, fieldName, query, value) => {
|
|
65
|
-
return query.LIKE(value);
|
|
65
|
+
return query.AND[fieldName].LIKE(value);
|
|
66
66
|
},
|
|
67
67
|
// NOT Like
|
|
68
68
|
'!*': (Model, fieldName, query, value) => {
|
|
69
|
-
return query.NOT_LIKE(value);
|
|
69
|
+
return query.AND[fieldName].NOT_LIKE(value);
|
|
70
70
|
},
|
|
71
71
|
};
|
|
72
72
|
|