@vsaas/loopback-datasource-juggler 10.0.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/LICENSE +25 -0
- package/NOTICE +23 -0
- package/README.md +74 -0
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/index.js +26 -0
- package/dist/lib/browser.depd.js +13 -0
- package/dist/lib/case-utils.js +21 -0
- package/dist/lib/connectors/kv-memory.js +158 -0
- package/dist/lib/connectors/memory.js +810 -0
- package/dist/lib/connectors/transient.js +126 -0
- package/dist/lib/dao.js +2445 -0
- package/dist/lib/datasource.js +2215 -0
- package/dist/lib/date-string.js +87 -0
- package/dist/lib/geo.js +244 -0
- package/dist/lib/globalize.js +33 -0
- package/dist/lib/hooks.js +79 -0
- package/dist/lib/id-utils.js +66 -0
- package/dist/lib/include.js +795 -0
- package/dist/lib/include_utils.js +104 -0
- package/dist/lib/introspection.js +37 -0
- package/dist/lib/jutil.js +65 -0
- package/dist/lib/kvao/delete-all.js +57 -0
- package/dist/lib/kvao/delete.js +43 -0
- package/dist/lib/kvao/expire.js +35 -0
- package/dist/lib/kvao/get.js +34 -0
- package/dist/lib/kvao/index.js +28 -0
- package/dist/lib/kvao/iterate-keys.js +38 -0
- package/dist/lib/kvao/keys.js +55 -0
- package/dist/lib/kvao/set.js +39 -0
- package/dist/lib/kvao/ttl.js +35 -0
- package/dist/lib/list.js +101 -0
- package/dist/lib/mixins.js +58 -0
- package/dist/lib/model-builder.js +608 -0
- package/dist/lib/model-definition.js +231 -0
- package/dist/lib/model-utils.js +368 -0
- package/dist/lib/model.js +586 -0
- package/dist/lib/observer.js +235 -0
- package/dist/lib/relation-definition.js +2604 -0
- package/dist/lib/relations.js +587 -0
- package/dist/lib/scope.js +392 -0
- package/dist/lib/transaction.js +183 -0
- package/dist/lib/types.js +58 -0
- package/dist/lib/utils.js +625 -0
- package/dist/lib/validations.js +742 -0
- package/dist/package.js +93 -0
- package/package.json +85 -0
- package/types/common.d.ts +28 -0
- package/types/connector.d.ts +52 -0
- package/types/datasource.d.ts +324 -0
- package/types/date-string.d.ts +21 -0
- package/types/inclusion-mixin.d.ts +44 -0
- package/types/index.d.ts +36 -0
- package/types/kv-model.d.ts +201 -0
- package/types/model.d.ts +368 -0
- package/types/observer-mixin.d.ts +174 -0
- package/types/persisted-model.d.ts +505 -0
- package/types/query.d.ts +108 -0
- package/types/relation-mixin.d.ts +577 -0
- package/types/relation.d.ts +301 -0
- package/types/scope.d.ts +92 -0
- package/types/transaction-mixin.d.ts +47 -0
- package/types/types.d.ts +65 -0
- package/types/validation-mixin.d.ts +287 -0
|
@@ -0,0 +1,2604 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.js");
|
|
3
|
+
const require_lib_globalize = require("./globalize.js");
|
|
4
|
+
const require_lib_utils = require("./utils.js");
|
|
5
|
+
const require_lib_validations = require("./validations.js");
|
|
6
|
+
const require_lib_model = require("./model.js");
|
|
7
|
+
const require_lib_scope = require("./scope.js");
|
|
8
|
+
const require_lib_connectors_memory = require("./connectors/memory.js");
|
|
9
|
+
//#region src/lib/relation-definition.ts
|
|
10
|
+
var require_relation_definition = /* @__PURE__ */ require_runtime.__commonJSMin(((exports) => {
|
|
11
|
+
/*!
|
|
12
|
+
* Dependencies
|
|
13
|
+
*/
|
|
14
|
+
const assert = require("assert");
|
|
15
|
+
const util = require("util");
|
|
16
|
+
const utils = require_lib_utils;
|
|
17
|
+
const i8n = require("inflection");
|
|
18
|
+
const defineScope = require_lib_scope.defineScope;
|
|
19
|
+
const g = require_lib_globalize();
|
|
20
|
+
const mergeQuery = utils.mergeQuery;
|
|
21
|
+
const idEquals = utils.idEquals;
|
|
22
|
+
const idsHaveDuplicates = utils.idsHaveDuplicates;
|
|
23
|
+
const ModelBaseClass = require_lib_model;
|
|
24
|
+
const applyFilter = require_lib_connectors_memory.applyFilter;
|
|
25
|
+
const ValidationError = require_lib_validations.ValidationError;
|
|
26
|
+
const deprecated = require("depd")("loopback-datasource-juggler");
|
|
27
|
+
const debug = require("debug")("loopback:relations");
|
|
28
|
+
function callbackToPromise(fn) {
|
|
29
|
+
return new Promise(function(resolve, reject) {
|
|
30
|
+
fn(function(err, result) {
|
|
31
|
+
if (err) return reject(err);
|
|
32
|
+
resolve(result);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const RelationTypes = {
|
|
37
|
+
belongsTo: "belongsTo",
|
|
38
|
+
hasMany: "hasMany",
|
|
39
|
+
hasOne: "hasOne",
|
|
40
|
+
hasAndBelongsToMany: "hasAndBelongsToMany",
|
|
41
|
+
referencesMany: "referencesMany",
|
|
42
|
+
embedsOne: "embedsOne",
|
|
43
|
+
embedsMany: "embedsMany"
|
|
44
|
+
};
|
|
45
|
+
const RelationClasses = {
|
|
46
|
+
belongsTo: BelongsTo,
|
|
47
|
+
hasMany: HasMany,
|
|
48
|
+
hasManyThrough: HasManyThrough,
|
|
49
|
+
hasOne: HasOne,
|
|
50
|
+
hasAndBelongsToMany: HasAndBelongsToMany,
|
|
51
|
+
referencesMany: ReferencesMany,
|
|
52
|
+
embedsOne: EmbedsOne,
|
|
53
|
+
embedsMany: EmbedsMany
|
|
54
|
+
};
|
|
55
|
+
exports.Relation = Relation;
|
|
56
|
+
exports.RelationDefinition = RelationDefinition;
|
|
57
|
+
exports.RelationTypes = RelationTypes;
|
|
58
|
+
exports.RelationClasses = RelationClasses;
|
|
59
|
+
exports.HasMany = HasMany;
|
|
60
|
+
exports.HasManyThrough = HasManyThrough;
|
|
61
|
+
exports.HasOne = HasOne;
|
|
62
|
+
exports.HasAndBelongsToMany = HasAndBelongsToMany;
|
|
63
|
+
exports.BelongsTo = BelongsTo;
|
|
64
|
+
exports.ReferencesMany = ReferencesMany;
|
|
65
|
+
exports.EmbedsOne = EmbedsOne;
|
|
66
|
+
exports.EmbedsMany = EmbedsMany;
|
|
67
|
+
function normalizeType(type) {
|
|
68
|
+
if (!type) return type;
|
|
69
|
+
const t1 = type.toLowerCase();
|
|
70
|
+
for (const t2 in RelationTypes) if (t2.toLowerCase() === t1) return t2;
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function extendScopeMethods(definition, scopeMethods, ext) {
|
|
74
|
+
let customMethods = [];
|
|
75
|
+
let relationClass = RelationClasses[definition.type];
|
|
76
|
+
if (definition.type === RelationTypes.hasMany && definition.modelThrough) relationClass = RelationClasses.hasManyThrough;
|
|
77
|
+
if (typeof ext === "function") customMethods = ext.call(definition, scopeMethods, relationClass);
|
|
78
|
+
else if (typeof ext === "object") {
|
|
79
|
+
function createFunc(definition, relationMethod) {
|
|
80
|
+
return function() {
|
|
81
|
+
const relation = new relationClass(definition, this);
|
|
82
|
+
return relationMethod.apply(relation, arguments);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
for (const key in ext) {
|
|
86
|
+
const relationMethod = ext[key];
|
|
87
|
+
const method = scopeMethods[key] = createFunc(definition, relationMethod);
|
|
88
|
+
if (relationMethod.shared) sharedMethod(definition, key, method, relationMethod);
|
|
89
|
+
customMethods.push(key);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return [].concat(customMethods || []);
|
|
93
|
+
}
|
|
94
|
+
function bindRelationMethods(relation, relationMethod, definition) {
|
|
95
|
+
const methods = definition.methods || {};
|
|
96
|
+
Object.keys(methods).forEach(function(m) {
|
|
97
|
+
if (typeof methods[m] !== "function") return;
|
|
98
|
+
relationMethod[m] = methods[m].bind(relation);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function preventFkOverride(inst, data, fkProp) {
|
|
102
|
+
if (!fkProp) return void 0;
|
|
103
|
+
if (data[fkProp] !== void 0 && !idEquals(data[fkProp], inst[fkProp])) return new Error(g.f("Cannot override foreign key %s from %s to %s", fkProp, inst[fkProp], data[fkProp]));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Relation definition class. Use to define relationships between models.
|
|
107
|
+
* @param {Object} definition
|
|
108
|
+
* @class RelationDefinition
|
|
109
|
+
*/
|
|
110
|
+
function RelationDefinition(definition) {
|
|
111
|
+
if (!(this instanceof RelationDefinition)) return new RelationDefinition(definition);
|
|
112
|
+
definition = definition || {};
|
|
113
|
+
this.name = definition.name;
|
|
114
|
+
assert(this.name, "Relation name is missing");
|
|
115
|
+
this.type = normalizeType(definition.type);
|
|
116
|
+
assert(this.type, "Invalid relation type: " + definition.type);
|
|
117
|
+
this.modelFrom = definition.modelFrom;
|
|
118
|
+
assert(this.modelFrom, "Source model is required");
|
|
119
|
+
this.keyFrom = definition.keyFrom;
|
|
120
|
+
this.modelTo = definition.modelTo;
|
|
121
|
+
this.keyTo = definition.keyTo;
|
|
122
|
+
this.polymorphic = definition.polymorphic;
|
|
123
|
+
if (typeof this.polymorphic !== "object") assert(this.modelTo, "Target model is required");
|
|
124
|
+
this.modelThrough = definition.modelThrough;
|
|
125
|
+
this.keyThrough = definition.keyThrough;
|
|
126
|
+
this.multiple = definition.multiple;
|
|
127
|
+
this.properties = definition.properties || {};
|
|
128
|
+
this.options = definition.options || {};
|
|
129
|
+
this.scope = definition.scope;
|
|
130
|
+
this.embed = definition.embed === true;
|
|
131
|
+
this.methods = definition.methods || {};
|
|
132
|
+
}
|
|
133
|
+
RelationDefinition.prototype.toJSON = function() {
|
|
134
|
+
const polymorphic = typeof this.polymorphic === "object";
|
|
135
|
+
let modelToName = this.modelTo && this.modelTo.modelName;
|
|
136
|
+
if (!modelToName && polymorphic && this.type === "belongsTo") modelToName = "<polymorphic>";
|
|
137
|
+
const json = {
|
|
138
|
+
name: this.name,
|
|
139
|
+
type: this.type,
|
|
140
|
+
modelFrom: this.modelFrom.modelName,
|
|
141
|
+
keyFrom: this.keyFrom,
|
|
142
|
+
modelTo: modelToName,
|
|
143
|
+
keyTo: this.keyTo,
|
|
144
|
+
multiple: this.multiple
|
|
145
|
+
};
|
|
146
|
+
if (this.modelThrough) {
|
|
147
|
+
json.modelThrough = this.modelThrough.modelName;
|
|
148
|
+
json.keyThrough = this.keyThrough;
|
|
149
|
+
}
|
|
150
|
+
if (polymorphic) json.polymorphic = this.polymorphic;
|
|
151
|
+
return json;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Define a relation scope method
|
|
155
|
+
* @param {String} name of the method
|
|
156
|
+
* @param {Function} function to define
|
|
157
|
+
*/
|
|
158
|
+
RelationDefinition.prototype.defineMethod = function(name, fn) {
|
|
159
|
+
const relationClass = RelationClasses[this.type];
|
|
160
|
+
const relationName = this.name;
|
|
161
|
+
const modelFrom = this.modelFrom;
|
|
162
|
+
const definition = this;
|
|
163
|
+
let method;
|
|
164
|
+
if (definition.multiple) {
|
|
165
|
+
const scope = this.modelFrom.scopes[this.name];
|
|
166
|
+
if (!scope) throw new Error(g.f("Unknown relation {{scope}}: %s", this.name));
|
|
167
|
+
method = scope.defineMethod(name, function() {
|
|
168
|
+
const relation = new relationClass(definition, this);
|
|
169
|
+
return fn.apply(relation, arguments);
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
definition.methods[name] = fn;
|
|
173
|
+
method = function() {
|
|
174
|
+
const rel = this[relationName];
|
|
175
|
+
return rel[name].apply(rel, arguments);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (method && fn.shared) {
|
|
179
|
+
sharedMethod(definition, name, method, fn);
|
|
180
|
+
modelFrom.prototype["__" + name + "__" + relationName] = method;
|
|
181
|
+
}
|
|
182
|
+
return method;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Apply the configured scope to the filter/query object.
|
|
186
|
+
* @param {Object} modelInstance
|
|
187
|
+
* @param {Object} filter (where, order, limit, fields, ...)
|
|
188
|
+
*/
|
|
189
|
+
RelationDefinition.prototype.applyScope = function(modelInstance, filter) {
|
|
190
|
+
filter = filter || {};
|
|
191
|
+
filter.where = filter.where || {};
|
|
192
|
+
if ((this.type !== "belongsTo" || this.type === "hasOne") && typeof this.polymorphic === "object") {
|
|
193
|
+
const discriminator = this.polymorphic.discriminator;
|
|
194
|
+
if (this.polymorphic.invert) filter.where[discriminator] = this.modelTo.modelName;
|
|
195
|
+
else filter.where[discriminator] = this.modelFrom.modelName;
|
|
196
|
+
}
|
|
197
|
+
let scope;
|
|
198
|
+
if (typeof this.scope === "function") scope = this.scope.call(this, modelInstance, filter);
|
|
199
|
+
else scope = this.scope;
|
|
200
|
+
if (typeof scope === "object") mergeQuery(filter, scope);
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Apply the configured properties to the target object.
|
|
204
|
+
* @param {Object} modelInstance
|
|
205
|
+
* @param {Object} target
|
|
206
|
+
*/
|
|
207
|
+
RelationDefinition.prototype.applyProperties = function(modelInstance, obj) {
|
|
208
|
+
let source = modelInstance, target = obj;
|
|
209
|
+
if (this.options.invertProperties) {
|
|
210
|
+
source = obj;
|
|
211
|
+
target = modelInstance;
|
|
212
|
+
}
|
|
213
|
+
if (this.options.embedsProperties) {
|
|
214
|
+
target = target.__data[this.name] = {};
|
|
215
|
+
target[this.keyTo] = source[this.keyTo];
|
|
216
|
+
}
|
|
217
|
+
let k, key;
|
|
218
|
+
if (typeof this.properties === "function") {
|
|
219
|
+
const data = this.properties.call(this, source, target);
|
|
220
|
+
for (k in data) target[k] = data[k];
|
|
221
|
+
} else if (Array.isArray(this.properties)) for (k = 0; k < this.properties.length; k++) {
|
|
222
|
+
key = this.properties[k];
|
|
223
|
+
target[key] = source[key];
|
|
224
|
+
}
|
|
225
|
+
else if (typeof this.properties === "object") for (k in this.properties) {
|
|
226
|
+
key = this.properties[k];
|
|
227
|
+
target[key] = source[k];
|
|
228
|
+
}
|
|
229
|
+
if ((this.type !== "belongsTo" || this.type === "hasOne") && typeof this.polymorphic === "object") {
|
|
230
|
+
const discriminator = this.polymorphic.discriminator;
|
|
231
|
+
if (this.polymorphic.invert) target[discriminator] = this.modelTo.modelName;
|
|
232
|
+
else target[discriminator] = this.modelFrom.modelName;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* A relation attaching to a given model instance
|
|
237
|
+
* @param {RelationDefinition|Object} definition
|
|
238
|
+
* @param {Object} modelInstance
|
|
239
|
+
* @returns {Relation}
|
|
240
|
+
* @constructor
|
|
241
|
+
* @class Relation
|
|
242
|
+
*/
|
|
243
|
+
function Relation(definition, modelInstance) {
|
|
244
|
+
if (!(this instanceof Relation)) return new Relation(definition, modelInstance);
|
|
245
|
+
if (!(definition instanceof RelationDefinition)) definition = new RelationDefinition(definition);
|
|
246
|
+
this.definition = definition;
|
|
247
|
+
this.modelInstance = modelInstance;
|
|
248
|
+
}
|
|
249
|
+
Relation.prototype.resetCache = function(cache) {
|
|
250
|
+
cache = cache || void 0;
|
|
251
|
+
this.modelInstance.__cachedRelations[this.definition.name] = cache;
|
|
252
|
+
};
|
|
253
|
+
Relation.prototype.getCache = function() {
|
|
254
|
+
return this.modelInstance.__cachedRelations[this.definition.name];
|
|
255
|
+
};
|
|
256
|
+
Relation.prototype.callScopeMethod = function(methodName) {
|
|
257
|
+
const args = Array.prototype.slice.call(arguments, 1);
|
|
258
|
+
const rel = this.modelInstance[this.definition.name];
|
|
259
|
+
if (rel && typeof rel[methodName] === "function") return rel[methodName].apply(rel, args);
|
|
260
|
+
else throw new Error(g.f("Unknown scope method: %s", methodName));
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Fetch the related model(s) - this is a helper method to unify access.
|
|
264
|
+
* @param (Boolean|Object} condOrRefresh refresh or conditions object
|
|
265
|
+
* @param {Object} [options] Options
|
|
266
|
+
* @param {Function} cb callback
|
|
267
|
+
*/
|
|
268
|
+
Relation.prototype.fetch = function(_condOrRefresh, _options, _cb) {
|
|
269
|
+
this.modelInstance[this.definition.name].apply(this.modelInstance, arguments);
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* HasMany subclass
|
|
273
|
+
* @param {RelationDefinition|Object} definition
|
|
274
|
+
* @param {Object} modelInstance
|
|
275
|
+
* @returns {HasMany}
|
|
276
|
+
* @constructor
|
|
277
|
+
* @class HasMany
|
|
278
|
+
*/
|
|
279
|
+
function HasMany(definition, modelInstance) {
|
|
280
|
+
if (!(this instanceof HasMany)) return new HasMany(definition, modelInstance);
|
|
281
|
+
assert(definition.type === RelationTypes.hasMany);
|
|
282
|
+
Relation.apply(this, arguments);
|
|
283
|
+
}
|
|
284
|
+
util.inherits(HasMany, Relation);
|
|
285
|
+
HasMany.prototype.removeFromCache = function(id) {
|
|
286
|
+
const cache = this.modelInstance.__cachedRelations[this.definition.name];
|
|
287
|
+
const idName = this.definition.modelTo.definition.idName();
|
|
288
|
+
if (Array.isArray(cache)) {
|
|
289
|
+
for (let i = 0, n = cache.length; i < n; i++) if (idEquals(cache[i][idName], id)) return cache.splice(i, 1);
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
};
|
|
293
|
+
HasMany.prototype.addToCache = function(inst) {
|
|
294
|
+
if (!inst) return;
|
|
295
|
+
let cache = this.modelInstance.__cachedRelations[this.definition.name];
|
|
296
|
+
if (cache === void 0) cache = this.modelInstance.__cachedRelations[this.definition.name] = [];
|
|
297
|
+
const idName = this.definition.modelTo.definition.idName();
|
|
298
|
+
if (Array.isArray(cache)) {
|
|
299
|
+
for (let i = 0, n = cache.length; i < n; i++) if (idEquals(cache[i][idName], inst[idName])) {
|
|
300
|
+
cache[i] = inst;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
cache.push(inst);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* HasManyThrough subclass
|
|
308
|
+
* @param {RelationDefinition|Object} definition
|
|
309
|
+
* @param {Object} modelInstance
|
|
310
|
+
* @returns {HasManyThrough}
|
|
311
|
+
* @constructor
|
|
312
|
+
* @class HasManyThrough
|
|
313
|
+
*/
|
|
314
|
+
function HasManyThrough(definition, modelInstance) {
|
|
315
|
+
if (!(this instanceof HasManyThrough)) return new HasManyThrough(definition, modelInstance);
|
|
316
|
+
assert(definition.type === RelationTypes.hasMany);
|
|
317
|
+
assert(definition.modelThrough);
|
|
318
|
+
HasMany.apply(this, arguments);
|
|
319
|
+
}
|
|
320
|
+
util.inherits(HasManyThrough, HasMany);
|
|
321
|
+
/**
|
|
322
|
+
* BelongsTo subclass
|
|
323
|
+
* @param {RelationDefinition|Object} definition
|
|
324
|
+
* @param {Object} modelInstance
|
|
325
|
+
* @returns {BelongsTo}
|
|
326
|
+
* @constructor
|
|
327
|
+
* @class BelongsTo
|
|
328
|
+
*/
|
|
329
|
+
function BelongsTo(definition, modelInstance) {
|
|
330
|
+
if (!(this instanceof BelongsTo)) return new BelongsTo(definition, modelInstance);
|
|
331
|
+
assert(definition.type === RelationTypes.belongsTo);
|
|
332
|
+
Relation.apply(this, arguments);
|
|
333
|
+
}
|
|
334
|
+
util.inherits(BelongsTo, Relation);
|
|
335
|
+
/**
|
|
336
|
+
* HasAndBelongsToMany subclass
|
|
337
|
+
* @param {RelationDefinition|Object} definition
|
|
338
|
+
* @param {Object} modelInstance
|
|
339
|
+
* @returns {HasAndBelongsToMany}
|
|
340
|
+
* @constructor
|
|
341
|
+
* @class HasAndBelongsToMany
|
|
342
|
+
*/
|
|
343
|
+
function HasAndBelongsToMany(definition, modelInstance) {
|
|
344
|
+
if (!(this instanceof HasAndBelongsToMany)) return new HasAndBelongsToMany(definition, modelInstance);
|
|
345
|
+
assert(definition.type === RelationTypes.hasAndBelongsToMany);
|
|
346
|
+
Relation.apply(this, arguments);
|
|
347
|
+
}
|
|
348
|
+
util.inherits(HasAndBelongsToMany, Relation);
|
|
349
|
+
/**
|
|
350
|
+
* HasOne subclass
|
|
351
|
+
* @param {RelationDefinition|Object} definition
|
|
352
|
+
* @param {Object} modelInstance
|
|
353
|
+
* @returns {HasOne}
|
|
354
|
+
* @constructor
|
|
355
|
+
* @class HasOne
|
|
356
|
+
*/
|
|
357
|
+
function HasOne(definition, modelInstance) {
|
|
358
|
+
if (!(this instanceof HasOne)) return new HasOne(definition, modelInstance);
|
|
359
|
+
assert(definition.type === RelationTypes.hasOne);
|
|
360
|
+
Relation.apply(this, arguments);
|
|
361
|
+
}
|
|
362
|
+
util.inherits(HasOne, Relation);
|
|
363
|
+
/**
|
|
364
|
+
* EmbedsOne subclass
|
|
365
|
+
* @param {RelationDefinition|Object} definition
|
|
366
|
+
* @param {Object} modelInstance
|
|
367
|
+
* @returns {EmbedsOne}
|
|
368
|
+
* @constructor
|
|
369
|
+
* @class EmbedsOne
|
|
370
|
+
*/
|
|
371
|
+
function EmbedsOne(definition, modelInstance) {
|
|
372
|
+
if (!(this instanceof EmbedsOne)) return new EmbedsOne(definition, modelInstance);
|
|
373
|
+
assert(definition.type === RelationTypes.embedsOne);
|
|
374
|
+
Relation.apply(this, arguments);
|
|
375
|
+
}
|
|
376
|
+
util.inherits(EmbedsOne, Relation);
|
|
377
|
+
/**
|
|
378
|
+
* EmbedsMany subclass
|
|
379
|
+
* @param {RelationDefinition|Object} definition
|
|
380
|
+
* @param {Object} modelInstance
|
|
381
|
+
* @returns {EmbedsMany}
|
|
382
|
+
* @constructor
|
|
383
|
+
* @class EmbedsMany
|
|
384
|
+
*/
|
|
385
|
+
function EmbedsMany(definition, modelInstance) {
|
|
386
|
+
if (!(this instanceof EmbedsMany)) return new EmbedsMany(definition, modelInstance);
|
|
387
|
+
assert(definition.type === RelationTypes.embedsMany);
|
|
388
|
+
Relation.apply(this, arguments);
|
|
389
|
+
}
|
|
390
|
+
util.inherits(EmbedsMany, Relation);
|
|
391
|
+
/**
|
|
392
|
+
* ReferencesMany subclass
|
|
393
|
+
* @param {RelationDefinition|Object} definition
|
|
394
|
+
* @param {Object} modelInstance
|
|
395
|
+
* @returns {ReferencesMany}
|
|
396
|
+
* @constructor
|
|
397
|
+
* @class ReferencesMany
|
|
398
|
+
*/
|
|
399
|
+
function ReferencesMany(definition, modelInstance) {
|
|
400
|
+
if (!(this instanceof ReferencesMany)) return new ReferencesMany(definition, modelInstance);
|
|
401
|
+
assert(definition.type === RelationTypes.referencesMany);
|
|
402
|
+
Relation.apply(this, arguments);
|
|
403
|
+
}
|
|
404
|
+
util.inherits(ReferencesMany, Relation);
|
|
405
|
+
/*!
|
|
406
|
+
* Find the relation by foreign key
|
|
407
|
+
* @param {*} foreignKey The foreign key
|
|
408
|
+
* @returns {Array} The array of matching relation objects
|
|
409
|
+
*/
|
|
410
|
+
function findBelongsTo(modelFrom, modelTo, keyTo) {
|
|
411
|
+
return Object.keys(modelFrom.relations).map(function(k) {
|
|
412
|
+
return modelFrom.relations[k];
|
|
413
|
+
}).filter(function(rel) {
|
|
414
|
+
return rel.type === RelationTypes.belongsTo && rel.modelTo === modelTo && (keyTo === void 0 || rel.keyTo === keyTo);
|
|
415
|
+
}).map(function(rel) {
|
|
416
|
+
return rel.keyFrom;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/*!
|
|
420
|
+
* Look up a model by name from the list of given models
|
|
421
|
+
* @param {Object} models Models keyed by name
|
|
422
|
+
* @param {String} modelName The model name
|
|
423
|
+
* @returns {*} The matching model class
|
|
424
|
+
*/
|
|
425
|
+
function lookupModel(models, modelName) {
|
|
426
|
+
if (models[modelName]) return models[modelName];
|
|
427
|
+
const lookupClassName = modelName.toLowerCase();
|
|
428
|
+
for (const name in models) if (name.toLowerCase() === lookupClassName) return models[name];
|
|
429
|
+
}
|
|
430
|
+
function lookupModelTo(modelFrom, modelToRef, params, singularize) {
|
|
431
|
+
let modelTo;
|
|
432
|
+
if (typeof modelToRef !== "string") modelTo = modelToRef;
|
|
433
|
+
else {
|
|
434
|
+
let modelToName;
|
|
435
|
+
modelTo = params.model || modelToRef;
|
|
436
|
+
if (typeof modelTo === "string") {
|
|
437
|
+
modelToName = modelTo;
|
|
438
|
+
modelToName = (singularize ? i8n.singularize(modelToName) : modelToName).toLowerCase();
|
|
439
|
+
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
440
|
+
}
|
|
441
|
+
if (!modelTo) {
|
|
442
|
+
const relationToName = params.as || modelToRef;
|
|
443
|
+
modelToName = (singularize ? i8n.singularize(relationToName) : relationToName).toLowerCase();
|
|
444
|
+
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (typeof modelTo !== "function") throw new Error(g.f("Could not find relation %s for model %s", params.as, modelFrom.modelName));
|
|
448
|
+
return modelTo;
|
|
449
|
+
}
|
|
450
|
+
function normalizeRelationAs(params, relationName) {
|
|
451
|
+
if (typeof relationName === "string") params.as = params.as || relationName;
|
|
452
|
+
return params;
|
|
453
|
+
}
|
|
454
|
+
function normalizePolymorphic(polymorphic, relationName) {
|
|
455
|
+
assert(polymorphic, "polymorphic param can't be false, null or undefined");
|
|
456
|
+
assert(!Array.isArray(polymorphic, "unexpected type for polymorphic param: 'Array'"));
|
|
457
|
+
let selector;
|
|
458
|
+
if (typeof polymorphic === "string") selector = polymorphic;
|
|
459
|
+
if (polymorphic === true) selector = relationName;
|
|
460
|
+
if (typeof polymorphic == "object") selector = polymorphic.selector || polymorphic.as;
|
|
461
|
+
selector = selector || relationName || "reference";
|
|
462
|
+
if (typeof polymorphic !== "object") polymorphic = {};
|
|
463
|
+
polymorphic.selector = selector;
|
|
464
|
+
polymorphic.foreignKey = polymorphic.foreignKey || i8n.camelize(selector + "_id", true);
|
|
465
|
+
polymorphic.discriminator = polymorphic.discriminator || i8n.camelize(selector + "_type", true);
|
|
466
|
+
return polymorphic;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Define a "one to many" relationship by specifying the model name
|
|
470
|
+
*
|
|
471
|
+
* Examples:
|
|
472
|
+
* ```
|
|
473
|
+
* User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'});
|
|
474
|
+
* ```
|
|
475
|
+
*
|
|
476
|
+
* ```
|
|
477
|
+
* Book.hasMany(Chapter);
|
|
478
|
+
* ```
|
|
479
|
+
* Or, equivalently:
|
|
480
|
+
* ```
|
|
481
|
+
* Book.hasMany('chapters', {model: Chapter});
|
|
482
|
+
* ```
|
|
483
|
+
* @param {Model} modelFrom Source model class
|
|
484
|
+
* @param {Object|String} modelToRef Reference to Model object to which you are
|
|
485
|
+
* creating the relation: model instance, model name, or name of relation to model.
|
|
486
|
+
* @options {Object} params Configuration parameters; see below.
|
|
487
|
+
* @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model.
|
|
488
|
+
* @property {String} foreignKey Property name of foreign key field.
|
|
489
|
+
* @property {Object} model Model object
|
|
490
|
+
*/
|
|
491
|
+
RelationDefinition.hasMany = function hasMany(modelFrom, modelToRef, params) {
|
|
492
|
+
const thisClassName = modelFrom.modelName;
|
|
493
|
+
params = params || {};
|
|
494
|
+
normalizeRelationAs(params, modelToRef);
|
|
495
|
+
const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
|
|
496
|
+
const relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
|
497
|
+
let fk = params.foreignKey || i8n.camelize(thisClassName + "_id", true);
|
|
498
|
+
let keyThrough = params.keyThrough || i8n.camelize(modelTo.modelName + "_id", true);
|
|
499
|
+
const pkName = params.primaryKey || modelFrom.dataSource.idName(modelFrom.modelName) || "id";
|
|
500
|
+
let discriminator, polymorphic;
|
|
501
|
+
if (params.polymorphic) {
|
|
502
|
+
polymorphic = normalizePolymorphic(params.polymorphic, relationName);
|
|
503
|
+
if (params.invert) {
|
|
504
|
+
polymorphic.invert = true;
|
|
505
|
+
keyThrough = polymorphic.foreignKey;
|
|
506
|
+
}
|
|
507
|
+
discriminator = polymorphic.discriminator;
|
|
508
|
+
if (!params.invert) fk = polymorphic.foreignKey;
|
|
509
|
+
if (!params.through) modelTo.dataSource.defineProperty(modelTo.modelName, discriminator, {
|
|
510
|
+
type: "string",
|
|
511
|
+
index: true
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
const definition = new RelationDefinition({
|
|
515
|
+
name: relationName,
|
|
516
|
+
type: RelationTypes.hasMany,
|
|
517
|
+
modelFrom,
|
|
518
|
+
keyFrom: pkName,
|
|
519
|
+
keyTo: fk,
|
|
520
|
+
modelTo,
|
|
521
|
+
multiple: true,
|
|
522
|
+
properties: params.properties,
|
|
523
|
+
scope: params.scope,
|
|
524
|
+
options: params.options,
|
|
525
|
+
keyThrough,
|
|
526
|
+
polymorphic
|
|
527
|
+
});
|
|
528
|
+
definition.modelThrough = params.through;
|
|
529
|
+
modelFrom.relations[relationName] = definition;
|
|
530
|
+
if (!params.through) modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName, pkName);
|
|
531
|
+
const scopeMethods = {
|
|
532
|
+
findById: scopeMethod(definition, "findById"),
|
|
533
|
+
destroy: scopeMethod(definition, "destroyById"),
|
|
534
|
+
updateById: scopeMethod(definition, "updateById"),
|
|
535
|
+
exists: scopeMethod(definition, "exists")
|
|
536
|
+
};
|
|
537
|
+
const findByIdFunc = scopeMethods.findById;
|
|
538
|
+
modelFrom.prototype["__findById__" + relationName] = findByIdFunc;
|
|
539
|
+
const destroyByIdFunc = scopeMethods.destroy;
|
|
540
|
+
modelFrom.prototype["__destroyById__" + relationName] = destroyByIdFunc;
|
|
541
|
+
const updateByIdFunc = scopeMethods.updateById;
|
|
542
|
+
modelFrom.prototype["__updateById__" + relationName] = updateByIdFunc;
|
|
543
|
+
const existsByIdFunc = scopeMethods.exists;
|
|
544
|
+
modelFrom.prototype["__exists__" + relationName] = existsByIdFunc;
|
|
545
|
+
if (definition.modelThrough) {
|
|
546
|
+
scopeMethods.create = scopeMethod(definition, "create");
|
|
547
|
+
scopeMethods.add = scopeMethod(definition, "add");
|
|
548
|
+
scopeMethods.remove = scopeMethod(definition, "remove");
|
|
549
|
+
const addFunc = scopeMethods.add;
|
|
550
|
+
modelFrom.prototype["__link__" + relationName] = addFunc;
|
|
551
|
+
const removeFunc = scopeMethods.remove;
|
|
552
|
+
modelFrom.prototype["__unlink__" + relationName] = removeFunc;
|
|
553
|
+
} else {
|
|
554
|
+
scopeMethods.create = scopeMethod(definition, "create");
|
|
555
|
+
scopeMethods.build = scopeMethod(definition, "build");
|
|
556
|
+
}
|
|
557
|
+
const customMethods = extendScopeMethods(definition, scopeMethods, params.scopeMethods);
|
|
558
|
+
for (let i = 0; i < customMethods.length; i++) {
|
|
559
|
+
const methodName = customMethods[i];
|
|
560
|
+
const method = scopeMethods[methodName];
|
|
561
|
+
if (typeof method === "function" && method.shared === true) modelFrom.prototype["__" + methodName + "__" + relationName] = method;
|
|
562
|
+
}
|
|
563
|
+
defineScope(modelFrom.prototype, params.through || modelTo, relationName, function() {
|
|
564
|
+
const filter = {};
|
|
565
|
+
filter.where = {};
|
|
566
|
+
filter.where[fk] = this[pkName];
|
|
567
|
+
definition.applyScope(this, filter);
|
|
568
|
+
if (definition.modelThrough) {
|
|
569
|
+
let throughRelationName;
|
|
570
|
+
for (const r in definition.modelThrough.relations) {
|
|
571
|
+
const relation = definition.modelThrough.relations[r];
|
|
572
|
+
if (relation.type === RelationTypes.belongsTo && (relation.polymorphic && !relation.modelTo || relation.modelTo === definition.modelTo) && relation.keyFrom === definition.keyThrough) {
|
|
573
|
+
throughRelationName = relation.name;
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (definition.polymorphic && definition.polymorphic.invert) {
|
|
578
|
+
filter.collect = definition.polymorphic.selector;
|
|
579
|
+
filter.include = filter.collect;
|
|
580
|
+
} else {
|
|
581
|
+
filter.collect = throughRelationName || i8n.camelize(modelTo.modelName, true);
|
|
582
|
+
filter.include = filter.collect;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return filter;
|
|
586
|
+
}, scopeMethods, definition.options);
|
|
587
|
+
return definition;
|
|
588
|
+
};
|
|
589
|
+
function scopeMethod(definition, methodName) {
|
|
590
|
+
let relationClass = RelationClasses[definition.type];
|
|
591
|
+
if (definition.type === RelationTypes.hasMany && definition.modelThrough) relationClass = RelationClasses.hasManyThrough;
|
|
592
|
+
const method = function() {
|
|
593
|
+
const relation = new relationClass(definition, this);
|
|
594
|
+
return relation[methodName].apply(relation, arguments);
|
|
595
|
+
};
|
|
596
|
+
const relationMethod = relationClass.prototype[methodName];
|
|
597
|
+
if (relationMethod.shared) sharedMethod(definition, methodName, method, relationMethod);
|
|
598
|
+
return method;
|
|
599
|
+
}
|
|
600
|
+
function sharedMethod(definition, methodName, method, relationMethod) {
|
|
601
|
+
method.shared = true;
|
|
602
|
+
method.accepts = relationMethod.accepts;
|
|
603
|
+
method.returns = relationMethod.returns;
|
|
604
|
+
method.http = relationMethod.http;
|
|
605
|
+
method.description = relationMethod.description;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Find a related item by foreign key
|
|
609
|
+
* @param {*} fkId The foreign key
|
|
610
|
+
* @param {Object} [options] Options
|
|
611
|
+
* @param {Function} cb The callback function
|
|
612
|
+
*/
|
|
613
|
+
HasMany.prototype.findById = function(fkId, options, cb) {
|
|
614
|
+
if (typeof options === "function" && cb === void 0) {
|
|
615
|
+
cb = options;
|
|
616
|
+
options = {};
|
|
617
|
+
}
|
|
618
|
+
const modelTo = this.definition.modelTo;
|
|
619
|
+
const modelFrom = this.definition.modelFrom;
|
|
620
|
+
const fk = this.definition.keyTo;
|
|
621
|
+
const pk = this.definition.keyFrom;
|
|
622
|
+
const modelInstance = this.modelInstance;
|
|
623
|
+
const idName = this.definition.modelTo.definition.idName();
|
|
624
|
+
const filter = {};
|
|
625
|
+
filter.where = {};
|
|
626
|
+
filter.where[idName] = fkId;
|
|
627
|
+
filter.where[fk] = modelInstance[pk];
|
|
628
|
+
cb = cb || utils.createPromiseCallback();
|
|
629
|
+
if (filter.where[fk] === void 0) {
|
|
630
|
+
process.nextTick(cb);
|
|
631
|
+
return cb.promise;
|
|
632
|
+
}
|
|
633
|
+
this.definition.applyScope(modelInstance, filter);
|
|
634
|
+
modelTo.findOne(filter, options, function(err, inst) {
|
|
635
|
+
if (err) return cb(err);
|
|
636
|
+
if (!inst) {
|
|
637
|
+
err = new Error(g.f("No instance with {{id}} %s found for %s", fkId, modelTo.modelName));
|
|
638
|
+
err.statusCode = 404;
|
|
639
|
+
return cb(err);
|
|
640
|
+
}
|
|
641
|
+
if (inst[fk] != null && idEquals(inst[fk], modelInstance[pk])) cb(null, inst);
|
|
642
|
+
else {
|
|
643
|
+
err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", modelFrom.modelName, pk, modelInstance[pk], modelTo.modelName, fk, inst[fk]));
|
|
644
|
+
err.statusCode = 400;
|
|
645
|
+
cb(err);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
return cb.promise;
|
|
649
|
+
};
|
|
650
|
+
/**
|
|
651
|
+
* Find a related item by foreign key
|
|
652
|
+
* @param {*} fkId The foreign key
|
|
653
|
+
* @param {Object} [options] Options
|
|
654
|
+
* @param {Function} cb The callback function
|
|
655
|
+
*/
|
|
656
|
+
HasMany.prototype.exists = function(fkId, options, cb) {
|
|
657
|
+
if (typeof options === "function" && cb === void 0) {
|
|
658
|
+
cb = options;
|
|
659
|
+
options = {};
|
|
660
|
+
}
|
|
661
|
+
const fk = this.definition.keyTo;
|
|
662
|
+
const pk = this.definition.keyFrom;
|
|
663
|
+
const modelInstance = this.modelInstance;
|
|
664
|
+
cb = cb || utils.createPromiseCallback();
|
|
665
|
+
this.findById(fkId, function(err, inst) {
|
|
666
|
+
if (err) return cb(err);
|
|
667
|
+
if (!inst) return cb(null, false);
|
|
668
|
+
if (inst[fk] && inst[fk].toString() === modelInstance[pk].toString()) cb(null, true);
|
|
669
|
+
else cb(null, false);
|
|
670
|
+
});
|
|
671
|
+
return cb.promise;
|
|
672
|
+
};
|
|
673
|
+
/**
|
|
674
|
+
* Update a related item by foreign key
|
|
675
|
+
* @param {*} fkId The foreign key
|
|
676
|
+
* @param {Object} Changes to the data
|
|
677
|
+
* @param {Object} [options] Options
|
|
678
|
+
* @param {Function} cb The callback function
|
|
679
|
+
*/
|
|
680
|
+
HasMany.prototype.updateById = function(fkId, data, options, cb) {
|
|
681
|
+
if (typeof options === "function" && cb === void 0) {
|
|
682
|
+
cb = options;
|
|
683
|
+
options = {};
|
|
684
|
+
}
|
|
685
|
+
cb = cb || utils.createPromiseCallback();
|
|
686
|
+
const fk = this.definition.keyTo;
|
|
687
|
+
this.findById(fkId, options, function(err, inst) {
|
|
688
|
+
if (err) return cb && cb(err);
|
|
689
|
+
const fkErr = preventFkOverride(inst, data, fk);
|
|
690
|
+
if (fkErr) return cb(fkErr);
|
|
691
|
+
inst.updateAttributes(data, options, cb);
|
|
692
|
+
});
|
|
693
|
+
return cb.promise;
|
|
694
|
+
};
|
|
695
|
+
/**
|
|
696
|
+
* Delete a related item by foreign key
|
|
697
|
+
* @param {*} fkId The foreign key
|
|
698
|
+
* @param {Object} [options] Options
|
|
699
|
+
* @param {Function} cb The callback function
|
|
700
|
+
*/
|
|
701
|
+
HasMany.prototype.destroyById = function(fkId, options, cb) {
|
|
702
|
+
if (typeof options === "function" && cb === void 0) {
|
|
703
|
+
cb = options;
|
|
704
|
+
options = {};
|
|
705
|
+
}
|
|
706
|
+
cb = cb || utils.createPromiseCallback();
|
|
707
|
+
const self = this;
|
|
708
|
+
this.findById(fkId, options, function(err, inst) {
|
|
709
|
+
if (err) return cb(err);
|
|
710
|
+
self.removeFromCache(fkId);
|
|
711
|
+
inst.destroy(options, cb);
|
|
712
|
+
});
|
|
713
|
+
return cb.promise;
|
|
714
|
+
};
|
|
715
|
+
const throughKeys = function(definition) {
|
|
716
|
+
const modelThrough = definition.modelThrough;
|
|
717
|
+
const pk2 = definition.modelTo.definition.idName();
|
|
718
|
+
let fk1, fk2;
|
|
719
|
+
if (typeof definition.polymorphic === "object") {
|
|
720
|
+
fk1 = definition.keyTo;
|
|
721
|
+
if (definition.polymorphic.invert) fk2 = definition.polymorphic.foreignKey;
|
|
722
|
+
else fk2 = definition.keyThrough;
|
|
723
|
+
} else if (definition.modelFrom === definition.modelTo) return findBelongsTo(modelThrough, definition.modelTo, pk2).sort(function(fk1, _fk2) {
|
|
724
|
+
return definition.keyTo === fk1 ? -1 : 1;
|
|
725
|
+
});
|
|
726
|
+
else {
|
|
727
|
+
fk1 = findBelongsTo(modelThrough, definition.modelFrom, definition.keyFrom)[0];
|
|
728
|
+
fk2 = findBelongsTo(modelThrough, definition.modelTo, pk2)[0];
|
|
729
|
+
}
|
|
730
|
+
return [fk1, fk2];
|
|
731
|
+
};
|
|
732
|
+
/**
|
|
733
|
+
* Find a related item by foreign key
|
|
734
|
+
* @param {*} fkId The foreign key value
|
|
735
|
+
* @param {Object} [options] Options
|
|
736
|
+
* @param {Function} cb The callback function
|
|
737
|
+
*/
|
|
738
|
+
HasManyThrough.prototype.findById = function(fkId, options, cb) {
|
|
739
|
+
if (typeof options === "function" && cb === void 0) {
|
|
740
|
+
cb = options;
|
|
741
|
+
options = {};
|
|
742
|
+
}
|
|
743
|
+
const self = this;
|
|
744
|
+
const modelTo = this.definition.modelTo;
|
|
745
|
+
const pk = this.definition.keyFrom;
|
|
746
|
+
const modelInstance = this.modelInstance;
|
|
747
|
+
const modelThrough = this.definition.modelThrough;
|
|
748
|
+
cb = cb || utils.createPromiseCallback();
|
|
749
|
+
self.exists(fkId, options, function(err, exists) {
|
|
750
|
+
if (err || !exists) {
|
|
751
|
+
if (!err) {
|
|
752
|
+
err = new Error(g.f("No relation found in %s for (%s.%s,%s.%s)", modelThrough.modelName, self.definition.modelFrom.modelName, modelInstance[pk], modelTo.modelName, fkId));
|
|
753
|
+
err.statusCode = 404;
|
|
754
|
+
}
|
|
755
|
+
return cb(err);
|
|
756
|
+
}
|
|
757
|
+
modelTo.findById(fkId, options, function(err, inst) {
|
|
758
|
+
if (err) return cb(err);
|
|
759
|
+
if (!inst) {
|
|
760
|
+
err = new Error(g.f("No instance with id %s found for %s", fkId, modelTo.modelName));
|
|
761
|
+
err.statusCode = 404;
|
|
762
|
+
return cb(err);
|
|
763
|
+
}
|
|
764
|
+
cb(err, inst);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
return cb.promise;
|
|
768
|
+
};
|
|
769
|
+
/**
|
|
770
|
+
* Delete a related item by foreign key
|
|
771
|
+
* @param {*} fkId The foreign key
|
|
772
|
+
* @param {Object} [options] Options
|
|
773
|
+
* @param {Function} cb The callback function
|
|
774
|
+
*/
|
|
775
|
+
HasManyThrough.prototype.destroyById = function(fkId, options, cb) {
|
|
776
|
+
if (typeof options === "function" && cb === void 0) {
|
|
777
|
+
cb = options;
|
|
778
|
+
options = {};
|
|
779
|
+
}
|
|
780
|
+
const self = this;
|
|
781
|
+
const modelTo = this.definition.modelTo;
|
|
782
|
+
const pk = this.definition.keyFrom;
|
|
783
|
+
const modelInstance = this.modelInstance;
|
|
784
|
+
const modelThrough = this.definition.modelThrough;
|
|
785
|
+
cb = cb || utils.createPromiseCallback();
|
|
786
|
+
self.exists(fkId, options, function(err, exists) {
|
|
787
|
+
if (err || !exists) {
|
|
788
|
+
if (!err) {
|
|
789
|
+
err = new Error(g.f("No record found in %s for (%s.%s ,%s.%s)", modelThrough.modelName, self.definition.modelFrom.modelName, modelInstance[pk], modelTo.modelName, fkId));
|
|
790
|
+
err.statusCode = 404;
|
|
791
|
+
}
|
|
792
|
+
return cb(err);
|
|
793
|
+
}
|
|
794
|
+
self.remove(fkId, options, function(err) {
|
|
795
|
+
if (err) return cb(err);
|
|
796
|
+
modelTo.deleteById(fkId, options, cb);
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
return cb.promise;
|
|
800
|
+
};
|
|
801
|
+
HasManyThrough.prototype.create = function create(data, options, cb) {
|
|
802
|
+
if (typeof options === "function" && cb === void 0) {
|
|
803
|
+
cb = options;
|
|
804
|
+
options = {};
|
|
805
|
+
}
|
|
806
|
+
const self = this;
|
|
807
|
+
const definition = this.definition;
|
|
808
|
+
const modelTo = definition.modelTo;
|
|
809
|
+
const modelThrough = definition.modelThrough;
|
|
810
|
+
if (typeof data === "function" && !cb) {
|
|
811
|
+
cb = data;
|
|
812
|
+
data = {};
|
|
813
|
+
}
|
|
814
|
+
cb = cb || utils.createPromiseCallback();
|
|
815
|
+
const modelInstance = this.modelInstance;
|
|
816
|
+
modelTo.create(data, options, function(err, to) {
|
|
817
|
+
if (err) return cb(err, to);
|
|
818
|
+
const pk2 = definition.modelTo.definition.idName();
|
|
819
|
+
const keys = throughKeys(definition);
|
|
820
|
+
const fk1 = keys[0];
|
|
821
|
+
const fk2 = keys[1];
|
|
822
|
+
function createRelation(to, next) {
|
|
823
|
+
const d = {}, q = {}, filter = { where: q };
|
|
824
|
+
d[fk1] = q[fk1] = modelInstance[definition.keyFrom];
|
|
825
|
+
d[fk2] = q[fk2] = to[pk2];
|
|
826
|
+
definition.applyProperties(modelInstance, d);
|
|
827
|
+
definition.applyScope(modelInstance, filter);
|
|
828
|
+
modelThrough.findOrCreate(filter, d, options, function(e, _through) {
|
|
829
|
+
if (e) to.destroy(options, function() {
|
|
830
|
+
next(e);
|
|
831
|
+
});
|
|
832
|
+
else {
|
|
833
|
+
self.addToCache(to);
|
|
834
|
+
next(err, to);
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
if (!Array.isArray(to)) createRelation(to, cb);
|
|
839
|
+
else Promise.all(to.map(function(item) {
|
|
840
|
+
return callbackToPromise(function(done) {
|
|
841
|
+
createRelation(item, done);
|
|
842
|
+
});
|
|
843
|
+
})).then(function(results) {
|
|
844
|
+
cb(null, results);
|
|
845
|
+
}, cb);
|
|
846
|
+
});
|
|
847
|
+
return cb.promise;
|
|
848
|
+
};
|
|
849
|
+
/**
|
|
850
|
+
* Add the target model instance to the 'hasMany' relation
|
|
851
|
+
* @param {Object|ID} acInst The actual instance or id value
|
|
852
|
+
* @param {Object} [data] Optional data object for the through model to be created
|
|
853
|
+
* @param {Object} [options] Options
|
|
854
|
+
* @param {Function} [cb] Callback function
|
|
855
|
+
*/
|
|
856
|
+
HasManyThrough.prototype.add = function(acInst, data, options, cb) {
|
|
857
|
+
if (typeof options === "function" && cb === void 0) {
|
|
858
|
+
cb = options;
|
|
859
|
+
options = {};
|
|
860
|
+
}
|
|
861
|
+
const self = this;
|
|
862
|
+
const definition = this.definition;
|
|
863
|
+
const modelThrough = definition.modelThrough;
|
|
864
|
+
const pk1 = definition.keyFrom;
|
|
865
|
+
if (typeof data === "function") {
|
|
866
|
+
cb = data;
|
|
867
|
+
data = {};
|
|
868
|
+
}
|
|
869
|
+
const query = {};
|
|
870
|
+
data = data || {};
|
|
871
|
+
cb = cb || utils.createPromiseCallback();
|
|
872
|
+
const pk2 = definition.modelTo.definition.idName();
|
|
873
|
+
const keys = throughKeys(definition);
|
|
874
|
+
const fk1 = keys[0];
|
|
875
|
+
const fk2 = keys[1];
|
|
876
|
+
query[fk1] = this.modelInstance[pk1];
|
|
877
|
+
query[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
|
|
878
|
+
const filter = { where: query };
|
|
879
|
+
definition.applyScope(this.modelInstance, filter);
|
|
880
|
+
data[fk1] = this.modelInstance[pk1];
|
|
881
|
+
data[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
|
|
882
|
+
definition.applyProperties(this.modelInstance, data);
|
|
883
|
+
modelThrough.findOrCreate(filter, data, options, function(err, ac) {
|
|
884
|
+
if (!err) {
|
|
885
|
+
if (acInst instanceof definition.modelTo) self.addToCache(acInst);
|
|
886
|
+
}
|
|
887
|
+
cb(err, ac);
|
|
888
|
+
});
|
|
889
|
+
return cb.promise;
|
|
890
|
+
};
|
|
891
|
+
/**
|
|
892
|
+
* Check if the target model instance is related to the 'hasMany' relation
|
|
893
|
+
* @param {Object|ID} acInst The actual instance or id value
|
|
894
|
+
*/
|
|
895
|
+
HasManyThrough.prototype.exists = function(acInst, options, cb) {
|
|
896
|
+
if (typeof options === "function" && cb === void 0) {
|
|
897
|
+
cb = options;
|
|
898
|
+
options = {};
|
|
899
|
+
}
|
|
900
|
+
const definition = this.definition;
|
|
901
|
+
const modelThrough = definition.modelThrough;
|
|
902
|
+
const pk1 = definition.keyFrom;
|
|
903
|
+
const query = {};
|
|
904
|
+
const pk2 = definition.modelTo.definition.idName();
|
|
905
|
+
const keys = throughKeys(definition);
|
|
906
|
+
const fk1 = keys[0];
|
|
907
|
+
const fk2 = keys[1];
|
|
908
|
+
query[fk1] = this.modelInstance[pk1];
|
|
909
|
+
query[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
|
|
910
|
+
const filter = { where: query };
|
|
911
|
+
definition.applyScope(this.modelInstance, filter);
|
|
912
|
+
cb = cb || utils.createPromiseCallback();
|
|
913
|
+
modelThrough.count(filter.where, options, function(err, ac) {
|
|
914
|
+
cb(err, ac > 0);
|
|
915
|
+
});
|
|
916
|
+
return cb.promise;
|
|
917
|
+
};
|
|
918
|
+
/**
|
|
919
|
+
* Remove the target model instance from the 'hasMany' relation
|
|
920
|
+
* @param {Object|ID) acInst The actual instance or id value
|
|
921
|
+
*/
|
|
922
|
+
HasManyThrough.prototype.remove = function(acInst, options, cb) {
|
|
923
|
+
if (typeof options === "function" && cb === void 0) {
|
|
924
|
+
cb = options;
|
|
925
|
+
options = {};
|
|
926
|
+
}
|
|
927
|
+
const self = this;
|
|
928
|
+
const definition = this.definition;
|
|
929
|
+
const modelThrough = definition.modelThrough;
|
|
930
|
+
const pk1 = definition.keyFrom;
|
|
931
|
+
const query = {};
|
|
932
|
+
const pk2 = definition.modelTo.definition.idName();
|
|
933
|
+
const keys = throughKeys(definition);
|
|
934
|
+
const fk1 = keys[0];
|
|
935
|
+
const fk2 = keys[1];
|
|
936
|
+
query[fk1] = this.modelInstance[pk1];
|
|
937
|
+
query[fk2] = acInst instanceof definition.modelTo ? acInst[pk2] : acInst;
|
|
938
|
+
const filter = { where: query };
|
|
939
|
+
definition.applyScope(this.modelInstance, filter);
|
|
940
|
+
cb = cb || utils.createPromiseCallback();
|
|
941
|
+
modelThrough.deleteAll(filter.where, options, function(err) {
|
|
942
|
+
if (!err) self.removeFromCache(query[fk2]);
|
|
943
|
+
cb(err);
|
|
944
|
+
});
|
|
945
|
+
return cb.promise;
|
|
946
|
+
};
|
|
947
|
+
/**
|
|
948
|
+
* Declare "belongsTo" relation that sets up a one-to-one connection with
|
|
949
|
+
* another model, such that each instance of the declaring model "belongs to"
|
|
950
|
+
* one instance of the other model.
|
|
951
|
+
*
|
|
952
|
+
* For example, if an application includes users and posts, and each post can
|
|
953
|
+
* be written by exactly one user. The following code specifies that `Post` has
|
|
954
|
+
* a reference called `author` to the `User` model via the `userId` property of
|
|
955
|
+
* `Post` as the foreign key.
|
|
956
|
+
* ```
|
|
957
|
+
* Post.belongsTo(User, {as: 'author', foreignKey: 'userId'});
|
|
958
|
+
* ```
|
|
959
|
+
*
|
|
960
|
+
* This optional parameter default value is false, so the related object will
|
|
961
|
+
* be loaded from cache if available.
|
|
962
|
+
*
|
|
963
|
+
* @param {Object|String} modelToRef Reference to Model object to which you are
|
|
964
|
+
* creating the relation: model instance, model name, or name of relation to model.
|
|
965
|
+
* @options {Object} params Configuration parameters; see below.
|
|
966
|
+
* @property {String} as Name of the property in the referring model that
|
|
967
|
+
* corresponds to the foreign key field in the related model.
|
|
968
|
+
* @property {String} foreignKey Name of foreign key property.
|
|
969
|
+
*
|
|
970
|
+
*/
|
|
971
|
+
RelationDefinition.belongsTo = function(modelFrom, modelToRef, params) {
|
|
972
|
+
let modelTo, discriminator, polymorphic;
|
|
973
|
+
params = params || {};
|
|
974
|
+
let pkName, relationName, fk;
|
|
975
|
+
if (params.polymorphic) {
|
|
976
|
+
relationName = params.as || (typeof modelToRef === "string" ? modelToRef : null);
|
|
977
|
+
polymorphic = normalizePolymorphic(params.polymorphic, relationName);
|
|
978
|
+
modelTo = null;
|
|
979
|
+
pkName = params.primaryKey || params.idName || "id";
|
|
980
|
+
fk = polymorphic.foreignKey;
|
|
981
|
+
discriminator = polymorphic.discriminator;
|
|
982
|
+
if (polymorphic.idType) modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, {
|
|
983
|
+
type: polymorphic.idType,
|
|
984
|
+
index: true
|
|
985
|
+
});
|
|
986
|
+
else modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelFrom.modelName, pkName);
|
|
987
|
+
modelFrom.dataSource.defineProperty(modelFrom.modelName, discriminator, {
|
|
988
|
+
type: "string",
|
|
989
|
+
index: true
|
|
990
|
+
});
|
|
991
|
+
} else {
|
|
992
|
+
normalizeRelationAs(params, modelToRef);
|
|
993
|
+
modelTo = lookupModelTo(modelFrom, modelToRef, params);
|
|
994
|
+
pkName = params.primaryKey || modelTo.dataSource.idName(modelTo.modelName) || "id";
|
|
995
|
+
relationName = params.as || i8n.camelize(modelTo.modelName, true);
|
|
996
|
+
fk = params.foreignKey || relationName + "Id";
|
|
997
|
+
modelFrom.dataSource.defineForeignKey(modelFrom.modelName, fk, modelTo.modelName, pkName);
|
|
998
|
+
}
|
|
999
|
+
const definition = modelFrom.relations[relationName] = new RelationDefinition({
|
|
1000
|
+
name: relationName,
|
|
1001
|
+
type: RelationTypes.belongsTo,
|
|
1002
|
+
modelFrom,
|
|
1003
|
+
keyFrom: fk,
|
|
1004
|
+
keyTo: pkName,
|
|
1005
|
+
modelTo,
|
|
1006
|
+
multiple: false,
|
|
1007
|
+
properties: params.properties,
|
|
1008
|
+
scope: params.scope,
|
|
1009
|
+
options: params.options,
|
|
1010
|
+
polymorphic,
|
|
1011
|
+
methods: params.methods
|
|
1012
|
+
});
|
|
1013
|
+
Object.defineProperty(modelFrom.prototype, relationName, {
|
|
1014
|
+
enumerable: true,
|
|
1015
|
+
configurable: true,
|
|
1016
|
+
get: function() {
|
|
1017
|
+
const relation = new BelongsTo(definition, this);
|
|
1018
|
+
const relationMethod = relation.related.bind(relation);
|
|
1019
|
+
relationMethod.get = relation.get.bind(relation);
|
|
1020
|
+
relationMethod.getAsync = function() {
|
|
1021
|
+
deprecated(g.f("BelongsTo method \"getAsync()\" is deprecated, use \"get()\" instead."));
|
|
1022
|
+
return this.get.apply(this, arguments);
|
|
1023
|
+
};
|
|
1024
|
+
relationMethod.update = relation.update.bind(relation);
|
|
1025
|
+
relationMethod.destroy = relation.destroy.bind(relation);
|
|
1026
|
+
if (!polymorphic) {
|
|
1027
|
+
relationMethod.create = relation.create.bind(relation);
|
|
1028
|
+
relationMethod.build = relation.build.bind(relation);
|
|
1029
|
+
relationMethod._targetClass = definition.modelTo.modelName;
|
|
1030
|
+
}
|
|
1031
|
+
bindRelationMethods(relation, relationMethod, definition);
|
|
1032
|
+
return relationMethod;
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
const fn = function() {
|
|
1036
|
+
this[relationName].apply(this, arguments);
|
|
1037
|
+
};
|
|
1038
|
+
modelFrom.prototype["__get__" + relationName] = fn;
|
|
1039
|
+
return definition;
|
|
1040
|
+
};
|
|
1041
|
+
BelongsTo.prototype.create = function(targetModelData, options, cb) {
|
|
1042
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1043
|
+
cb = options;
|
|
1044
|
+
options = {};
|
|
1045
|
+
}
|
|
1046
|
+
const self = this;
|
|
1047
|
+
const modelTo = this.definition.modelTo;
|
|
1048
|
+
const fk = this.definition.keyFrom;
|
|
1049
|
+
const pk = this.definition.keyTo;
|
|
1050
|
+
const modelInstance = this.modelInstance;
|
|
1051
|
+
if (typeof targetModelData === "function" && !cb) {
|
|
1052
|
+
cb = targetModelData;
|
|
1053
|
+
targetModelData = {};
|
|
1054
|
+
}
|
|
1055
|
+
cb = cb || utils.createPromiseCallback();
|
|
1056
|
+
this.definition.applyProperties(modelInstance, targetModelData || {});
|
|
1057
|
+
modelTo.create(targetModelData, options, function(err, targetModel) {
|
|
1058
|
+
if (!err) {
|
|
1059
|
+
modelInstance[fk] = targetModel[pk];
|
|
1060
|
+
if (modelInstance.isNewRecord()) {
|
|
1061
|
+
self.resetCache(targetModel);
|
|
1062
|
+
if (cb) cb(err, targetModel);
|
|
1063
|
+
} else modelInstance.save(options, function(err, _inst) {
|
|
1064
|
+
if (cb && err) return cb(err);
|
|
1065
|
+
self.resetCache(targetModel);
|
|
1066
|
+
if (cb) cb(err, targetModel);
|
|
1067
|
+
});
|
|
1068
|
+
} else if (cb) cb(err);
|
|
1069
|
+
});
|
|
1070
|
+
return cb.promise;
|
|
1071
|
+
};
|
|
1072
|
+
BelongsTo.prototype.build = function(targetModelData) {
|
|
1073
|
+
const modelTo = this.definition.modelTo;
|
|
1074
|
+
this.definition.applyProperties(this.modelInstance, targetModelData || {});
|
|
1075
|
+
return new modelTo(targetModelData);
|
|
1076
|
+
};
|
|
1077
|
+
BelongsTo.prototype.update = function(targetModelData, options, cb) {
|
|
1078
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1079
|
+
cb = options;
|
|
1080
|
+
options = {};
|
|
1081
|
+
}
|
|
1082
|
+
cb = cb || utils.createPromiseCallback();
|
|
1083
|
+
const definition = this.definition;
|
|
1084
|
+
const fk = definition.keyTo;
|
|
1085
|
+
this.fetch(options, function(err, inst) {
|
|
1086
|
+
if (inst instanceof ModelBaseClass) {
|
|
1087
|
+
const fkErr = preventFkOverride(inst, targetModelData, fk);
|
|
1088
|
+
if (fkErr) return cb(fkErr);
|
|
1089
|
+
inst.updateAttributes(targetModelData, options, cb);
|
|
1090
|
+
} else cb(new Error(g.f("{{BelongsTo}} relation %s is empty", definition.name)));
|
|
1091
|
+
});
|
|
1092
|
+
return cb.promise;
|
|
1093
|
+
};
|
|
1094
|
+
BelongsTo.prototype.destroy = function(options, cb) {
|
|
1095
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1096
|
+
cb = options;
|
|
1097
|
+
options = {};
|
|
1098
|
+
}
|
|
1099
|
+
const definition = this.definition;
|
|
1100
|
+
const modelInstance = this.modelInstance;
|
|
1101
|
+
const fk = definition.keyFrom;
|
|
1102
|
+
cb = cb || utils.createPromiseCallback();
|
|
1103
|
+
this.fetch(options, function(err, targetModel) {
|
|
1104
|
+
if (targetModel instanceof ModelBaseClass) {
|
|
1105
|
+
modelInstance[fk] = null;
|
|
1106
|
+
modelInstance.save(options, function(err, targetModel) {
|
|
1107
|
+
if (cb && err) return cb(err);
|
|
1108
|
+
if (cb) cb(err, targetModel);
|
|
1109
|
+
});
|
|
1110
|
+
} else cb(new Error(g.f("{{BelongsTo}} relation %s is empty", definition.name)));
|
|
1111
|
+
});
|
|
1112
|
+
return cb.promise;
|
|
1113
|
+
};
|
|
1114
|
+
/**
|
|
1115
|
+
* Define the method for the belongsTo relation itself
|
|
1116
|
+
* It will support one of the following styles:
|
|
1117
|
+
* - order.customer(refresh, options, callback): Load the target model instance asynchronously
|
|
1118
|
+
* - order.customer(customer): Synchronous setter of the target model instance
|
|
1119
|
+
* - order.customer(): Synchronous getter of the target model instance
|
|
1120
|
+
*
|
|
1121
|
+
* @param refresh
|
|
1122
|
+
* @param params
|
|
1123
|
+
* @returns {*}
|
|
1124
|
+
*/
|
|
1125
|
+
BelongsTo.prototype.related = function(condOrRefresh, options, cb) {
|
|
1126
|
+
const self = this;
|
|
1127
|
+
const modelFrom = this.definition.modelFrom;
|
|
1128
|
+
let modelTo = this.definition.modelTo;
|
|
1129
|
+
const pk = this.definition.keyTo;
|
|
1130
|
+
const fk = this.definition.keyFrom;
|
|
1131
|
+
const modelInstance = this.modelInstance;
|
|
1132
|
+
let discriminator;
|
|
1133
|
+
let scopeQuery = null;
|
|
1134
|
+
let newValue;
|
|
1135
|
+
if (condOrRefresh instanceof ModelBaseClass && options === void 0 && cb === void 0) {
|
|
1136
|
+
newValue = condOrRefresh;
|
|
1137
|
+
condOrRefresh = false;
|
|
1138
|
+
} else if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
|
|
1139
|
+
cb = condOrRefresh;
|
|
1140
|
+
condOrRefresh = false;
|
|
1141
|
+
} else if (typeof options === "function" && cb === void 0) {
|
|
1142
|
+
cb = options;
|
|
1143
|
+
options = {};
|
|
1144
|
+
}
|
|
1145
|
+
if (!newValue) scopeQuery = condOrRefresh;
|
|
1146
|
+
if (typeof this.definition.polymorphic === "object") discriminator = this.definition.polymorphic.discriminator;
|
|
1147
|
+
let cachedValue;
|
|
1148
|
+
if (!condOrRefresh) cachedValue = self.getCache();
|
|
1149
|
+
if (newValue) {
|
|
1150
|
+
modelInstance[fk] = newValue[pk];
|
|
1151
|
+
if (discriminator) modelInstance[discriminator] = newValue.constructor.modelName;
|
|
1152
|
+
this.definition.applyProperties(modelInstance, newValue);
|
|
1153
|
+
self.resetCache(newValue);
|
|
1154
|
+
} else if (typeof cb === "function") {
|
|
1155
|
+
if (discriminator) {
|
|
1156
|
+
let modelToName = modelInstance[discriminator];
|
|
1157
|
+
if (typeof modelToName !== "string") throw new Error(g.f("{{Polymorphic}} model not found: `%s` not set", discriminator));
|
|
1158
|
+
modelToName = modelToName.toLowerCase();
|
|
1159
|
+
modelTo = lookupModel(modelFrom.dataSource.modelBuilder.models, modelToName);
|
|
1160
|
+
if (!modelTo) throw new Error(g.f("{{Polymorphic}} model not found: `%s`", modelToName));
|
|
1161
|
+
}
|
|
1162
|
+
if (cachedValue === void 0 || !(cachedValue instanceof ModelBaseClass)) {
|
|
1163
|
+
const query = { where: {} };
|
|
1164
|
+
query.where[pk] = modelInstance[fk];
|
|
1165
|
+
if (query.where[pk] === void 0 || query.where[pk] === null) return process.nextTick(cb);
|
|
1166
|
+
this.definition.applyScope(modelInstance, query);
|
|
1167
|
+
if (scopeQuery) mergeQuery(query, scopeQuery);
|
|
1168
|
+
if (Array.isArray(query.fields) && query.fields.indexOf(pk) === -1) query.fields.push(pk);
|
|
1169
|
+
modelTo.findOne(query, options, function(err, inst) {
|
|
1170
|
+
if (err) return cb(err);
|
|
1171
|
+
if (!inst) return cb(null, null);
|
|
1172
|
+
if (inst[pk] != null && modelInstance[fk] != null && inst[pk].toString() === modelInstance[fk].toString()) {
|
|
1173
|
+
self.resetCache(inst);
|
|
1174
|
+
cb(null, inst);
|
|
1175
|
+
} else {
|
|
1176
|
+
err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", self.definition.modelFrom.modelName, fk, modelInstance[fk], modelTo.modelName, pk, inst[pk]));
|
|
1177
|
+
err.statusCode = 400;
|
|
1178
|
+
cb(err);
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
return modelInstance[fk];
|
|
1182
|
+
} else {
|
|
1183
|
+
cb(null, cachedValue);
|
|
1184
|
+
return cachedValue;
|
|
1185
|
+
}
|
|
1186
|
+
} else if (condOrRefresh === void 0) return cachedValue;
|
|
1187
|
+
else {
|
|
1188
|
+
modelInstance[fk] = newValue;
|
|
1189
|
+
self.resetCache();
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
/**
|
|
1193
|
+
* Define a Promise-based method for the belongsTo relation itself
|
|
1194
|
+
* - order.customer.get(cb): Load the target model instance asynchronously
|
|
1195
|
+
*
|
|
1196
|
+
* @param {Function} cb Callback of the form function (err, inst)
|
|
1197
|
+
* @returns {Promise | Undefined} returns promise if callback is omitted
|
|
1198
|
+
*/
|
|
1199
|
+
BelongsTo.prototype.get = function(options, cb) {
|
|
1200
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1201
|
+
cb = options;
|
|
1202
|
+
options = {};
|
|
1203
|
+
}
|
|
1204
|
+
cb = cb || utils.createPromiseCallback();
|
|
1205
|
+
this.related(true, options, cb);
|
|
1206
|
+
return cb.promise;
|
|
1207
|
+
};
|
|
1208
|
+
/**
|
|
1209
|
+
* A hasAndBelongsToMany relation creates a direct many-to-many connection with
|
|
1210
|
+
* another model, with no intervening model. For example, if your application
|
|
1211
|
+
* includes users and groups, with each group having many users and each user
|
|
1212
|
+
* appearing in many groups, you could declare the models this way:
|
|
1213
|
+
* ```
|
|
1214
|
+
* User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'});
|
|
1215
|
+
* ```
|
|
1216
|
+
*
|
|
1217
|
+
* @param {Object|String} modelToRef Reference to Model object to which you are
|
|
1218
|
+
* creating the relation: model instance, model name, or name of relation to model.
|
|
1219
|
+
* @options {Object} params Configuration parameters; see below.
|
|
1220
|
+
* @property {String} as Name of the property in the referring model that
|
|
1221
|
+
* corresponds to the foreign key field in the related model.
|
|
1222
|
+
* @property {String} foreignKey Property name of foreign key field.
|
|
1223
|
+
* @property {Object} model Model object
|
|
1224
|
+
*/
|
|
1225
|
+
RelationDefinition.hasAndBelongsToMany = function hasAndBelongsToMany(modelFrom, modelToRef, params) {
|
|
1226
|
+
params = params || {};
|
|
1227
|
+
normalizeRelationAs(params, modelToRef);
|
|
1228
|
+
const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
|
|
1229
|
+
const models = modelFrom.dataSource.modelBuilder.models;
|
|
1230
|
+
if (!params.through) {
|
|
1231
|
+
if (params.polymorphic) throw new Error(g.f("{{Polymorphic}} relations need a through model"));
|
|
1232
|
+
if (params.throughTable) params.through = modelFrom.dataSource.define(params.throughTable);
|
|
1233
|
+
else {
|
|
1234
|
+
const name1 = modelFrom.modelName + modelTo.modelName;
|
|
1235
|
+
const name2 = modelTo.modelName + modelFrom.modelName;
|
|
1236
|
+
params.through = lookupModel(models, name1) || lookupModel(models, name2) || modelFrom.dataSource.define(name1);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
const options = {
|
|
1240
|
+
as: params.as,
|
|
1241
|
+
through: params.through
|
|
1242
|
+
};
|
|
1243
|
+
options.properties = params.properties;
|
|
1244
|
+
options.scope = params.scope;
|
|
1245
|
+
options.options = params.options;
|
|
1246
|
+
if (params.polymorphic) {
|
|
1247
|
+
const relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
|
1248
|
+
const polymorphic = normalizePolymorphic(params.polymorphic, relationName);
|
|
1249
|
+
options.polymorphic = polymorphic;
|
|
1250
|
+
if (typeof params.through.prototype[polymorphic.selector] !== "function") params.through.belongsTo(polymorphic.selector, { polymorphic: true });
|
|
1251
|
+
} else params.through.belongsTo(modelFrom);
|
|
1252
|
+
params.through.belongsTo(modelTo);
|
|
1253
|
+
return this.hasMany(modelFrom, modelTo, options);
|
|
1254
|
+
};
|
|
1255
|
+
/**
|
|
1256
|
+
* A HasOne relation creates a one-to-one connection from modelFrom to modelTo.
|
|
1257
|
+
* This relation indicates that each instance of a model contains or possesses
|
|
1258
|
+
* one instance of another model. For example, each supplier in your application
|
|
1259
|
+
* has only one account.
|
|
1260
|
+
*
|
|
1261
|
+
* @param {Function} modelFrom The declaring model class
|
|
1262
|
+
* @param {Object|String} modelToRef Reference to Model object to which you are
|
|
1263
|
+
* creating the relation: model instance, model name, or name of relation to model.
|
|
1264
|
+
* @options {Object} params Configuration parameters; see below.
|
|
1265
|
+
* @property {String} as Name of the property in the referring model that
|
|
1266
|
+
* corresponds to the foreign key field in the related model.
|
|
1267
|
+
* @property {String} foreignKey Property name of foreign key field.
|
|
1268
|
+
* @property {Object} model Model object
|
|
1269
|
+
*/
|
|
1270
|
+
RelationDefinition.hasOne = function(modelFrom, modelToRef, params) {
|
|
1271
|
+
params = params || {};
|
|
1272
|
+
normalizeRelationAs(params, modelToRef);
|
|
1273
|
+
const modelTo = lookupModelTo(modelFrom, modelToRef, params);
|
|
1274
|
+
const pk = params.primaryKey || modelFrom.dataSource.idName(modelFrom.modelName) || "id";
|
|
1275
|
+
const relationName = params.as || i8n.camelize(modelTo.modelName, true);
|
|
1276
|
+
let fk = params.foreignKey || i8n.camelize(modelFrom.modelName + "_id", true);
|
|
1277
|
+
let discriminator, polymorphic;
|
|
1278
|
+
if (params.polymorphic) {
|
|
1279
|
+
polymorphic = normalizePolymorphic(params.polymorphic, relationName);
|
|
1280
|
+
fk = polymorphic.foreignKey;
|
|
1281
|
+
discriminator = polymorphic.discriminator;
|
|
1282
|
+
if (!params.through) modelTo.dataSource.defineProperty(modelTo.modelName, discriminator, {
|
|
1283
|
+
type: "string",
|
|
1284
|
+
index: true
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
const definition = modelFrom.relations[relationName] = new RelationDefinition({
|
|
1288
|
+
name: relationName,
|
|
1289
|
+
type: RelationTypes.hasOne,
|
|
1290
|
+
modelFrom,
|
|
1291
|
+
keyFrom: pk,
|
|
1292
|
+
keyTo: fk,
|
|
1293
|
+
modelTo,
|
|
1294
|
+
multiple: false,
|
|
1295
|
+
properties: params.properties,
|
|
1296
|
+
scope: params.scope,
|
|
1297
|
+
options: params.options,
|
|
1298
|
+
polymorphic,
|
|
1299
|
+
methods: params.methods
|
|
1300
|
+
});
|
|
1301
|
+
modelTo.dataSource.defineForeignKey(modelTo.modelName, fk, modelFrom.modelName, pk);
|
|
1302
|
+
Object.defineProperty(modelFrom.prototype, relationName, {
|
|
1303
|
+
enumerable: true,
|
|
1304
|
+
configurable: true,
|
|
1305
|
+
get: function() {
|
|
1306
|
+
const relation = new HasOne(definition, this);
|
|
1307
|
+
const relationMethod = relation.related.bind(relation);
|
|
1308
|
+
relationMethod.get = relation.get.bind(relation);
|
|
1309
|
+
relationMethod.getAsync = function() {
|
|
1310
|
+
deprecated(g.f("HasOne method \"getAsync()\" is deprecated, use \"get()\" instead."));
|
|
1311
|
+
return this.get.apply(this, arguments);
|
|
1312
|
+
};
|
|
1313
|
+
relationMethod.create = relation.create.bind(relation);
|
|
1314
|
+
relationMethod.build = relation.build.bind(relation);
|
|
1315
|
+
relationMethod.update = relation.update.bind(relation);
|
|
1316
|
+
relationMethod.destroy = relation.destroy.bind(relation);
|
|
1317
|
+
relationMethod._targetClass = definition.modelTo.modelName;
|
|
1318
|
+
bindRelationMethods(relation, relationMethod, definition);
|
|
1319
|
+
return relationMethod;
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
modelFrom.prototype["__get__" + relationName] = function() {
|
|
1323
|
+
this[relationName].apply(this, arguments);
|
|
1324
|
+
};
|
|
1325
|
+
modelFrom.prototype["__create__" + relationName] = function() {
|
|
1326
|
+
this[relationName].create.apply(this, arguments);
|
|
1327
|
+
};
|
|
1328
|
+
modelFrom.prototype["__update__" + relationName] = function() {
|
|
1329
|
+
this[relationName].update.apply(this, arguments);
|
|
1330
|
+
};
|
|
1331
|
+
modelFrom.prototype["__destroy__" + relationName] = function() {
|
|
1332
|
+
this[relationName].destroy.apply(this, arguments);
|
|
1333
|
+
};
|
|
1334
|
+
return definition;
|
|
1335
|
+
};
|
|
1336
|
+
/**
|
|
1337
|
+
* Create a target model instance
|
|
1338
|
+
* @param {Object} targetModelData The target model data
|
|
1339
|
+
* @callback {Function} [cb] Callback function
|
|
1340
|
+
* @param {String|Object} err Error string or object
|
|
1341
|
+
* @param {Object} The newly created target model instance
|
|
1342
|
+
*/
|
|
1343
|
+
HasOne.prototype.create = function(targetModelData, options, cb) {
|
|
1344
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1345
|
+
cb = options;
|
|
1346
|
+
options = {};
|
|
1347
|
+
}
|
|
1348
|
+
const self = this;
|
|
1349
|
+
const modelTo = this.definition.modelTo;
|
|
1350
|
+
const fk = this.definition.keyTo;
|
|
1351
|
+
const pk = this.definition.keyFrom;
|
|
1352
|
+
const modelInstance = this.modelInstance;
|
|
1353
|
+
if (typeof targetModelData === "function" && !cb) {
|
|
1354
|
+
cb = targetModelData;
|
|
1355
|
+
targetModelData = {};
|
|
1356
|
+
}
|
|
1357
|
+
targetModelData = targetModelData || {};
|
|
1358
|
+
cb = cb || utils.createPromiseCallback();
|
|
1359
|
+
targetModelData[fk] = modelInstance[pk];
|
|
1360
|
+
const query = { where: {} };
|
|
1361
|
+
query.where[fk] = targetModelData[fk];
|
|
1362
|
+
this.definition.applyScope(modelInstance, query);
|
|
1363
|
+
this.definition.applyProperties(modelInstance, targetModelData);
|
|
1364
|
+
modelTo.findOrCreate(query, targetModelData, options, function(err, targetModel, created) {
|
|
1365
|
+
if (err) return cb && cb(err);
|
|
1366
|
+
if (created) {
|
|
1367
|
+
self.resetCache(targetModel);
|
|
1368
|
+
if (cb) cb(err, targetModel);
|
|
1369
|
+
} else if (cb) cb(new Error(g.f("{{HasOne}} relation cannot create more than one instance of %s", modelTo.modelName)));
|
|
1370
|
+
});
|
|
1371
|
+
return cb.promise;
|
|
1372
|
+
};
|
|
1373
|
+
HasOne.prototype.update = function(targetModelData, options, cb) {
|
|
1374
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1375
|
+
cb = options;
|
|
1376
|
+
options = {};
|
|
1377
|
+
}
|
|
1378
|
+
cb = cb || utils.createPromiseCallback();
|
|
1379
|
+
const definition = this.definition;
|
|
1380
|
+
const fk = this.definition.keyTo;
|
|
1381
|
+
this.fetch(function(err, targetModel) {
|
|
1382
|
+
if (targetModel instanceof ModelBaseClass) {
|
|
1383
|
+
const fkErr = preventFkOverride(targetModel, targetModelData, fk);
|
|
1384
|
+
if (fkErr) return cb(fkErr);
|
|
1385
|
+
targetModel.updateAttributes(targetModelData, options, cb);
|
|
1386
|
+
} else cb(new Error(g.f("{{HasOne}} relation %s is empty", definition.name)));
|
|
1387
|
+
});
|
|
1388
|
+
return cb.promise;
|
|
1389
|
+
};
|
|
1390
|
+
HasOne.prototype.destroy = function(options, cb) {
|
|
1391
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1392
|
+
cb = options;
|
|
1393
|
+
options = {};
|
|
1394
|
+
}
|
|
1395
|
+
cb = cb || utils.createPromiseCallback();
|
|
1396
|
+
const definition = this.definition;
|
|
1397
|
+
this.fetch(function(err, targetModel) {
|
|
1398
|
+
if (targetModel instanceof ModelBaseClass) targetModel.destroy(options, cb);
|
|
1399
|
+
else cb(new Error(g.f("{{HasOne}} relation %s is empty", definition.name)));
|
|
1400
|
+
});
|
|
1401
|
+
return cb.promise;
|
|
1402
|
+
};
|
|
1403
|
+
/**
|
|
1404
|
+
* Create a target model instance
|
|
1405
|
+
* @param {Object} targetModelData The target model data
|
|
1406
|
+
* @callback {Function} [cb] Callback function
|
|
1407
|
+
* @param {String|Object} err Error string or object
|
|
1408
|
+
* @param {Object} The newly created target model instance
|
|
1409
|
+
*/
|
|
1410
|
+
HasMany.prototype.create = function(targetModelData, options, cb) {
|
|
1411
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1412
|
+
cb = options;
|
|
1413
|
+
options = {};
|
|
1414
|
+
}
|
|
1415
|
+
const self = this;
|
|
1416
|
+
const modelTo = this.definition.modelTo;
|
|
1417
|
+
const fk = this.definition.keyTo;
|
|
1418
|
+
const pk = this.definition.keyFrom;
|
|
1419
|
+
const modelInstance = this.modelInstance;
|
|
1420
|
+
if (typeof targetModelData === "function" && !cb) {
|
|
1421
|
+
cb = targetModelData;
|
|
1422
|
+
targetModelData = {};
|
|
1423
|
+
}
|
|
1424
|
+
targetModelData = targetModelData || {};
|
|
1425
|
+
cb = cb || utils.createPromiseCallback();
|
|
1426
|
+
const fkAndProps = function(item) {
|
|
1427
|
+
item[fk] = modelInstance[pk];
|
|
1428
|
+
self.definition.applyProperties(modelInstance, item);
|
|
1429
|
+
};
|
|
1430
|
+
const apply = function(data, fn) {
|
|
1431
|
+
if (Array.isArray(data)) data.forEach(fn);
|
|
1432
|
+
else fn(data);
|
|
1433
|
+
};
|
|
1434
|
+
apply(targetModelData, fkAndProps);
|
|
1435
|
+
modelTo.create(targetModelData, options, function(err, targetModel) {
|
|
1436
|
+
if (!err) {
|
|
1437
|
+
apply(targetModel, self.addToCache.bind(self));
|
|
1438
|
+
if (cb) cb(err, targetModel);
|
|
1439
|
+
} else if (cb) cb(err);
|
|
1440
|
+
});
|
|
1441
|
+
return cb.promise;
|
|
1442
|
+
};
|
|
1443
|
+
/**
|
|
1444
|
+
* Build a target model instance
|
|
1445
|
+
* @param {Object} targetModelData The target model data
|
|
1446
|
+
* @returns {Object} The newly built target model instance
|
|
1447
|
+
*/
|
|
1448
|
+
HasMany.prototype.build = HasOne.prototype.build = function(targetModelData) {
|
|
1449
|
+
const modelTo = this.definition.modelTo;
|
|
1450
|
+
const pk = this.definition.keyFrom;
|
|
1451
|
+
const fk = this.definition.keyTo;
|
|
1452
|
+
targetModelData = targetModelData || {};
|
|
1453
|
+
targetModelData[fk] = this.modelInstance[pk];
|
|
1454
|
+
this.definition.applyProperties(this.modelInstance, targetModelData);
|
|
1455
|
+
return new modelTo(targetModelData);
|
|
1456
|
+
};
|
|
1457
|
+
/**
|
|
1458
|
+
* Define the method for the hasOne relation itself
|
|
1459
|
+
* It will support one of the following styles:
|
|
1460
|
+
* - order.customer(refresh, callback): Load the target model instance asynchronously
|
|
1461
|
+
* - order.customer(customer): Synchronous setter of the target model instance
|
|
1462
|
+
* - order.customer(): Synchronous getter of the target model instance
|
|
1463
|
+
*
|
|
1464
|
+
* @param {Boolean} refresh Reload from the data source
|
|
1465
|
+
* @param {Object|Function} params Query parameters
|
|
1466
|
+
* @returns {Object}
|
|
1467
|
+
*/
|
|
1468
|
+
HasOne.prototype.related = function(condOrRefresh, options, cb) {
|
|
1469
|
+
const self = this;
|
|
1470
|
+
const modelTo = this.definition.modelTo;
|
|
1471
|
+
const fk = this.definition.keyTo;
|
|
1472
|
+
const pk = this.definition.keyFrom;
|
|
1473
|
+
const definition = this.definition;
|
|
1474
|
+
const modelInstance = this.modelInstance;
|
|
1475
|
+
let newValue;
|
|
1476
|
+
if (condOrRefresh instanceof ModelBaseClass && options === void 0 && cb === void 0) {
|
|
1477
|
+
newValue = condOrRefresh;
|
|
1478
|
+
condOrRefresh = false;
|
|
1479
|
+
} else if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
|
|
1480
|
+
cb = condOrRefresh;
|
|
1481
|
+
condOrRefresh = false;
|
|
1482
|
+
} else if (typeof options === "function" && cb === void 0) {
|
|
1483
|
+
cb = options;
|
|
1484
|
+
options = {};
|
|
1485
|
+
}
|
|
1486
|
+
let cachedValue;
|
|
1487
|
+
if (!condOrRefresh) cachedValue = self.getCache();
|
|
1488
|
+
if (newValue) {
|
|
1489
|
+
newValue[fk] = modelInstance[pk];
|
|
1490
|
+
self.resetCache(newValue);
|
|
1491
|
+
} else if (typeof cb === "function") if (cachedValue === void 0) {
|
|
1492
|
+
const query = { where: {} };
|
|
1493
|
+
query.where[fk] = modelInstance[pk];
|
|
1494
|
+
definition.applyScope(modelInstance, query);
|
|
1495
|
+
modelTo.findOne(query, options, function(err, inst) {
|
|
1496
|
+
if (err) return cb(err);
|
|
1497
|
+
if (!inst) return cb(null, null);
|
|
1498
|
+
if (inst[fk] != null && modelInstance[pk] != null && inst[fk].toString() === modelInstance[pk].toString()) {
|
|
1499
|
+
self.resetCache(inst);
|
|
1500
|
+
cb(null, inst);
|
|
1501
|
+
} else {
|
|
1502
|
+
err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", self.definition.modelFrom.modelName, pk, modelInstance[pk], modelTo.modelName, fk, inst[fk]));
|
|
1503
|
+
err.statusCode = 400;
|
|
1504
|
+
cb(err);
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
1507
|
+
return modelInstance[pk];
|
|
1508
|
+
} else {
|
|
1509
|
+
cb(null, cachedValue);
|
|
1510
|
+
return cachedValue;
|
|
1511
|
+
}
|
|
1512
|
+
else if (condOrRefresh === void 0) return cachedValue;
|
|
1513
|
+
else {
|
|
1514
|
+
newValue[fk] = modelInstance[pk];
|
|
1515
|
+
self.resetCache();
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
/**
|
|
1519
|
+
* Define a Promise-based method for the hasOne relation itself
|
|
1520
|
+
* - order.customer.get(cb): Load the target model instance asynchronously
|
|
1521
|
+
*
|
|
1522
|
+
* @param {Function} cb Callback of the form function (err, inst)
|
|
1523
|
+
* @returns {Promise | Undefined} Returns promise if cb is omitted
|
|
1524
|
+
*/
|
|
1525
|
+
HasOne.prototype.get = function(options, cb) {
|
|
1526
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1527
|
+
cb = options;
|
|
1528
|
+
options = {};
|
|
1529
|
+
}
|
|
1530
|
+
cb = cb || utils.createPromiseCallback();
|
|
1531
|
+
this.related(true, cb);
|
|
1532
|
+
return cb.promise;
|
|
1533
|
+
};
|
|
1534
|
+
RelationDefinition.embedsOne = function(modelFrom, modelToRef, params) {
|
|
1535
|
+
params = params || {};
|
|
1536
|
+
normalizeRelationAs(params, modelToRef);
|
|
1537
|
+
const modelTo = lookupModelTo(modelFrom, modelToRef, params);
|
|
1538
|
+
modelFrom.modelName;
|
|
1539
|
+
const relationName = params.as || i8n.camelize(modelTo.modelName, true) + "Item";
|
|
1540
|
+
let propertyName = params.property || i8n.camelize(modelTo.modelName, true);
|
|
1541
|
+
const idName = modelTo.dataSource.idName(modelTo.modelName) || "id";
|
|
1542
|
+
if (relationName === propertyName) {
|
|
1543
|
+
propertyName = "_" + propertyName;
|
|
1544
|
+
debug("EmbedsOne property cannot be equal to relation name: forcing property %s for relation %s", propertyName, relationName);
|
|
1545
|
+
}
|
|
1546
|
+
const definition = modelFrom.relations[relationName] = new RelationDefinition({
|
|
1547
|
+
name: relationName,
|
|
1548
|
+
type: RelationTypes.embedsOne,
|
|
1549
|
+
modelFrom,
|
|
1550
|
+
keyFrom: propertyName,
|
|
1551
|
+
keyTo: idName,
|
|
1552
|
+
modelTo,
|
|
1553
|
+
multiple: false,
|
|
1554
|
+
properties: params.properties,
|
|
1555
|
+
scope: params.scope,
|
|
1556
|
+
options: params.options,
|
|
1557
|
+
embed: true,
|
|
1558
|
+
methods: params.methods
|
|
1559
|
+
});
|
|
1560
|
+
const opts = Object.assign(params.options && params.options.property ? params.options.property : {}, { type: modelTo });
|
|
1561
|
+
if (params.default === true) opts.default = function() {
|
|
1562
|
+
return new modelTo();
|
|
1563
|
+
};
|
|
1564
|
+
else if (typeof params.default === "object") opts.default = (function(def) {
|
|
1565
|
+
return function() {
|
|
1566
|
+
return new modelTo(def);
|
|
1567
|
+
};
|
|
1568
|
+
})(params.default);
|
|
1569
|
+
modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts);
|
|
1570
|
+
if (definition.options.validate !== false) modelFrom.validate(relationName, function(err) {
|
|
1571
|
+
const inst = this[propertyName];
|
|
1572
|
+
if (inst instanceof modelTo) {
|
|
1573
|
+
if (!inst.isValid()) {
|
|
1574
|
+
const first = Object.keys(inst.errors)[0];
|
|
1575
|
+
const msg = "is invalid: `" + first + "` " + inst.errors[first];
|
|
1576
|
+
this.errors.add(relationName, msg, "invalid");
|
|
1577
|
+
err(false);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
Object.defineProperty(modelFrom.prototype, relationName, {
|
|
1582
|
+
enumerable: true,
|
|
1583
|
+
configurable: true,
|
|
1584
|
+
get: function() {
|
|
1585
|
+
const relation = new EmbedsOne(definition, this);
|
|
1586
|
+
const relationMethod = relation.related.bind(relation);
|
|
1587
|
+
relationMethod.create = relation.create.bind(relation);
|
|
1588
|
+
relationMethod.build = relation.build.bind(relation);
|
|
1589
|
+
relationMethod.update = relation.update.bind(relation);
|
|
1590
|
+
relationMethod.destroy = relation.destroy.bind(relation);
|
|
1591
|
+
relationMethod.value = relation.embeddedValue.bind(relation);
|
|
1592
|
+
relationMethod._targetClass = definition.modelTo.modelName;
|
|
1593
|
+
bindRelationMethods(relation, relationMethod, definition);
|
|
1594
|
+
return relationMethod;
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
modelFrom.prototype["__get__" + relationName] = function() {
|
|
1598
|
+
this[relationName].apply(this, arguments);
|
|
1599
|
+
};
|
|
1600
|
+
modelFrom.prototype["__create__" + relationName] = function() {
|
|
1601
|
+
this[relationName].create.apply(this, arguments);
|
|
1602
|
+
};
|
|
1603
|
+
modelFrom.prototype["__update__" + relationName] = function() {
|
|
1604
|
+
this[relationName].update.apply(this, arguments);
|
|
1605
|
+
};
|
|
1606
|
+
modelFrom.prototype["__destroy__" + relationName] = function() {
|
|
1607
|
+
this[relationName].destroy.apply(this, arguments);
|
|
1608
|
+
};
|
|
1609
|
+
return definition;
|
|
1610
|
+
};
|
|
1611
|
+
EmbedsOne.prototype.related = function(condOrRefresh, options, cb) {
|
|
1612
|
+
const modelTo = this.definition.modelTo;
|
|
1613
|
+
const modelInstance = this.modelInstance;
|
|
1614
|
+
const propertyName = this.definition.keyFrom;
|
|
1615
|
+
let newValue;
|
|
1616
|
+
if (condOrRefresh instanceof ModelBaseClass && options === void 0 && cb === void 0) {
|
|
1617
|
+
newValue = condOrRefresh;
|
|
1618
|
+
condOrRefresh = false;
|
|
1619
|
+
} else if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
|
|
1620
|
+
cb = condOrRefresh;
|
|
1621
|
+
condOrRefresh = false;
|
|
1622
|
+
} else if (typeof options === "function" && cb === void 0) {
|
|
1623
|
+
cb = options;
|
|
1624
|
+
options = {};
|
|
1625
|
+
}
|
|
1626
|
+
if (newValue) {
|
|
1627
|
+
if (newValue instanceof modelTo) {
|
|
1628
|
+
this.definition.applyProperties(modelInstance, newValue);
|
|
1629
|
+
modelInstance.setAttribute(propertyName, newValue);
|
|
1630
|
+
}
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
const embeddedInstance = this.embeddedValue();
|
|
1634
|
+
if (embeddedInstance) embeddedInstance.__persisted = true;
|
|
1635
|
+
if (typeof cb === "function") process.nextTick(function() {
|
|
1636
|
+
cb(null, embeddedInstance);
|
|
1637
|
+
});
|
|
1638
|
+
else if (condOrRefresh === void 0) return embeddedInstance;
|
|
1639
|
+
};
|
|
1640
|
+
EmbedsOne.prototype.prepareEmbeddedInstance = function(inst) {
|
|
1641
|
+
if (inst && inst.triggerParent !== "function") {
|
|
1642
|
+
const self = this;
|
|
1643
|
+
const propertyName = this.definition.keyFrom;
|
|
1644
|
+
const modelInstance = this.modelInstance;
|
|
1645
|
+
if (this.definition.options.persistent) inst.__persisted = !!inst[this.definition.keyTo];
|
|
1646
|
+
else inst.__persisted = true;
|
|
1647
|
+
inst.triggerParent = function(actionName, callback) {
|
|
1648
|
+
if (actionName === "save") {
|
|
1649
|
+
const embeddedValue = self.embeddedValue();
|
|
1650
|
+
modelInstance.updateAttribute(propertyName, embeddedValue, function(err, modelInst) {
|
|
1651
|
+
callback(err, err ? null : modelInst);
|
|
1652
|
+
});
|
|
1653
|
+
} else if (actionName === "destroy") {
|
|
1654
|
+
modelInstance.unsetAttribute(propertyName, true);
|
|
1655
|
+
modelInstance.save(function(err, modelInst) {
|
|
1656
|
+
callback(err, modelInst);
|
|
1657
|
+
});
|
|
1658
|
+
} else process.nextTick(callback);
|
|
1659
|
+
};
|
|
1660
|
+
const originalTrigger = inst.trigger;
|
|
1661
|
+
inst.trigger = function(actionName, work, data, callback) {
|
|
1662
|
+
if (typeof work === "function") {
|
|
1663
|
+
const originalWork = work;
|
|
1664
|
+
work = function(next) {
|
|
1665
|
+
originalWork.call(this, function(done) {
|
|
1666
|
+
inst.triggerParent(actionName, function(_err, _inst) {
|
|
1667
|
+
next(done);
|
|
1668
|
+
});
|
|
1669
|
+
});
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
originalTrigger.call(this, actionName, work, data, callback);
|
|
1673
|
+
};
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
EmbedsOne.prototype.embeddedValue = function(modelInstance) {
|
|
1677
|
+
modelInstance = modelInstance || this.modelInstance;
|
|
1678
|
+
const embeddedValue = modelInstance[this.definition.keyFrom];
|
|
1679
|
+
this.prepareEmbeddedInstance(embeddedValue);
|
|
1680
|
+
return embeddedValue;
|
|
1681
|
+
};
|
|
1682
|
+
EmbedsOne.prototype.create = function(targetModelData, options, cb) {
|
|
1683
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1684
|
+
cb = options;
|
|
1685
|
+
options = {};
|
|
1686
|
+
}
|
|
1687
|
+
const modelTo = this.definition.modelTo;
|
|
1688
|
+
const propertyName = this.definition.keyFrom;
|
|
1689
|
+
const modelInstance = this.modelInstance;
|
|
1690
|
+
if (typeof targetModelData === "function" && !cb) {
|
|
1691
|
+
cb = targetModelData;
|
|
1692
|
+
targetModelData = {};
|
|
1693
|
+
}
|
|
1694
|
+
targetModelData = targetModelData || {};
|
|
1695
|
+
cb = cb || utils.createPromiseCallback();
|
|
1696
|
+
const inst = this.callScopeMethod("build", targetModelData);
|
|
1697
|
+
const updateEmbedded = function(callback) {
|
|
1698
|
+
if (modelInstance.isNewRecord()) {
|
|
1699
|
+
modelInstance.setAttribute(propertyName, inst);
|
|
1700
|
+
modelInstance.save(options, function(err) {
|
|
1701
|
+
callback(err, err ? null : inst);
|
|
1702
|
+
});
|
|
1703
|
+
} else modelInstance.updateAttribute(propertyName, inst, options, function(err) {
|
|
1704
|
+
callback(err, err ? null : inst);
|
|
1705
|
+
});
|
|
1706
|
+
};
|
|
1707
|
+
if (this.definition.options.persistent) inst.save(options, function(err) {
|
|
1708
|
+
if (err) return cb(err, inst);
|
|
1709
|
+
updateEmbedded(cb);
|
|
1710
|
+
});
|
|
1711
|
+
else {
|
|
1712
|
+
const context = {
|
|
1713
|
+
Model: modelTo,
|
|
1714
|
+
instance: inst,
|
|
1715
|
+
options: options || {},
|
|
1716
|
+
hookState: {}
|
|
1717
|
+
};
|
|
1718
|
+
modelTo.notifyObserversOf("before save", context, function(err) {
|
|
1719
|
+
if (err) return process.nextTick(function() {
|
|
1720
|
+
cb(err);
|
|
1721
|
+
});
|
|
1722
|
+
err = inst.isValid() ? null : new ValidationError(inst);
|
|
1723
|
+
if (err) process.nextTick(function() {
|
|
1724
|
+
cb(err);
|
|
1725
|
+
});
|
|
1726
|
+
else updateEmbedded(function(err, inst) {
|
|
1727
|
+
if (err) return cb(err);
|
|
1728
|
+
context.instance = inst;
|
|
1729
|
+
modelTo.notifyObserversOf("after save", context, function(err) {
|
|
1730
|
+
cb(err, err ? null : inst);
|
|
1731
|
+
});
|
|
1732
|
+
});
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
return cb.promise;
|
|
1736
|
+
};
|
|
1737
|
+
EmbedsOne.prototype.build = function(targetModelData) {
|
|
1738
|
+
const modelTo = this.definition.modelTo;
|
|
1739
|
+
const modelInstance = this.modelInstance;
|
|
1740
|
+
const propertyName = this.definition.keyFrom;
|
|
1741
|
+
const forceId = this.definition.options.forceId;
|
|
1742
|
+
const persistent = this.definition.options.persistent;
|
|
1743
|
+
const connector = modelTo.dataSource.connector;
|
|
1744
|
+
targetModelData = targetModelData || {};
|
|
1745
|
+
this.definition.applyProperties(modelInstance, targetModelData);
|
|
1746
|
+
const pk = this.definition.keyTo;
|
|
1747
|
+
const pkProp = modelTo.definition.properties[pk];
|
|
1748
|
+
let assignId = forceId || targetModelData[pk] === void 0;
|
|
1749
|
+
assignId = assignId && !persistent && pkProp && pkProp.generated;
|
|
1750
|
+
if (assignId && typeof connector.generateId === "function") {
|
|
1751
|
+
const id = connector.generateId(modelTo.modelName, targetModelData, pk);
|
|
1752
|
+
targetModelData[pk] = id;
|
|
1753
|
+
}
|
|
1754
|
+
const embeddedInstance = new modelTo(targetModelData);
|
|
1755
|
+
modelInstance[propertyName] = embeddedInstance;
|
|
1756
|
+
this.prepareEmbeddedInstance(embeddedInstance);
|
|
1757
|
+
return embeddedInstance;
|
|
1758
|
+
};
|
|
1759
|
+
EmbedsOne.prototype.update = function(targetModelData, options, cb) {
|
|
1760
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1761
|
+
cb = options;
|
|
1762
|
+
options = {};
|
|
1763
|
+
}
|
|
1764
|
+
const modelTo = this.definition.modelTo;
|
|
1765
|
+
const modelInstance = this.modelInstance;
|
|
1766
|
+
const propertyName = this.definition.keyFrom;
|
|
1767
|
+
const data = targetModelData instanceof ModelBaseClass ? targetModelData.toObject() : targetModelData;
|
|
1768
|
+
const embeddedInstance = this.embeddedValue();
|
|
1769
|
+
if (embeddedInstance instanceof modelTo) {
|
|
1770
|
+
cb = cb || utils.createPromiseCallback();
|
|
1771
|
+
const hookState = {};
|
|
1772
|
+
let context = {
|
|
1773
|
+
Model: modelTo,
|
|
1774
|
+
currentInstance: embeddedInstance,
|
|
1775
|
+
data,
|
|
1776
|
+
options: options || {},
|
|
1777
|
+
hookState
|
|
1778
|
+
};
|
|
1779
|
+
modelTo.notifyObserversOf("before save", context, function(err) {
|
|
1780
|
+
if (err) return cb(err);
|
|
1781
|
+
embeddedInstance.setAttributes(context.data);
|
|
1782
|
+
if (!embeddedInstance.isValid()) return cb(new ValidationError(embeddedInstance));
|
|
1783
|
+
modelInstance.save(function(err, inst) {
|
|
1784
|
+
if (err) return cb(err);
|
|
1785
|
+
context = {
|
|
1786
|
+
Model: modelTo,
|
|
1787
|
+
instance: inst ? inst[propertyName] : embeddedInstance,
|
|
1788
|
+
options: options || {},
|
|
1789
|
+
hookState
|
|
1790
|
+
};
|
|
1791
|
+
modelTo.notifyObserversOf("after save", context, function(err) {
|
|
1792
|
+
cb(err, context.instance);
|
|
1793
|
+
});
|
|
1794
|
+
});
|
|
1795
|
+
});
|
|
1796
|
+
} else if (!embeddedInstance && cb) return this.callScopeMethod("create", data, cb);
|
|
1797
|
+
else if (!embeddedInstance) return this.callScopeMethod("build", data);
|
|
1798
|
+
return cb.promise;
|
|
1799
|
+
};
|
|
1800
|
+
EmbedsOne.prototype.destroy = function(options, cb) {
|
|
1801
|
+
if (typeof options === "function" && cb === void 0) {
|
|
1802
|
+
cb = options;
|
|
1803
|
+
options = {};
|
|
1804
|
+
}
|
|
1805
|
+
cb = cb || utils.createPromiseCallback();
|
|
1806
|
+
const modelTo = this.definition.modelTo;
|
|
1807
|
+
const modelInstance = this.modelInstance;
|
|
1808
|
+
const propertyName = this.definition.keyFrom;
|
|
1809
|
+
const embeddedInstance = modelInstance[propertyName];
|
|
1810
|
+
if (!embeddedInstance) {
|
|
1811
|
+
cb();
|
|
1812
|
+
return cb.promise;
|
|
1813
|
+
}
|
|
1814
|
+
modelInstance.unsetAttribute(propertyName, true);
|
|
1815
|
+
const context = {
|
|
1816
|
+
Model: modelTo,
|
|
1817
|
+
instance: embeddedInstance,
|
|
1818
|
+
options: options || {},
|
|
1819
|
+
hookState: {}
|
|
1820
|
+
};
|
|
1821
|
+
modelTo.notifyObserversOf("before delete", context, function(err) {
|
|
1822
|
+
if (err) return cb(err);
|
|
1823
|
+
modelInstance.save(function(err, _result) {
|
|
1824
|
+
if (err) return cb(err);
|
|
1825
|
+
modelTo.notifyObserversOf("after delete", context, cb);
|
|
1826
|
+
});
|
|
1827
|
+
});
|
|
1828
|
+
return cb.promise;
|
|
1829
|
+
};
|
|
1830
|
+
RelationDefinition.embedsMany = function embedsMany(modelFrom, modelToRef, params) {
|
|
1831
|
+
params = params || {};
|
|
1832
|
+
normalizeRelationAs(params, modelToRef);
|
|
1833
|
+
const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
|
|
1834
|
+
modelFrom.modelName;
|
|
1835
|
+
const relationName = params.as || i8n.camelize(modelTo.modelName, true) + "List";
|
|
1836
|
+
let propertyName = params.property || i8n.camelize(modelTo.pluralModelName, true);
|
|
1837
|
+
const idName = modelTo.dataSource.idName(modelTo.modelName) || "id";
|
|
1838
|
+
if (relationName === propertyName) {
|
|
1839
|
+
propertyName = "_" + propertyName;
|
|
1840
|
+
debug("EmbedsMany property cannot be equal to relation name: forcing property %s for relation %s", propertyName, relationName);
|
|
1841
|
+
}
|
|
1842
|
+
const definition = modelFrom.relations[relationName] = new RelationDefinition({
|
|
1843
|
+
name: relationName,
|
|
1844
|
+
type: RelationTypes.embedsMany,
|
|
1845
|
+
modelFrom,
|
|
1846
|
+
keyFrom: propertyName,
|
|
1847
|
+
keyTo: idName,
|
|
1848
|
+
modelTo,
|
|
1849
|
+
multiple: true,
|
|
1850
|
+
properties: params.properties,
|
|
1851
|
+
scope: params.scope,
|
|
1852
|
+
options: params.options,
|
|
1853
|
+
embed: true
|
|
1854
|
+
});
|
|
1855
|
+
const opts = Object.assign(params.options && params.options.property ? params.options.property : {}, params.options && params.options.omitDefaultEmbeddedItem ? { type: [modelTo] } : {
|
|
1856
|
+
type: [modelTo],
|
|
1857
|
+
default: function() {
|
|
1858
|
+
return [];
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
modelFrom.dataSource.defineProperty(modelFrom.modelName, propertyName, opts);
|
|
1862
|
+
if (typeof modelTo.dataSource.connector.generateId !== "function") modelFrom.validate(propertyName, function(err) {
|
|
1863
|
+
const self = this;
|
|
1864
|
+
const embeddedList = this[propertyName] || [];
|
|
1865
|
+
let hasErrors = false;
|
|
1866
|
+
embeddedList.forEach(function(item, idx) {
|
|
1867
|
+
if (item instanceof modelTo && item[idName] == void 0) {
|
|
1868
|
+
hasErrors = true;
|
|
1869
|
+
let msg = "contains invalid item at index `" + idx + "`:";
|
|
1870
|
+
msg += " `" + idName + "` is blank";
|
|
1871
|
+
self.errors.add(propertyName, msg, "invalid");
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
if (hasErrors) err(false);
|
|
1875
|
+
});
|
|
1876
|
+
if (!params.polymorphic) modelFrom.validate(propertyName, function(err) {
|
|
1877
|
+
if (idsHaveDuplicates((this[propertyName] || []).map(function(m) {
|
|
1878
|
+
return m[idName] && m[idName].toString();
|
|
1879
|
+
}))) {
|
|
1880
|
+
this.errors.add(propertyName, "contains duplicate `" + idName + "`", "uniqueness");
|
|
1881
|
+
err(false);
|
|
1882
|
+
}
|
|
1883
|
+
}, { code: "uniqueness" });
|
|
1884
|
+
if (definition.options.validate !== false) modelFrom.validate(propertyName, function(err) {
|
|
1885
|
+
const self = this;
|
|
1886
|
+
const embeddedList = this[propertyName] || [];
|
|
1887
|
+
let hasErrors = false;
|
|
1888
|
+
embeddedList.forEach(function(item, idx) {
|
|
1889
|
+
if (item instanceof modelTo) {
|
|
1890
|
+
if (!item.isValid()) {
|
|
1891
|
+
hasErrors = true;
|
|
1892
|
+
const id = item[idName];
|
|
1893
|
+
const first = Object.keys(item.errors)[0];
|
|
1894
|
+
let msg = id ? "contains invalid item: `" + id + "`" : "contains invalid item at index `" + idx + "`";
|
|
1895
|
+
msg += " (`" + first + "` " + item.errors[first] + ")";
|
|
1896
|
+
self.errors.add(propertyName, msg, "invalid");
|
|
1897
|
+
}
|
|
1898
|
+
} else {
|
|
1899
|
+
hasErrors = true;
|
|
1900
|
+
self.errors.add(propertyName, "contains invalid item", "invalid");
|
|
1901
|
+
}
|
|
1902
|
+
});
|
|
1903
|
+
if (hasErrors) err(false);
|
|
1904
|
+
});
|
|
1905
|
+
const scopeMethods = {
|
|
1906
|
+
findById: scopeMethod(definition, "findById"),
|
|
1907
|
+
destroy: scopeMethod(definition, "destroyById"),
|
|
1908
|
+
updateById: scopeMethod(definition, "updateById"),
|
|
1909
|
+
exists: scopeMethod(definition, "exists"),
|
|
1910
|
+
add: scopeMethod(definition, "add"),
|
|
1911
|
+
remove: scopeMethod(definition, "remove"),
|
|
1912
|
+
get: scopeMethod(definition, "get"),
|
|
1913
|
+
set: scopeMethod(definition, "set"),
|
|
1914
|
+
unset: scopeMethod(definition, "unset"),
|
|
1915
|
+
at: scopeMethod(definition, "at"),
|
|
1916
|
+
value: scopeMethod(definition, "embeddedValue")
|
|
1917
|
+
};
|
|
1918
|
+
const findByIdFunc = scopeMethods.findById;
|
|
1919
|
+
modelFrom.prototype["__findById__" + relationName] = findByIdFunc;
|
|
1920
|
+
const destroyByIdFunc = scopeMethods.destroy;
|
|
1921
|
+
modelFrom.prototype["__destroyById__" + relationName] = destroyByIdFunc;
|
|
1922
|
+
const updateByIdFunc = scopeMethods.updateById;
|
|
1923
|
+
modelFrom.prototype["__updateById__" + relationName] = updateByIdFunc;
|
|
1924
|
+
const addFunc = scopeMethods.add;
|
|
1925
|
+
modelFrom.prototype["__link__" + relationName] = addFunc;
|
|
1926
|
+
const removeFunc = scopeMethods.remove;
|
|
1927
|
+
modelFrom.prototype["__unlink__" + relationName] = removeFunc;
|
|
1928
|
+
scopeMethods.create = scopeMethod(definition, "create");
|
|
1929
|
+
scopeMethods.build = scopeMethod(definition, "build");
|
|
1930
|
+
scopeMethods.related = scopeMethod(definition, "related");
|
|
1931
|
+
if (!definition.options.persistent) scopeMethods.destroyAll = scopeMethod(definition, "destroyAll");
|
|
1932
|
+
const customMethods = extendScopeMethods(definition, scopeMethods, params.scopeMethods);
|
|
1933
|
+
for (let i = 0; i < customMethods.length; i++) {
|
|
1934
|
+
const methodName = customMethods[i];
|
|
1935
|
+
const method = scopeMethods[methodName];
|
|
1936
|
+
if (typeof method === "function" && method.shared === true) modelFrom.prototype["__" + methodName + "__" + relationName] = method;
|
|
1937
|
+
}
|
|
1938
|
+
const scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function() {
|
|
1939
|
+
return {};
|
|
1940
|
+
}, scopeMethods, definition.options);
|
|
1941
|
+
scopeDefinition.related = scopeMethods.related;
|
|
1942
|
+
return definition;
|
|
1943
|
+
};
|
|
1944
|
+
EmbedsMany.prototype.prepareEmbeddedInstance = function(inst) {
|
|
1945
|
+
if (inst && inst.triggerParent !== "function") {
|
|
1946
|
+
const self = this;
|
|
1947
|
+
const propertyName = this.definition.keyFrom;
|
|
1948
|
+
const modelInstance = this.modelInstance;
|
|
1949
|
+
if (this.definition.options.persistent) inst.__persisted = !!inst[this.definition.keyTo];
|
|
1950
|
+
else inst.__persisted = true;
|
|
1951
|
+
inst.triggerParent = function(actionName, callback) {
|
|
1952
|
+
if (actionName === "save" || actionName === "destroy") {
|
|
1953
|
+
const embeddedList = self.embeddedList();
|
|
1954
|
+
if (actionName === "destroy") {
|
|
1955
|
+
const index = embeddedList.indexOf(inst);
|
|
1956
|
+
if (index > -1) embeddedList.splice(index, 1);
|
|
1957
|
+
}
|
|
1958
|
+
modelInstance.updateAttribute(propertyName, embeddedList, function(err, modelInst) {
|
|
1959
|
+
callback(err, err ? null : modelInst);
|
|
1960
|
+
});
|
|
1961
|
+
} else process.nextTick(callback);
|
|
1962
|
+
};
|
|
1963
|
+
const originalTrigger = inst.trigger;
|
|
1964
|
+
inst.trigger = function(actionName, work, data, callback) {
|
|
1965
|
+
if (typeof work === "function") {
|
|
1966
|
+
const originalWork = work;
|
|
1967
|
+
work = function(next) {
|
|
1968
|
+
originalWork.call(this, function(done) {
|
|
1969
|
+
inst.triggerParent(actionName, function(_err, _inst) {
|
|
1970
|
+
next(done);
|
|
1971
|
+
});
|
|
1972
|
+
});
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1975
|
+
originalTrigger.call(this, actionName, work, data, callback);
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
};
|
|
1979
|
+
EmbedsMany.prototype.embeddedList = EmbedsMany.prototype.embeddedValue = function(modelInstance) {
|
|
1980
|
+
modelInstance = modelInstance || this.modelInstance;
|
|
1981
|
+
const embeddedList = modelInstance[this.definition.keyFrom] || [];
|
|
1982
|
+
embeddedList.forEach(this.prepareEmbeddedInstance.bind(this));
|
|
1983
|
+
return embeddedList;
|
|
1984
|
+
};
|
|
1985
|
+
EmbedsMany.prototype.related = function(receiver, scopeParams, condOrRefresh, options, cb) {
|
|
1986
|
+
const modelTo = this.definition.modelTo;
|
|
1987
|
+
this.modelInstance;
|
|
1988
|
+
let actualCond = {};
|
|
1989
|
+
if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
|
|
1990
|
+
cb = condOrRefresh;
|
|
1991
|
+
condOrRefresh = false;
|
|
1992
|
+
} else if (typeof options === "function" && cb === void 0) {
|
|
1993
|
+
cb = options;
|
|
1994
|
+
options = {};
|
|
1995
|
+
}
|
|
1996
|
+
if (typeof condOrRefresh === "object") actualCond = condOrRefresh;
|
|
1997
|
+
let embeddedList = this.embeddedList(receiver);
|
|
1998
|
+
this.definition.applyScope(receiver, actualCond);
|
|
1999
|
+
const params = mergeQuery(actualCond, scopeParams);
|
|
2000
|
+
if (params.where && Object.keys(params.where).length > 0) embeddedList = embeddedList ? embeddedList.filter(applyFilter(params)) : embeddedList;
|
|
2001
|
+
const returnRelated = function(list) {
|
|
2002
|
+
if (params.include) modelTo.include(list, params.include, options, cb);
|
|
2003
|
+
else process.nextTick(function() {
|
|
2004
|
+
cb(null, list);
|
|
2005
|
+
});
|
|
2006
|
+
};
|
|
2007
|
+
returnRelated(embeddedList);
|
|
2008
|
+
};
|
|
2009
|
+
EmbedsMany.prototype.findById = function(fkId, options, cb) {
|
|
2010
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2011
|
+
cb = options;
|
|
2012
|
+
options = {};
|
|
2013
|
+
}
|
|
2014
|
+
const pk = this.definition.keyTo;
|
|
2015
|
+
const modelTo = this.definition.modelTo;
|
|
2016
|
+
this.modelInstance;
|
|
2017
|
+
const embeddedList = this.embeddedList();
|
|
2018
|
+
const find = function(id) {
|
|
2019
|
+
for (let i = 0; i < embeddedList.length; i++) {
|
|
2020
|
+
const item = embeddedList[i];
|
|
2021
|
+
if (idEquals(item[pk], id)) return item;
|
|
2022
|
+
}
|
|
2023
|
+
return null;
|
|
2024
|
+
};
|
|
2025
|
+
let item = find(fkId.toString());
|
|
2026
|
+
item = item instanceof modelTo ? item : null;
|
|
2027
|
+
if (typeof cb === "function") process.nextTick(function() {
|
|
2028
|
+
cb(null, item);
|
|
2029
|
+
});
|
|
2030
|
+
return item;
|
|
2031
|
+
};
|
|
2032
|
+
EmbedsMany.prototype.exists = function(fkId, options, cb) {
|
|
2033
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2034
|
+
cb = options;
|
|
2035
|
+
options = {};
|
|
2036
|
+
}
|
|
2037
|
+
const modelTo = this.definition.modelTo;
|
|
2038
|
+
return this.findById(fkId, options, function(err, inst) {
|
|
2039
|
+
if (cb) cb(err, inst instanceof modelTo);
|
|
2040
|
+
}) instanceof modelTo;
|
|
2041
|
+
};
|
|
2042
|
+
EmbedsMany.prototype.updateById = function(fkId, data, options, cb) {
|
|
2043
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2044
|
+
cb = options;
|
|
2045
|
+
options = {};
|
|
2046
|
+
}
|
|
2047
|
+
if (typeof data === "function") {
|
|
2048
|
+
cb = data;
|
|
2049
|
+
data = {};
|
|
2050
|
+
}
|
|
2051
|
+
options = options || {};
|
|
2052
|
+
const modelTo = this.definition.modelTo;
|
|
2053
|
+
const propertyName = this.definition.keyFrom;
|
|
2054
|
+
const modelInstance = this.modelInstance;
|
|
2055
|
+
const embeddedList = this.embeddedList();
|
|
2056
|
+
const inst = this.findById(fkId);
|
|
2057
|
+
if (inst instanceof modelTo) {
|
|
2058
|
+
const hookState = {};
|
|
2059
|
+
let context = {
|
|
2060
|
+
Model: modelTo,
|
|
2061
|
+
currentInstance: inst,
|
|
2062
|
+
data,
|
|
2063
|
+
options,
|
|
2064
|
+
hookState
|
|
2065
|
+
};
|
|
2066
|
+
modelTo.notifyObserversOf("before save", context, function(err) {
|
|
2067
|
+
if (err) return cb && cb(err);
|
|
2068
|
+
inst.setAttributes(data);
|
|
2069
|
+
err = inst.isValid() ? null : new ValidationError(inst);
|
|
2070
|
+
if (err && typeof cb === "function") return process.nextTick(function() {
|
|
2071
|
+
cb(err, inst);
|
|
2072
|
+
});
|
|
2073
|
+
context = {
|
|
2074
|
+
Model: modelTo,
|
|
2075
|
+
instance: inst,
|
|
2076
|
+
options,
|
|
2077
|
+
hookState
|
|
2078
|
+
};
|
|
2079
|
+
if (typeof cb === "function") modelInstance.updateAttribute(propertyName, embeddedList, options, function(err) {
|
|
2080
|
+
if (err) return cb(err, inst);
|
|
2081
|
+
modelTo.notifyObserversOf("after save", context, function(err) {
|
|
2082
|
+
cb(err, inst);
|
|
2083
|
+
});
|
|
2084
|
+
});
|
|
2085
|
+
else modelTo.notifyObserversOf("after save", context, function(err) {
|
|
2086
|
+
if (!err) return;
|
|
2087
|
+
debug("Unhandled error in \"after save\" hooks: %s", err.stack || err);
|
|
2088
|
+
});
|
|
2089
|
+
});
|
|
2090
|
+
} else if (typeof cb === "function") process.nextTick(function() {
|
|
2091
|
+
cb(null, null);
|
|
2092
|
+
});
|
|
2093
|
+
return inst;
|
|
2094
|
+
};
|
|
2095
|
+
EmbedsMany.prototype.destroyById = function(fkId, options, cb) {
|
|
2096
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2097
|
+
cb = options;
|
|
2098
|
+
options = {};
|
|
2099
|
+
}
|
|
2100
|
+
const modelTo = this.definition.modelTo;
|
|
2101
|
+
const propertyName = this.definition.keyFrom;
|
|
2102
|
+
const modelInstance = this.modelInstance;
|
|
2103
|
+
const embeddedList = this.embeddedList();
|
|
2104
|
+
const inst = fkId instanceof modelTo ? fkId : this.findById(fkId);
|
|
2105
|
+
if (inst instanceof modelTo) {
|
|
2106
|
+
const context = {
|
|
2107
|
+
Model: modelTo,
|
|
2108
|
+
instance: inst,
|
|
2109
|
+
options: options || {},
|
|
2110
|
+
hookState: {}
|
|
2111
|
+
};
|
|
2112
|
+
modelTo.notifyObserversOf("before delete", context, function(err) {
|
|
2113
|
+
if (err) return cb(err);
|
|
2114
|
+
const index = embeddedList.indexOf(inst);
|
|
2115
|
+
if (index > -1) embeddedList.splice(index, 1);
|
|
2116
|
+
if (typeof cb !== "function") return;
|
|
2117
|
+
modelInstance.updateAttribute(propertyName, embeddedList, context.options, function(err) {
|
|
2118
|
+
if (err) return cb(err);
|
|
2119
|
+
modelTo.notifyObserversOf("after delete", context, function(err) {
|
|
2120
|
+
cb(err);
|
|
2121
|
+
});
|
|
2122
|
+
});
|
|
2123
|
+
});
|
|
2124
|
+
} else if (typeof cb === "function") process.nextTick(cb);
|
|
2125
|
+
return inst;
|
|
2126
|
+
};
|
|
2127
|
+
EmbedsMany.prototype.destroyAll = function(where, options, cb) {
|
|
2128
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2129
|
+
cb = options;
|
|
2130
|
+
options = {};
|
|
2131
|
+
} else if (typeof where === "function" && options === void 0 && cb === void 0) {
|
|
2132
|
+
cb = where;
|
|
2133
|
+
where = {};
|
|
2134
|
+
}
|
|
2135
|
+
const propertyName = this.definition.keyFrom;
|
|
2136
|
+
const modelInstance = this.modelInstance;
|
|
2137
|
+
let embeddedList = this.embeddedList();
|
|
2138
|
+
if (where && Object.keys(where).length > 0) {
|
|
2139
|
+
const filter = applyFilter({ where });
|
|
2140
|
+
const reject = function(v) {
|
|
2141
|
+
return !filter(v);
|
|
2142
|
+
};
|
|
2143
|
+
embeddedList = embeddedList ? embeddedList.filter(reject) : embeddedList;
|
|
2144
|
+
} else embeddedList = [];
|
|
2145
|
+
if (typeof cb === "function") modelInstance.updateAttribute(propertyName, embeddedList, options || {}, function(err) {
|
|
2146
|
+
cb(err);
|
|
2147
|
+
});
|
|
2148
|
+
else modelInstance.setAttribute(propertyName, embeddedList, options || {});
|
|
2149
|
+
};
|
|
2150
|
+
EmbedsMany.prototype.get = EmbedsMany.prototype.findById;
|
|
2151
|
+
EmbedsMany.prototype.set = EmbedsMany.prototype.updateById;
|
|
2152
|
+
EmbedsMany.prototype.unset = EmbedsMany.prototype.destroyById;
|
|
2153
|
+
EmbedsMany.prototype.at = function(index, cb) {
|
|
2154
|
+
const modelTo = this.definition.modelTo;
|
|
2155
|
+
this.modelInstance;
|
|
2156
|
+
let item = this.embeddedList()[parseInt(index)];
|
|
2157
|
+
item = item instanceof modelTo ? item : null;
|
|
2158
|
+
if (typeof cb === "function") process.nextTick(function() {
|
|
2159
|
+
cb(null, item);
|
|
2160
|
+
});
|
|
2161
|
+
return item;
|
|
2162
|
+
};
|
|
2163
|
+
EmbedsMany.prototype.create = function(targetModelData, options, cb) {
|
|
2164
|
+
this.definition.keyTo;
|
|
2165
|
+
const modelTo = this.definition.modelTo;
|
|
2166
|
+
const propertyName = this.definition.keyFrom;
|
|
2167
|
+
const modelInstance = this.modelInstance;
|
|
2168
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2169
|
+
cb = options;
|
|
2170
|
+
options = {};
|
|
2171
|
+
}
|
|
2172
|
+
if (typeof targetModelData === "function" && !cb) {
|
|
2173
|
+
cb = targetModelData;
|
|
2174
|
+
targetModelData = {};
|
|
2175
|
+
}
|
|
2176
|
+
targetModelData = targetModelData || {};
|
|
2177
|
+
cb = cb || utils.createPromiseCallback();
|
|
2178
|
+
const inst = this.callScopeMethod("build", targetModelData);
|
|
2179
|
+
const embeddedList = this.embeddedList();
|
|
2180
|
+
const updateEmbedded = function(callback) {
|
|
2181
|
+
if (modelInstance.isNewRecord()) {
|
|
2182
|
+
modelInstance.setAttribute(propertyName, embeddedList);
|
|
2183
|
+
modelInstance.save(options, function(err) {
|
|
2184
|
+
callback(err, err ? null : inst);
|
|
2185
|
+
});
|
|
2186
|
+
} else modelInstance.updateAttribute(propertyName, embeddedList, options, function(err) {
|
|
2187
|
+
callback(err, err ? null : inst);
|
|
2188
|
+
});
|
|
2189
|
+
};
|
|
2190
|
+
if (this.definition.options.persistent) inst.save(function(err) {
|
|
2191
|
+
if (err) return cb(err, inst);
|
|
2192
|
+
updateEmbedded(cb);
|
|
2193
|
+
});
|
|
2194
|
+
else {
|
|
2195
|
+
const err = inst.isValid() ? null : new ValidationError(inst);
|
|
2196
|
+
if (err) process.nextTick(function() {
|
|
2197
|
+
cb(err);
|
|
2198
|
+
});
|
|
2199
|
+
else {
|
|
2200
|
+
const context = {
|
|
2201
|
+
Model: modelTo,
|
|
2202
|
+
instance: inst,
|
|
2203
|
+
options: options || {},
|
|
2204
|
+
hookState: {}
|
|
2205
|
+
};
|
|
2206
|
+
modelTo.notifyObserversOf("before save", context, function(err) {
|
|
2207
|
+
if (err) return cb(err);
|
|
2208
|
+
updateEmbedded(function(err, inst) {
|
|
2209
|
+
if (err) return cb(err, null);
|
|
2210
|
+
modelTo.notifyObserversOf("after save", context, function(err) {
|
|
2211
|
+
cb(err, err ? null : inst);
|
|
2212
|
+
});
|
|
2213
|
+
});
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return cb.promise;
|
|
2218
|
+
};
|
|
2219
|
+
EmbedsMany.prototype.build = function(targetModelData) {
|
|
2220
|
+
const modelTo = this.definition.modelTo;
|
|
2221
|
+
const modelInstance = this.modelInstance;
|
|
2222
|
+
const forceId = this.definition.options.forceId;
|
|
2223
|
+
const persistent = this.definition.options.persistent;
|
|
2224
|
+
const propertyName = this.definition.keyFrom;
|
|
2225
|
+
const connector = modelTo.dataSource.connector;
|
|
2226
|
+
const pk = this.definition.keyTo;
|
|
2227
|
+
const pkProp = modelTo.definition.properties[pk];
|
|
2228
|
+
const pkType = pkProp && pkProp.type;
|
|
2229
|
+
const embeddedList = this.embeddedList();
|
|
2230
|
+
targetModelData = targetModelData || {};
|
|
2231
|
+
let assignId = forceId || targetModelData[pk] === void 0;
|
|
2232
|
+
assignId = assignId && !persistent;
|
|
2233
|
+
if (assignId && pkType === Number) {
|
|
2234
|
+
const ids = embeddedList.map(function(m) {
|
|
2235
|
+
return typeof m[pk] === "number" ? m[pk] : 0;
|
|
2236
|
+
});
|
|
2237
|
+
if (ids.length > 0) targetModelData[pk] = Math.max.apply(null, ids) + 1;
|
|
2238
|
+
else targetModelData[pk] = 1;
|
|
2239
|
+
} else if (assignId && typeof connector.generateId === "function") {
|
|
2240
|
+
const id = connector.generateId(modelTo.modelName, targetModelData, pk);
|
|
2241
|
+
targetModelData[pk] = id;
|
|
2242
|
+
}
|
|
2243
|
+
this.definition.applyProperties(modelInstance, targetModelData);
|
|
2244
|
+
const inst = new modelTo(targetModelData);
|
|
2245
|
+
if (this.definition.options.prepend) {
|
|
2246
|
+
embeddedList.unshift(inst);
|
|
2247
|
+
modelInstance[propertyName] = embeddedList;
|
|
2248
|
+
} else {
|
|
2249
|
+
embeddedList.push(inst);
|
|
2250
|
+
modelInstance[propertyName] = embeddedList;
|
|
2251
|
+
}
|
|
2252
|
+
this.prepareEmbeddedInstance(inst);
|
|
2253
|
+
return inst;
|
|
2254
|
+
};
|
|
2255
|
+
/**
|
|
2256
|
+
* Add the target model instance to the 'embedsMany' relation
|
|
2257
|
+
* @param {Object|ID} acInst The actual instance or id value
|
|
2258
|
+
*/
|
|
2259
|
+
EmbedsMany.prototype.add = function(acInst, data, options, cb) {
|
|
2260
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2261
|
+
cb = options;
|
|
2262
|
+
options = {};
|
|
2263
|
+
} else if (typeof data === "function" && options === void 0 && cb === void 0) {
|
|
2264
|
+
cb = data;
|
|
2265
|
+
data = {};
|
|
2266
|
+
}
|
|
2267
|
+
cb = cb || utils.createPromiseCallback();
|
|
2268
|
+
const self = this;
|
|
2269
|
+
const definition = this.definition;
|
|
2270
|
+
const modelTo = this.definition.modelTo;
|
|
2271
|
+
const modelInstance = this.modelInstance;
|
|
2272
|
+
const defOpts = definition.options;
|
|
2273
|
+
const belongsTo = defOpts.belongsTo && modelTo.relations[defOpts.belongsTo];
|
|
2274
|
+
if (!belongsTo) throw new Error("Invalid reference: " + defOpts.belongsTo || "(none)");
|
|
2275
|
+
const fk2 = belongsTo.keyTo;
|
|
2276
|
+
const pk2 = belongsTo.modelTo.definition.idName() || "id";
|
|
2277
|
+
const query = {};
|
|
2278
|
+
query[fk2] = acInst instanceof belongsTo.modelTo ? acInst[pk2] : acInst;
|
|
2279
|
+
const filter = { where: query };
|
|
2280
|
+
belongsTo.applyScope(modelInstance, filter);
|
|
2281
|
+
belongsTo.modelTo.findOne(filter, options, function(err, ref) {
|
|
2282
|
+
if (ref instanceof belongsTo.modelTo) {
|
|
2283
|
+
const inst = self.build(data || {});
|
|
2284
|
+
inst[defOpts.belongsTo](ref);
|
|
2285
|
+
modelInstance.save(function(err) {
|
|
2286
|
+
cb(err, err ? null : inst);
|
|
2287
|
+
});
|
|
2288
|
+
} else cb(null, null);
|
|
2289
|
+
});
|
|
2290
|
+
return cb.promise;
|
|
2291
|
+
};
|
|
2292
|
+
/**
|
|
2293
|
+
* Remove the target model instance from the 'embedsMany' relation
|
|
2294
|
+
* @param {Object|ID) acInst The actual instance or id value
|
|
2295
|
+
*/
|
|
2296
|
+
EmbedsMany.prototype.remove = function(acInst, options, cb) {
|
|
2297
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2298
|
+
cb = options;
|
|
2299
|
+
options = {};
|
|
2300
|
+
}
|
|
2301
|
+
const self = this;
|
|
2302
|
+
const definition = this.definition;
|
|
2303
|
+
const modelTo = this.definition.modelTo;
|
|
2304
|
+
const modelInstance = this.modelInstance;
|
|
2305
|
+
const defOpts = definition.options;
|
|
2306
|
+
const belongsTo = defOpts.belongsTo && modelTo.relations[defOpts.belongsTo];
|
|
2307
|
+
if (!belongsTo) throw new Error("Invalid reference: " + defOpts.belongsTo || "(none)");
|
|
2308
|
+
const fk2 = belongsTo.keyTo;
|
|
2309
|
+
const pk2 = belongsTo.modelTo.definition.idName() || "id";
|
|
2310
|
+
const query = {};
|
|
2311
|
+
query[fk2] = acInst instanceof belongsTo.modelTo ? acInst[pk2] : acInst;
|
|
2312
|
+
const filter = { where: query };
|
|
2313
|
+
belongsTo.applyScope(modelInstance, filter);
|
|
2314
|
+
cb = cb || utils.createPromiseCallback();
|
|
2315
|
+
modelInstance[definition.name](filter, options, function(err, items) {
|
|
2316
|
+
if (err) return cb(err);
|
|
2317
|
+
items.forEach(function(item) {
|
|
2318
|
+
self.unset(item);
|
|
2319
|
+
});
|
|
2320
|
+
modelInstance.save(options, function(err) {
|
|
2321
|
+
cb(err);
|
|
2322
|
+
});
|
|
2323
|
+
});
|
|
2324
|
+
return cb.promise;
|
|
2325
|
+
};
|
|
2326
|
+
RelationDefinition.referencesMany = function referencesMany(modelFrom, modelToRef, params) {
|
|
2327
|
+
params = params || {};
|
|
2328
|
+
normalizeRelationAs(params, modelToRef);
|
|
2329
|
+
const modelTo = lookupModelTo(modelFrom, modelToRef, params, true);
|
|
2330
|
+
modelFrom.modelName;
|
|
2331
|
+
const relationName = params.as || i8n.camelize(modelTo.pluralModelName, true);
|
|
2332
|
+
const fk = params.foreignKey || i8n.camelize(modelTo.modelName + "_ids", true);
|
|
2333
|
+
const idName = modelTo.dataSource.idName(modelTo.modelName) || "id";
|
|
2334
|
+
const idType = modelTo.definition.properties[idName].type;
|
|
2335
|
+
const definition = modelFrom.relations[relationName] = new RelationDefinition({
|
|
2336
|
+
name: relationName,
|
|
2337
|
+
type: RelationTypes.referencesMany,
|
|
2338
|
+
modelFrom,
|
|
2339
|
+
keyFrom: fk,
|
|
2340
|
+
keyTo: idName,
|
|
2341
|
+
modelTo,
|
|
2342
|
+
multiple: true,
|
|
2343
|
+
properties: params.properties,
|
|
2344
|
+
scope: params.scope,
|
|
2345
|
+
options: params.options
|
|
2346
|
+
});
|
|
2347
|
+
modelFrom.dataSource.defineProperty(modelFrom.modelName, fk, {
|
|
2348
|
+
type: [idType],
|
|
2349
|
+
default: function() {
|
|
2350
|
+
return [];
|
|
2351
|
+
}
|
|
2352
|
+
});
|
|
2353
|
+
modelFrom.validate(relationName, function(err) {
|
|
2354
|
+
if (idsHaveDuplicates(this[fk] || [])) {
|
|
2355
|
+
const msg = "contains duplicate `" + modelTo.modelName + "` instance";
|
|
2356
|
+
this.errors.add(relationName, msg, "uniqueness");
|
|
2357
|
+
err(false);
|
|
2358
|
+
}
|
|
2359
|
+
}, { code: "uniqueness" });
|
|
2360
|
+
const scopeMethods = {
|
|
2361
|
+
findById: scopeMethod(definition, "findById"),
|
|
2362
|
+
destroy: scopeMethod(definition, "destroyById"),
|
|
2363
|
+
updateById: scopeMethod(definition, "updateById"),
|
|
2364
|
+
exists: scopeMethod(definition, "exists"),
|
|
2365
|
+
add: scopeMethod(definition, "add"),
|
|
2366
|
+
remove: scopeMethod(definition, "remove"),
|
|
2367
|
+
at: scopeMethod(definition, "at")
|
|
2368
|
+
};
|
|
2369
|
+
const findByIdFunc = scopeMethods.findById;
|
|
2370
|
+
modelFrom.prototype["__findById__" + relationName] = findByIdFunc;
|
|
2371
|
+
const destroyByIdFunc = scopeMethods.destroy;
|
|
2372
|
+
modelFrom.prototype["__destroyById__" + relationName] = destroyByIdFunc;
|
|
2373
|
+
const updateByIdFunc = scopeMethods.updateById;
|
|
2374
|
+
modelFrom.prototype["__updateById__" + relationName] = updateByIdFunc;
|
|
2375
|
+
const addFunc = scopeMethods.add;
|
|
2376
|
+
modelFrom.prototype["__link__" + relationName] = addFunc;
|
|
2377
|
+
const removeFunc = scopeMethods.remove;
|
|
2378
|
+
modelFrom.prototype["__unlink__" + relationName] = removeFunc;
|
|
2379
|
+
scopeMethods.create = scopeMethod(definition, "create");
|
|
2380
|
+
scopeMethods.build = scopeMethod(definition, "build");
|
|
2381
|
+
scopeMethods.related = scopeMethod(definition, "related");
|
|
2382
|
+
const customMethods = extendScopeMethods(definition, scopeMethods, params.scopeMethods);
|
|
2383
|
+
for (let i = 0; i < customMethods.length; i++) {
|
|
2384
|
+
const methodName = customMethods[i];
|
|
2385
|
+
const method = scopeMethods[methodName];
|
|
2386
|
+
if (typeof method === "function" && method.shared === true) modelFrom.prototype["__" + methodName + "__" + relationName] = method;
|
|
2387
|
+
}
|
|
2388
|
+
const scopeDefinition = defineScope(modelFrom.prototype, modelTo, relationName, function() {
|
|
2389
|
+
return {};
|
|
2390
|
+
}, scopeMethods, definition.options);
|
|
2391
|
+
scopeDefinition.related = scopeMethods.related;
|
|
2392
|
+
return definition;
|
|
2393
|
+
};
|
|
2394
|
+
ReferencesMany.prototype.related = function(receiver, scopeParams, condOrRefresh, options, cb) {
|
|
2395
|
+
const fk = this.definition.keyFrom;
|
|
2396
|
+
const modelTo = this.definition.modelTo;
|
|
2397
|
+
this.definition.name;
|
|
2398
|
+
const modelInstance = this.modelInstance;
|
|
2399
|
+
const self = receiver;
|
|
2400
|
+
let actualCond = {};
|
|
2401
|
+
if (typeof condOrRefresh === "function" && options === void 0 && cb === void 0) {
|
|
2402
|
+
cb = condOrRefresh;
|
|
2403
|
+
condOrRefresh = void 0;
|
|
2404
|
+
} else if (typeof options === "function" && cb === void 0) {
|
|
2405
|
+
cb = options;
|
|
2406
|
+
options = {};
|
|
2407
|
+
if (typeof condOrRefresh === "boolean") condOrRefresh = {};
|
|
2408
|
+
}
|
|
2409
|
+
actualCond = condOrRefresh || {};
|
|
2410
|
+
const ids = self[fk] || [];
|
|
2411
|
+
this.definition.applyScope(modelInstance, actualCond);
|
|
2412
|
+
const params = mergeQuery(actualCond, scopeParams);
|
|
2413
|
+
return modelTo.findByIds(ids, params, options, cb);
|
|
2414
|
+
};
|
|
2415
|
+
ReferencesMany.prototype.findById = function(fkId, options, cb) {
|
|
2416
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2417
|
+
cb = options;
|
|
2418
|
+
options = {};
|
|
2419
|
+
}
|
|
2420
|
+
const modelTo = this.definition.modelTo;
|
|
2421
|
+
const modelFrom = this.definition.modelFrom;
|
|
2422
|
+
this.definition.name;
|
|
2423
|
+
const modelInstance = this.modelInstance;
|
|
2424
|
+
const pk = this.definition.keyTo;
|
|
2425
|
+
const fk = this.definition.keyFrom;
|
|
2426
|
+
if (typeof fkId === "object") fkId = fkId.toString();
|
|
2427
|
+
const ids = modelInstance[fk] || [];
|
|
2428
|
+
const filter = {};
|
|
2429
|
+
this.definition.applyScope(modelInstance, filter);
|
|
2430
|
+
cb = cb || utils.createPromiseCallback();
|
|
2431
|
+
modelTo.findByIds([fkId], filter, options, function(err, instances) {
|
|
2432
|
+
if (err) return cb(err);
|
|
2433
|
+
const inst = instances[0];
|
|
2434
|
+
if (!inst) {
|
|
2435
|
+
err = new Error(g.f("No instance with {{id}} %s found for %s", fkId, modelTo.modelName));
|
|
2436
|
+
err.statusCode = 404;
|
|
2437
|
+
return cb(err);
|
|
2438
|
+
}
|
|
2439
|
+
if (utils.findIndexOf(ids, inst[pk], idEquals) > -1) cb(null, inst);
|
|
2440
|
+
else {
|
|
2441
|
+
err = new Error(g.f("Key mismatch: %s.%s: %s, %s.%s: %s", modelFrom.modelName, fk, modelInstance[fk], modelTo.modelName, pk, inst[pk]));
|
|
2442
|
+
err.statusCode = 400;
|
|
2443
|
+
cb(err);
|
|
2444
|
+
}
|
|
2445
|
+
});
|
|
2446
|
+
return cb.promise;
|
|
2447
|
+
};
|
|
2448
|
+
ReferencesMany.prototype.exists = function(fkId, options, cb) {
|
|
2449
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2450
|
+
cb = options;
|
|
2451
|
+
options = {};
|
|
2452
|
+
}
|
|
2453
|
+
const fk = this.definition.keyFrom;
|
|
2454
|
+
const ids = this.modelInstance[fk] || [];
|
|
2455
|
+
cb = cb || utils.createPromiseCallback();
|
|
2456
|
+
process.nextTick(function() {
|
|
2457
|
+
cb(null, utils.findIndexOf(ids, fkId, idEquals) > -1);
|
|
2458
|
+
});
|
|
2459
|
+
return cb.promise;
|
|
2460
|
+
};
|
|
2461
|
+
ReferencesMany.prototype.updateById = function(fkId, data, options, cb) {
|
|
2462
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2463
|
+
cb = options;
|
|
2464
|
+
options = {};
|
|
2465
|
+
} else if (typeof data === "function" && options === void 0 && cb === void 0) {
|
|
2466
|
+
cb = data;
|
|
2467
|
+
data = {};
|
|
2468
|
+
}
|
|
2469
|
+
cb = cb || utils.createPromiseCallback();
|
|
2470
|
+
this.findById(fkId, options, function(err, inst) {
|
|
2471
|
+
if (err) return cb(err);
|
|
2472
|
+
inst.updateAttributes(data, options, cb);
|
|
2473
|
+
});
|
|
2474
|
+
return cb.promise;
|
|
2475
|
+
};
|
|
2476
|
+
ReferencesMany.prototype.destroyById = function(fkId, options, cb) {
|
|
2477
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2478
|
+
cb = options;
|
|
2479
|
+
options = {};
|
|
2480
|
+
}
|
|
2481
|
+
const self = this;
|
|
2482
|
+
cb = cb || utils.createPromiseCallback();
|
|
2483
|
+
this.findById(fkId, function(err, inst) {
|
|
2484
|
+
if (err) return cb(err);
|
|
2485
|
+
self.remove(inst, function(_err, _ids) {
|
|
2486
|
+
inst.destroy(cb);
|
|
2487
|
+
});
|
|
2488
|
+
});
|
|
2489
|
+
return cb.promise;
|
|
2490
|
+
};
|
|
2491
|
+
ReferencesMany.prototype.at = function(index, options, cb) {
|
|
2492
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2493
|
+
cb = options;
|
|
2494
|
+
options = {};
|
|
2495
|
+
}
|
|
2496
|
+
const fk = this.definition.keyFrom;
|
|
2497
|
+
const ids = this.modelInstance[fk] || [];
|
|
2498
|
+
cb = cb || utils.createPromiseCallback();
|
|
2499
|
+
this.findById(ids[index], options, cb);
|
|
2500
|
+
return cb.promise;
|
|
2501
|
+
};
|
|
2502
|
+
ReferencesMany.prototype.create = function(targetModelData, options, cb) {
|
|
2503
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2504
|
+
cb = options;
|
|
2505
|
+
options = {};
|
|
2506
|
+
}
|
|
2507
|
+
const definition = this.definition;
|
|
2508
|
+
this.definition.modelTo;
|
|
2509
|
+
this.definition.name;
|
|
2510
|
+
const modelInstance = this.modelInstance;
|
|
2511
|
+
const pk = this.definition.keyTo;
|
|
2512
|
+
const fk = this.definition.keyFrom;
|
|
2513
|
+
if (typeof targetModelData === "function" && !cb) {
|
|
2514
|
+
cb = targetModelData;
|
|
2515
|
+
targetModelData = {};
|
|
2516
|
+
}
|
|
2517
|
+
targetModelData = targetModelData || {};
|
|
2518
|
+
cb = cb || utils.createPromiseCallback();
|
|
2519
|
+
const ids = modelInstance[fk] || [];
|
|
2520
|
+
this.callScopeMethod("build", targetModelData).save(options, function(err, inst) {
|
|
2521
|
+
if (err) return cb(err, inst);
|
|
2522
|
+
let id = inst[pk];
|
|
2523
|
+
if (typeof id === "object") id = id.toString();
|
|
2524
|
+
if (definition.options.prepend) ids.unshift(id);
|
|
2525
|
+
else ids.push(id);
|
|
2526
|
+
modelInstance.updateAttribute(fk, ids, options, function(err, _modelInst) {
|
|
2527
|
+
cb(err, inst);
|
|
2528
|
+
});
|
|
2529
|
+
});
|
|
2530
|
+
return cb.promise;
|
|
2531
|
+
};
|
|
2532
|
+
ReferencesMany.prototype.build = function(targetModelData) {
|
|
2533
|
+
const modelTo = this.definition.modelTo;
|
|
2534
|
+
targetModelData = targetModelData || {};
|
|
2535
|
+
this.definition.applyProperties(this.modelInstance, targetModelData);
|
|
2536
|
+
return new modelTo(targetModelData);
|
|
2537
|
+
};
|
|
2538
|
+
/**
|
|
2539
|
+
* Add the target model instance to the 'embedsMany' relation
|
|
2540
|
+
* @param {Object|ID} acInst The actual instance or id value
|
|
2541
|
+
*/
|
|
2542
|
+
ReferencesMany.prototype.add = function(acInst, options, cb) {
|
|
2543
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2544
|
+
cb = options;
|
|
2545
|
+
options = {};
|
|
2546
|
+
}
|
|
2547
|
+
const definition = this.definition;
|
|
2548
|
+
const modelTo = this.definition.modelTo;
|
|
2549
|
+
const modelInstance = this.modelInstance;
|
|
2550
|
+
const pk = this.definition.keyTo;
|
|
2551
|
+
const fk = this.definition.keyFrom;
|
|
2552
|
+
const insert = function(inst, done) {
|
|
2553
|
+
let id = inst[pk];
|
|
2554
|
+
if (typeof id === "object") id = id.toString();
|
|
2555
|
+
const ids = modelInstance[fk] || [];
|
|
2556
|
+
if (definition.options.prepend) ids.unshift(id);
|
|
2557
|
+
else ids.push(id);
|
|
2558
|
+
modelInstance.updateAttribute(fk, ids, options, function(err) {
|
|
2559
|
+
done(err, err ? null : inst);
|
|
2560
|
+
});
|
|
2561
|
+
};
|
|
2562
|
+
cb = cb || utils.createPromiseCallback();
|
|
2563
|
+
if (acInst instanceof modelTo) insert(acInst, cb);
|
|
2564
|
+
else {
|
|
2565
|
+
const filter = { where: {} };
|
|
2566
|
+
filter.where[pk] = acInst;
|
|
2567
|
+
definition.applyScope(modelInstance, filter);
|
|
2568
|
+
modelTo.findOne(filter, options, function(err, inst) {
|
|
2569
|
+
if (err || !inst) return cb(err, null);
|
|
2570
|
+
insert(inst, cb);
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
return cb.promise;
|
|
2574
|
+
};
|
|
2575
|
+
/**
|
|
2576
|
+
* Remove the target model instance from the 'embedsMany' relation
|
|
2577
|
+
* @param {Object|ID) acInst The actual instance or id value
|
|
2578
|
+
*/
|
|
2579
|
+
ReferencesMany.prototype.remove = function(acInst, options, cb) {
|
|
2580
|
+
if (typeof options === "function" && cb === void 0) {
|
|
2581
|
+
cb = options;
|
|
2582
|
+
options = {};
|
|
2583
|
+
}
|
|
2584
|
+
const definition = this.definition;
|
|
2585
|
+
const modelInstance = this.modelInstance;
|
|
2586
|
+
const pk = this.definition.keyTo;
|
|
2587
|
+
const fk = this.definition.keyFrom;
|
|
2588
|
+
const ids = modelInstance[fk] || [];
|
|
2589
|
+
const id = acInst instanceof definition.modelTo ? acInst[pk] : acInst;
|
|
2590
|
+
cb = cb || utils.createPromiseCallback();
|
|
2591
|
+
const index = utils.findIndexOf(ids, id, idEquals);
|
|
2592
|
+
if (index > -1) {
|
|
2593
|
+
ids.splice(index, 1);
|
|
2594
|
+
modelInstance.updateAttribute(fk, ids, options, function(err, inst) {
|
|
2595
|
+
cb(err, inst[fk] || []);
|
|
2596
|
+
});
|
|
2597
|
+
} else process.nextTick(function() {
|
|
2598
|
+
cb(null, ids);
|
|
2599
|
+
});
|
|
2600
|
+
return cb.promise;
|
|
2601
|
+
};
|
|
2602
|
+
}));
|
|
2603
|
+
//#endregion
|
|
2604
|
+
module.exports = require_relation_definition();
|