1 | ;
|
2 |
|
3 | var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
|
4 |
|
5 | var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
|
6 |
|
7 | var _typeof2 = require('babel-runtime/helpers/typeof');
|
8 |
|
9 | var _typeof3 = _interopRequireDefault(_typeof2);
|
10 |
|
11 | var _extends2 = require('babel-runtime/helpers/extends');
|
12 |
|
13 | var _extends3 = _interopRequireDefault(_extends2);
|
14 |
|
15 | var _lodash = require('lodash');
|
16 |
|
17 | var _lodash2 = _interopRequireDefault(_lodash);
|
18 |
|
19 | var _createError = require('create-error');
|
20 |
|
21 | var _createError2 = _interopRequireDefault(_createError);
|
22 |
|
23 | var _sync = require('./sync');
|
24 |
|
25 | var _sync2 = _interopRequireDefault(_sync);
|
26 |
|
27 | var _helpers = require('./helpers');
|
28 |
|
29 | var _helpers2 = _interopRequireDefault(_helpers);
|
30 |
|
31 | var _eager = require('./eager');
|
32 |
|
33 | var _eager2 = _interopRequireDefault(_eager);
|
34 |
|
35 | var _errors = require('./errors');
|
36 |
|
37 | var _errors2 = _interopRequireDefault(_errors);
|
38 |
|
39 | var _model = require('./base/model');
|
40 |
|
41 | var _model2 = _interopRequireDefault(_model);
|
42 |
|
43 | var _promise = require('./base/promise');
|
44 |
|
45 | var _promise2 = _interopRequireDefault(_promise);
|
46 |
|
47 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
48 |
|
49 | /**
|
50 | * @class Model
|
51 | * @extends ModelBase
|
52 | * @inheritdoc
|
53 | * @classdesc
|
54 | *
|
55 | * Models are simple objects representing individual database rows, specifying
|
56 | * the tableName and any relations to other models. They can be extended with
|
57 | * any domain-specific methods, which can handle components such as validations,
|
58 | * computed properties, and access control.
|
59 | *
|
60 | * @constructor
|
61 | * @description
|
62 | *
|
63 | * When creating an instance of a model, you can pass in the initial values of
|
64 | * the attributes, which will be {@link Model#set set} on the
|
65 | * model. If you define an {@link initialize} function, it will be invoked
|
66 | * when the model is created.
|
67 | *
|
68 | * new Book({
|
69 | * title: "One Thousand and One Nights",
|
70 | * author: "Scheherazade"
|
71 | * });
|
72 | *
|
73 | * In rare cases, if you're looking to get fancy, you may want to override
|
74 | * {@link Model#constructor constructor}, which allows you to replace the
|
75 | * actual constructor function for your model.
|
76 | *
|
77 | * let Book = bookshelf.Model.extend({
|
78 | * tableName: 'documents',
|
79 | * constructor: function() {
|
80 | * bookshelf.Model.apply(this, arguments);
|
81 | * this.on('saving', function(model, attrs, options) {
|
82 | * options.query.where('type', '=', 'book');
|
83 | * });
|
84 | * }
|
85 | * });
|
86 | *
|
87 | * @param {Object} attributes Initial values for this model's attributes.
|
88 | * @param {Object=} options Hash of options.
|
89 | * @param {string=} options.tableName Initial value for {@link Model#tableName tableName}.
|
90 | * @param {Boolean=} [options.hasTimestamps=false]
|
91 | *
|
92 | * Initial value for {@link Model#hasTimestamps hasTimestamps}.
|
93 | *
|
94 | * @param {Boolean} [options.parse=false]
|
95 | *
|
96 | * Convert attributes by {@link Model#parse parse} before being {@link
|
97 | * Model#set set} on the model.
|
98 | *
|
99 | */
|
100 | var BookshelfModel = _model2.default.extend({
|
101 |
|
102 | /**
|
103 | * The `hasOne` relation specifies that this table has exactly one of another
|
104 | * type of object, specified by a foreign key in the other table.
|
105 | *
|
106 | * let Record = bookshelf.Model.extend({
|
107 | * tableName: 'health_records'
|
108 | * });
|
109 | *
|
110 | * let Patient = bookshelf.Model.extend({
|
111 | * tableName: 'patients',
|
112 | * record: function() {
|
113 | * return this.hasOne(Record);
|
114 | * }
|
115 | * });
|
116 | *
|
117 | * // select * from `health_records` where `patient_id` = 1;
|
118 | * new Patient({id: 1}).related('record').fetch().then(function(model) {
|
119 | * // ...
|
120 | * });
|
121 | *
|
122 | * // alternatively, if you don't need the relation loaded on the patient's relations hash:
|
123 | * new Patient({id: 1}).record().fetch().then(function(model) {
|
124 | * // ...
|
125 | * });
|
126 | *
|
127 | * @method Model#hasOne
|
128 | *
|
129 | * @param {Model} Target
|
130 | *
|
131 | * Constructor of {@link Model} targeted by join.
|
132 | *
|
133 | * @param {string=} foreignKey
|
134 | *
|
135 | * ForeignKey in the `Target` model. By default, the `foreignKey` is assumed to
|
136 | * be the singular form of this model's {@link Model#tableName tableName},
|
137 | * followed by `_id` / `_{{{@link Model#idAttribute idAttribute}}}`.
|
138 | *
|
139 | * @param {string=} foreignKeyTarget
|
140 | *
|
141 | * Column in the `Target` model's table which `foreignKey` references, if other
|
142 | * than `Target` model's `id` / `{@link Model#idAttribute idAttribute}`.
|
143 | *
|
144 | * @returns {Model}
|
145 | */
|
146 | hasOne: function hasOne(Target, foreignKey, foreignKeyTarget) {
|
147 | return this._relation('hasOne', Target, { foreignKey: foreignKey, foreignKeyTarget: foreignKeyTarget }).init(this);
|
148 | },
|
149 |
|
150 |
|
151 | /**
|
152 | * The `hasMany` relation specifies that this model has one or more rows in
|
153 | * another table which match on this model's primary key.
|
154 | *
|
155 | * let Author = bookshelf.Model.extend({
|
156 | * tableName: 'authors',
|
157 | * books: function() {
|
158 | * return this.hasMany(Book);
|
159 | * }
|
160 | * });
|
161 | *
|
162 | * // select * from `authors` where id = 1
|
163 | * // select * from `books` where author_id = 1
|
164 | * Author.where({id: 1}).fetch({withRelated: ['books']}).then(function(author) {
|
165 | * console.log(JSON.stringify(author.related('books')));
|
166 | * });
|
167 | *
|
168 | * @method Model#hasMany
|
169 | *
|
170 | * @param {Model} Target
|
171 | *
|
172 | * Constructor of {@link Model} targeted by join.
|
173 | *
|
174 | * @param {string=} foreignKey
|
175 | *
|
176 | * ForeignKey in the `Target` model. By default, the foreignKey is assumed to
|
177 | * be the singular form of this model's tableName, followed by `_id` /
|
178 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
179 | *
|
180 | * @param {string=} foreignKeyTarget
|
181 | *
|
182 | * Column in the `Target` model's table which `foreignKey` references, if other
|
183 | * than `Target` model's `id` / `{@link Model#idAttribute idAttribute}`.
|
184 | *
|
185 | * @returns {Collection}
|
186 | */
|
187 | hasMany: function hasMany(Target, foreignKey, foreignKeyTarget) {
|
188 | return this._relation('hasMany', Target, { foreignKey: foreignKey, foreignKeyTarget: foreignKeyTarget }).init(this);
|
189 | },
|
190 |
|
191 |
|
192 | /**
|
193 | * The `belongsTo` relationship is used when a model is a member of
|
194 | * another `Target` model.
|
195 | *
|
196 | * It can be used in a {@linkplain one-to-one} associations as the inverse
|
197 | * of a {@link Model#hasOne hasOne}. It can also used in {@linkplain
|
198 | * one-to-many} associations as the inverse of a {@link Model#hasMany hasMany}
|
199 | * (and is the one side of that association). In both cases, the {@link
|
200 | * Model#belongsTo belongsTo} relationship is used for a model that is a
|
201 | * member of another Target model, referenced by the foreignKey in the current
|
202 | * model.
|
203 | *
|
204 | * let Book = bookshelf.Model.extend({
|
205 | * tableName: 'books',
|
206 | * author: function() {
|
207 | * return this.belongsTo(Author);
|
208 | * }
|
209 | * });
|
210 | *
|
211 | * // select * from `books` where id = 1
|
212 | * // select * from `authors` where id = book.author_id
|
213 | * Book.where({id: 1}).fetch({withRelated: ['author']}).then(function(book) {
|
214 | * console.log(JSON.stringify(book.related('author')));
|
215 | * });
|
216 | *
|
217 | * @method Model#belongsTo
|
218 | *
|
219 | * @param {Model} Target
|
220 | *
|
221 | * Constructor of {@link Model} targeted by join.
|
222 | *
|
223 | * @param {string=} foreignKey
|
224 | *
|
225 | * ForeignKey in this model. By default, the foreignKey is assumed to
|
226 | * be the singular form of the `Target` model's tableName, followed by `_id` /
|
227 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
228 | *
|
229 | * @param {string=} foreignKeyTarget
|
230 | *
|
231 | * Column in the `Target` model's table which `foreignKey` references, if other
|
232 | * than `Target` model's `id` / `{@link Model#idAttribute idAttribute}`.
|
233 | *
|
234 | * @returns {Model}
|
235 | */
|
236 | belongsTo: function belongsTo(Target, foreignKey, foreignKeyTarget) {
|
237 | return this._relation('belongsTo', Target, { foreignKey: foreignKey, foreignKeyTarget: foreignKeyTarget }).init(this);
|
238 | },
|
239 |
|
240 |
|
241 | /**
|
242 | * Defines a many-to-many relation, where the current model is joined to one
|
243 | * or more of a `Target` model through another table. The default name for
|
244 | * the joining table is the two table names, joined by an underscore, ordered
|
245 | * alphabetically. For example, a `users` table and an `accounts` table would have
|
246 | * a joining table of accounts_users.
|
247 | *
|
248 | * let Account = bookshelf.Model.extend({
|
249 | * tableName: 'accounts'
|
250 | * });
|
251 | *
|
252 | * let User = bookshelf.Model.extend({
|
253 | * tableName: 'users',
|
254 | * allAccounts: function () {
|
255 | * return this.belongsToMany(Account);
|
256 | * },
|
257 | * adminAccounts: function() {
|
258 | * return this.belongsToMany(Account).query({where: {access: 'admin'}});
|
259 | * },
|
260 | * viewAccounts: function() {
|
261 | * return this.belongsToMany(Account).query({where: {access: 'readonly'}});
|
262 | * }
|
263 | * });
|
264 | *
|
265 | * The default key names in the joining table are the singular versions of the
|
266 | * model table names, followed by `_id` /
|
267 | * _{{{@link Model#idAttribute idAttribute}}}. So in the above case, the
|
268 | * columns in the joining table
|
269 | * would be `user_id`, `account_id`, and `access`, which is used as an
|
270 | * example of how dynamic relations can be formed using different contexts.
|
271 | * To customize the keys used in, or the {@link Model#tableName tableName}
|
272 | * used for the join table, you may specify them like so:
|
273 | *
|
274 | * this.belongsToMany(Account, 'users_accounts', 'userid', 'accountid');
|
275 | *
|
276 | * If you wish to create a {@link Model#belongsToMany belongsToMany}
|
277 | * association where the joining table has a primary key, and more information
|
278 | * about the model, you may create a {@link Model#belongsToMany belongsToMany}
|
279 | * {@link Relation#through through} relation:
|
280 | *
|
281 | * let Doctor = bookshelf.Model.extend({
|
282 | * patients: function() {
|
283 | * return this.belongsToMany(Patient).through(Appointment);
|
284 | * }
|
285 | * });
|
286 | *
|
287 | * let Appointment = bookshelf.Model.extend({
|
288 | * patient: function() {
|
289 | * return this.belongsTo(Patient);
|
290 | * },
|
291 | * doctor: function() {
|
292 | * return this.belongsTo(Doctor);
|
293 | * }
|
294 | * });
|
295 | *
|
296 | * let Patient = bookshelf.Model.extend({
|
297 | * doctors: function() {
|
298 | * return this.belongsToMany(Doctor).through(Appointment);
|
299 | * }
|
300 | * });
|
301 | *
|
302 | * Collections returned by a `belongsToMany` relation are decorated with
|
303 | * several pivot helper methods. See {@link Collection#attach attach},
|
304 | * {@link Collection#detach detach}, {@link Collection#updatePivot
|
305 | * updatePivot} and {@link Collection#withPivot withPivot} for more
|
306 | * information.
|
307 | *
|
308 | * @belongsTo Model
|
309 | * @method Model#belongsToMany
|
310 | * @param {Model} Target
|
311 | *
|
312 | * Constructor of {@link Model} targeted by join.
|
313 | *
|
314 | * @param {string=} table
|
315 | *
|
316 | * Name of the joining table. Defaults to the two table names, joined by an
|
317 | * underscore, ordered alphabetically.
|
318 | *
|
319 | * @param {string=} foreignKey
|
320 | *
|
321 | * Foreign key in this model. By default, the `foreignKey` is assumed to
|
322 | * be the singular form of this model's tableName, followed by `_id` /
|
323 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
324 | *
|
325 | * @param {string=} otherKey
|
326 | *
|
327 | * Foreign key in the `Target` model. By default, the `otherKey` is assumed to
|
328 | * be the singular form of the `Target` model's tableName, followed by `_id` /
|
329 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
330 | *
|
331 | * @param {string=} foreignKeyTarget
|
332 | *
|
333 | * Column in this model's table which `foreignKey` references, if other
|
334 | * than `id` / `{@link Model#idAttribute idAttribute}`.
|
335 | *
|
336 | * @param {string=} otherKeyTarget
|
337 | *
|
338 | * Column in the `Target` model's table which `otherKey` references, if other
|
339 | * than `Target` model's `id` / `{@link Model#idAttribute idAttribute}`.
|
340 | *
|
341 | * @returns {Collection}
|
342 | */
|
343 | belongsToMany: function belongsToMany(Target, joinTableName, foreignKey, otherKey, foreignKeyTarget, otherKeyTarget) {
|
344 | return this._relation('belongsToMany', Target, {
|
345 | joinTableName: joinTableName, foreignKey: foreignKey, otherKey: otherKey, foreignKeyTarget: foreignKeyTarget, otherKeyTarget: otherKeyTarget
|
346 | }).init(this);
|
347 | },
|
348 |
|
349 |
|
350 | /**
|
351 | * The {@link Model#morphOne morphOne} is used to signify a {@link oneToOne
|
352 | * one-to-one} {@link polymorphicRelation polymorphic relation} with
|
353 | * another `Target` model, where the `name` of the model is used to determine
|
354 | * which database table keys are used. The naming convention requires the
|
355 | * `name` prefix an `_id` and `_type` field in the database. So for the case
|
356 | * below the table names would be `imageable_type` and `imageable_id`. The
|
357 | * `morphValue` may be optionally set to store/retrieve a different value in
|
358 | * the `_type` column than the {@link Model#tableName}.
|
359 | *
|
360 | * let Site = bookshelf.Model.extend({
|
361 | * tableName: 'sites',
|
362 | * photo: function() {
|
363 | * return this.morphOne(Photo, 'imageable');
|
364 | * }
|
365 | * });
|
366 | *
|
367 | * And with custom `columnNames`:
|
368 | *
|
369 | * let Site = bookshelf.Model.extend({
|
370 | * tableName: 'sites',
|
371 | * photo: function() {
|
372 | * return this.morphOne(Photo, 'imageable', ["ImageableType", "ImageableId"]);
|
373 | * }
|
374 | * });
|
375 | *
|
376 | * Note that both `columnNames` and `morphValue` are optional arguments. How
|
377 | * your argument is treated when only one is specified, depends on the type.
|
378 | * If your argument is an array, it will be assumed to contain custom
|
379 | * `columnNames`. If it's not, it will be assumed to indicate a `morphValue`.
|
380 | *
|
381 | * @method Model#morphOne
|
382 | *
|
383 | * @param {Model} Target Constructor of {@link Model} targeted by join.
|
384 | * @param {string=} name Prefix for `_id` and `_type` columns.
|
385 | * @param {(string[])=} columnNames
|
386 | *
|
387 | * Array containing two column names, the first is the `_type`, the second
|
388 | * is the `_id`.
|
389 | *
|
390 | * @param {string=} [morphValue=Target#{@link Model#tableName tableName}]
|
391 | *
|
392 | * The string value associated with this relationship. Stored in the `_type`
|
393 | * column of the polymorphic table. Defaults to `Target#{@link
|
394 | * Model#tableName tableName}`.
|
395 | *
|
396 | * @returns {Model} The related model.
|
397 | */
|
398 | morphOne: function morphOne(Target, name, columnNames, morphValue) {
|
399 | return this._morphOneOrMany(Target, name, columnNames, morphValue, 'morphOne');
|
400 | },
|
401 |
|
402 |
|
403 | /**
|
404 | * {@link Model#morphMany morphMany} is essentially the same as a {@link
|
405 | * Model#morphOne morphOne}, but creating a {@link Collection collection}
|
406 | * rather than a {@link Model model} (similar to a {@link Model#hasOne
|
407 | * hasOne} vs. {@link Model#hasMany hasMany} relation).
|
408 | *
|
409 | * {@link Model#morphMany morphMany} is used to signify a {@link oneToMany
|
410 | * one-to-many} or {@link manyToMany many-to-many} {@link polymorphicRelation
|
411 | * polymorphic relation} with another `Target` model, where the `name` of the
|
412 | * model is used to determine which database table keys are used. The naming
|
413 | * convention requires the `name` prefix an `_id` and `_type` field in the
|
414 | * database. So for the case below the table names would be `imageable_type`
|
415 | * and `imageable_id`. The `morphValue` may be optionally set to
|
416 | * store/retrieve a different value in the `_type` column than the `Target`'s
|
417 | * {@link Model#tableName tableName}.
|
418 | *
|
419 | * let Post = bookshelf.Model.extend({
|
420 | * tableName: 'posts',
|
421 | * photos: function() {
|
422 | * return this.morphMany(Photo, 'imageable');
|
423 | * }
|
424 | * });
|
425 | *
|
426 | * And with custom columnNames:
|
427 | *
|
428 | * let Post = bookshelf.Model.extend({
|
429 | * tableName: 'posts',
|
430 | * photos: function() {
|
431 | * return this.morphMany(Photo, 'imageable', ["ImageableType", "ImageableId"]);
|
432 | * }
|
433 | * });
|
434 | *
|
435 | * @method Model#morphMany
|
436 | *
|
437 | * @param {Model} Target Constructor of {@link Model} targeted by join.
|
438 | * @param {string=} name Prefix for `_id` and `_type` columns.
|
439 | * @param {(string[])=} columnNames
|
440 | *
|
441 | * Array containing two column names, the first is the `_type`, the second is the `_id`.
|
442 | *
|
443 | * @param {string=} [morphValue=Target#{@link Model#tableName tablename}]
|
444 | *
|
445 | * The string value associated with this relationship. Stored in the `_type`
|
446 | * column of the polymorphic table. Defaults to `Target`#{@link Model#tableName
|
447 | * tablename}.
|
448 | *
|
449 | * @returns {Collection} A collection of related models.
|
450 | */
|
451 | morphMany: function morphMany(Target, name, columnNames, morphValue) {
|
452 | return this._morphOneOrMany(Target, name, columnNames, morphValue, 'morphMany');
|
453 | },
|
454 |
|
455 |
|
456 | /**
|
457 | * The {@link Model#morphTo morphTo} relation is used to specify the inverse
|
458 | * of the {@link Model#morphOne morphOne} or {@link Model#morphMany
|
459 | * morphMany} relations, where the `targets` must be passed to signify which
|
460 | * {@link Model models} are the potential opposite end of the {@link
|
461 | * polymorphicRelation polymorphic relation}.
|
462 | *
|
463 | * let Photo = bookshelf.Model.extend({
|
464 | * tableName: 'photos',
|
465 | * imageable: function() {
|
466 | * return this.morphTo('imageable', Site, Post);
|
467 | * }
|
468 | * });
|
469 | *
|
470 | * And with custom columnNames:
|
471 | *
|
472 | * let Photo = bookshelf.Model.extend({
|
473 | * tableName: 'photos',
|
474 | * imageable: function() {
|
475 | * return this.morphTo('imageable', ["ImageableType", "ImageableId"], Site, Post);
|
476 | * }
|
477 | * });
|
478 | *
|
479 | * And with custom morphValues, the inverse of morphValue of
|
480 | * {@link Model#morphOne morphOne} and {@link Model#morphMany morphMany},
|
481 | * where the `morphValues` may be optionally set to check against a
|
482 | * different value in the `_type` column than the {@link Model#tableName};
|
483 | * for example, a more descriptive name, or a name that betters adheres to
|
484 | * whatever standard you are using for models.
|
485 | *
|
486 | * let Photo = bookshelf.Model.extend({
|
487 | * tableName: 'photos',
|
488 | * imageable: function() {
|
489 | * return this.morphTo('imageable', [Site, "favicon"], [Post, "cover_photo"]);
|
490 | * }
|
491 | * });
|
492 | *
|
493 | * @method Model#morphTo
|
494 | *
|
495 | * @param {string} name Prefix for `_id` and `_type` columns.
|
496 | * @param {(string[])=} columnNames
|
497 | *
|
498 | * Array containing two column names, the first is the `_type`, the second is the `_id`.
|
499 | *
|
500 | * @param {...Model} Target Constructor of {@link Model} targeted by join.
|
501 | *
|
502 | * @returns {Model}
|
503 | */
|
504 | morphTo: function morphTo(morphName) {
|
505 | if (!_lodash2.default.isString(morphName)) throw new Error('The `morphTo` name must be specified.');
|
506 | var columnNames = void 0,
|
507 | candidates = void 0;
|
508 | if (_lodash2.default.isNil(arguments[1]) || _lodash2.default.isArray(arguments[1]) && _lodash2.default.isString(arguments[1][0])) {
|
509 | columnNames = arguments[1] || null; // may be `null` or `undefined`
|
510 | candidates = _lodash2.default.drop(arguments, 2);
|
511 | } else {
|
512 | columnNames = null;
|
513 | candidates = _lodash2.default.drop(arguments, 1);
|
514 | }
|
515 |
|
516 | candidates = _lodash2.default.map(candidates, function (target) {
|
517 | if (_lodash2.default.isArray(target)) {
|
518 | return target;
|
519 | }
|
520 |
|
521 | // Set up the morphValue by default as the tableName
|
522 | return [target, _lodash2.default.result(target.prototype, 'tableName')];
|
523 | });
|
524 |
|
525 | return this._relation('morphTo', null, { morphName: morphName, columnNames: columnNames, candidates: candidates }).init(this);
|
526 | },
|
527 |
|
528 |
|
529 | /**
|
530 | * Helps to create dynamic relations between {@link Model models} and {@link
|
531 | * Collection collections}, where a {@link Model#hasOne hasOne}, {@link
|
532 | * Model#hasMany hasMany}, {@link Model#belongsTo belongsTo}, or {@link
|
533 | * Model#belongsToMany belongsToMany} relation may run through a `JoinModel`.
|
534 | *
|
535 | * A good example of where this would be useful is if a book {@link
|
536 | * Model#hasMany hasMany} paragraphs through chapters. Consider the following examples:
|
537 | *
|
538 | *
|
539 | * let Book = bookshelf.Model.extend({
|
540 | * tableName: 'books',
|
541 | *
|
542 | * // Find all paragraphs associated with this book, by
|
543 | * // passing through the "Chapter" model.
|
544 | * paragraphs: function() {
|
545 | * return this.hasMany(Paragraph).through(Chapter);
|
546 | * },
|
547 | *
|
548 | * chapters: function() {
|
549 | * return this.hasMany(Chapter);
|
550 | * }
|
551 | * });
|
552 | *
|
553 | * let Chapter = bookshelf.Model.extend({
|
554 | * tableName: 'chapters',
|
555 | *
|
556 | * paragraphs: function() {
|
557 | * return this.hasMany(Paragraph);
|
558 | * }
|
559 | * });
|
560 | *
|
561 | * let Paragraph = bookshelf.Model.extend({
|
562 | * tableName: 'paragraphs',
|
563 | *
|
564 | * chapter: function() {
|
565 | * return this.belongsTo(Chapter);
|
566 | * },
|
567 | *
|
568 | * // A reverse relation, where we can get the book from the chapter.
|
569 | * book: function() {
|
570 | * return this.belongsTo(Book).through(Chapter);
|
571 | * }
|
572 | * });
|
573 | *
|
574 | * The "through" table creates a pivot model, which it assigns to {@link
|
575 | * Model#pivot model.pivot} after it is created. On {@link Model#toJSON
|
576 | * toJSON}, the pivot model is flattened to values prefixed with
|
577 | * `_pivot_`.
|
578 | *
|
579 | * @method Model#through
|
580 | * @param {Model} Interim Pivot model.
|
581 | * @param {string=} throughForeignKey
|
582 | *
|
583 | * Foreign key in this model. By default, the `foreignKey` is assumed to
|
584 | * be the singular form of the `Target` model's tableName, followed by `_id` /
|
585 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
586 | *
|
587 | * @param {string=} otherKey
|
588 | *
|
589 | * Foreign key in the `Interim` model. By default, the `otherKey` is assumed to
|
590 | * be the singular form of this model's tableName, followed by `_id` /
|
591 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
592 | *
|
593 | * @param {string=} throughForeignKeyTarget
|
594 | *
|
595 | * Column in the `Target` model which `throughForeignKey` references, if other
|
596 | * than `Target` model's `id` / `{@link Model#idAttribute idAttribute}`.
|
597 | *
|
598 | * @param {string=} otherKeyTarget
|
599 | *
|
600 | * Column in this model which `otherKey` references, if other
|
601 | * than `id` / `{@link Model#idAttribute idAttribute}`.
|
602 | *
|
603 | * @returns {Collection}
|
604 | */
|
605 | through: function through(Interim, throughForeignKey, otherKey, throughForeignKeyTarget, otherKeyTarget) {
|
606 | return this.relatedData.through(this, Interim, {
|
607 | throughForeignKey: throughForeignKey, otherKey: otherKey, throughForeignKeyTarget: throughForeignKeyTarget, otherKeyTarget: otherKeyTarget
|
608 | });
|
609 | },
|
610 |
|
611 |
|
612 | /**
|
613 | * @method Model#refresh
|
614 | * @since 0.8.2
|
615 | * @description
|
616 | *
|
617 | * Update the attributes of a model, fetching it by its primary key. If no
|
618 | * attribute matches its {@link Model#idAttribute idAttribute}, then fetch by
|
619 | * all available fields.
|
620 | *
|
621 | * @param {Object} options
|
622 | * A hash of options. See {@link Model#fetch} for details.
|
623 | * @returns {Promise<Model>}
|
624 | * A promise resolving to this model.
|
625 | */
|
626 | refresh: function refresh(options) {
|
627 |
|
628 | // If this is new, we use all its attributes. Otherwise we just grab the
|
629 | // primary key.
|
630 | var attributes = this.isNew() ? this.attributes : _lodash2.default.pick(this.attributes, this.idAttribute);
|
631 |
|
632 | return this._doFetch(attributes, options);
|
633 | },
|
634 |
|
635 |
|
636 | /**
|
637 | * Fetches a {@link Model model} from the database, using any {@link
|
638 | * Model#attributes attributes} currently set on the model to form a `select`
|
639 | * query.
|
640 | *
|
641 | * A {@link Model#event:fetching "fetching"} event will be fired just before the
|
642 | * record is fetched; a good place to hook into for validation. {@link
|
643 | * Model#event:fetched "fetched"} event will be fired when a record is
|
644 | * successfully retrieved.
|
645 | *
|
646 | * If you need to constrain the query
|
647 | * performed by fetch, you can call {@link Model#query query} before calling
|
648 | * {@link Model#fetch fetch}.
|
649 | *
|
650 | * // select * from `books` where `ISBN-13` = '9780440180296'
|
651 | * new Book({'ISBN-13': '9780440180296'})
|
652 | * .fetch()
|
653 | * .then(function(model) {
|
654 | * // outputs 'Slaughterhouse Five'
|
655 | * console.log(model.get('title'));
|
656 | * });
|
657 | *
|
658 | * _If you'd like to only fetch specific columns, you may specify a `columns`
|
659 | * property in the `options` for the {@link Model#fetch fetch} call, or use
|
660 | * {@link Model#query query}, tapping into the {@link Knex} {@link
|
661 | * Knex#column column} method to specify which columns will be fetched._
|
662 | *
|
663 | * A single property, or an array of properties can be specified as a value for
|
664 | * the `withRelated` property. You can also execute callbacks on relations
|
665 | * queries (eg. for sorting a relation). The results of these relation queries
|
666 | * will be loaded into a {@link Model#relations relations} property on the
|
667 | * model, may be retrieved with the {@link Model#related related} method, and
|
668 | * will be serialized as properties on a {@link Model#toJSON toJSON} call
|
669 | * unless `{shallow: true}` is passed.
|
670 | *
|
671 | * let Book = bookshelf.Model.extend({
|
672 | * tableName: 'books',
|
673 | * editions: function() {
|
674 | * return this.hasMany(Edition);
|
675 | * },
|
676 | * chapters: function() {
|
677 | * return this.hasMany(Chapter);
|
678 | * },
|
679 | * genre: function() {
|
680 | * return this.belongsTo(Genre);
|
681 | * }
|
682 | * })
|
683 | *
|
684 | * new Book({'ISBN-13': '9780440180296'}).fetch({
|
685 | * withRelated: [
|
686 | * 'genre', 'editions',
|
687 | * { chapters: function(query) { query.orderBy('chapter_number'); }}
|
688 | * ]
|
689 | * }).then(function(book) {
|
690 | * console.log(book.related('genre').toJSON());
|
691 | * console.log(book.related('editions').toJSON());
|
692 | * console.log(book.toJSON());
|
693 | * });
|
694 | *
|
695 | * @method Model#fetch
|
696 | *
|
697 | * @param {Object=} options - Hash of options.
|
698 | * @param {Boolean=} [options.require=false]
|
699 | * Reject the returned response with a {@link Model.NotFoundError
|
700 | * NotFoundError} if results are empty.
|
701 | * @param {string|string[]} [options.columns='*']
|
702 | * Specify columns to be retrieved.
|
703 | * @param {Transaction} [options.transacting]
|
704 | * Optionally run the query in a transaction.
|
705 | * @param {string|Object|mixed[]} [options.withRelated]
|
706 | * Relations to be retrieved with `Model` instance. Either one or more
|
707 | * relation names or objects mapping relation names to query callbacks.
|
708 | *
|
709 | * @fires Model#fetching
|
710 | * @fires Model#fetched
|
711 | *
|
712 | * @throws {Model.NotFoundError}
|
713 | *
|
714 | * @returns {Promise<Model|null>}
|
715 | * A promise resolving to the fetched {@link Model model} or `null` if
|
716 | * none exists.
|
717 | *
|
718 | */
|
719 | fetch: function fetch(options) {
|
720 |
|
721 | // Fetch uses all set attributes.
|
722 | return this._doFetch(this.attributes, options);
|
723 | },
|
724 |
|
725 |
|
726 | _doFetch: _promise2.default.method(function (attributes, options) {
|
727 | options = options ? _lodash2.default.clone(options) : {};
|
728 |
|
729 | // Run the `first` call on the `sync` object to fetch a single model.
|
730 | return this.sync(options).first(attributes).bind(this)
|
731 |
|
732 | // Jump the rest of the chain if the response doesn't exist...
|
733 | .tap(function (response) {
|
734 | if (!response || response.length === 0) {
|
735 | throw new this.constructor.NotFoundError('EmptyResponse');
|
736 | }
|
737 | })
|
738 |
|
739 | // Now, load all of the data into the model as necessary.
|
740 | .tap(this._handleResponse)
|
741 |
|
742 | // If the "withRelated" is specified, we also need to eager load all of the
|
743 | // data on the model, as a side-effect, before we ultimately jump into the
|
744 | // next step of the model. Since the `columns` are only relevant to the
|
745 | // current level, ensure those are omitted from the options.
|
746 | .tap(function (response) {
|
747 | if (options.withRelated) {
|
748 | return this._handleEager(response, _lodash2.default.omit(options, 'columns'));
|
749 | }
|
750 | }).tap(function (response) {
|
751 |
|
752 | /**
|
753 | * Fired after a `fetch` operation. A promise may be returned from the
|
754 | * event handler for async behaviour.
|
755 | *
|
756 | * @event Model#fetched
|
757 | * @param {Model} model
|
758 | * The model firing the event.
|
759 | * @param {Object} response
|
760 | * Knex query response.
|
761 | * @param {Object} options
|
762 | * Options object passed to {@link Model#fetch fetch}.
|
763 | * @returns {Promise}
|
764 | * If the handler returns a promise, `fetch` will wait for it to
|
765 | * be resolved.
|
766 | */
|
767 | return this.triggerThen('fetched', this, response, options);
|
768 | }).return(this).catch(this.constructor.NotFoundError, function (err) {
|
769 | if (options.require) {
|
770 | throw err;
|
771 | }
|
772 | return null;
|
773 | });
|
774 | }),
|
775 |
|
776 | // Private for now.
|
777 | all: function all() {
|
778 | var collection = this.constructor.collection();
|
779 | collection._knex = this.query().clone();
|
780 | this.resetQuery();
|
781 | if (this.relatedData) collection.relatedData = this.relatedData;
|
782 | return collection;
|
783 | },
|
784 |
|
785 |
|
786 | /**
|
787 | * @method Model#count
|
788 | * @since 0.8.2
|
789 | * @description
|
790 | *
|
791 | * Gets the number of matching records in the database, respecting any
|
792 | * previous calls to {@link Model#query}.
|
793 | *
|
794 | * @example
|
795 | *
|
796 | * Duck.where('color', 'blue').count('name')
|
797 | * .then(function(count) { //...
|
798 | *
|
799 | * @param {string} [column='*']
|
800 | * Specify a column to count - rows with null values in this column will be excluded.
|
801 | * @param {Object=} options
|
802 | * Hash of options.
|
803 | * @returns {Promise<Number>}
|
804 | * A promise resolving to the number of matching rows.
|
805 | */
|
806 | count: function count(column, options) {
|
807 | return this.all().count(column, options);
|
808 | },
|
809 |
|
810 |
|
811 | /**
|
812 | * Fetches a collection of {@link Model models} from the database, using any
|
813 | * query parameters currently set on the model to form a select query. Returns
|
814 | * a promise, which will resolve with the fetched collection. If you wish to
|
815 | * trigger an error if no models are found, pass {require: true} as one of
|
816 | * the options to the `fetchAll` call.
|
817 | *
|
818 | * If you need to constrain the query performed by fetch, you can call the
|
819 | * {@link Model#query query} method before calling fetch.
|
820 | *
|
821 | * @method Model#fetchAll
|
822 | *
|
823 | * @param {Object=} options - Hash of options.
|
824 | * @param {Boolean=} [options.require=false]
|
825 | *
|
826 | * Rejects the returned promise with an `Collection.EmptyError` if no records are returned.
|
827 | *
|
828 | * @param {Transaction=} options.transacting
|
829 | *
|
830 | * Optionally run the query in a transaction.
|
831 | *
|
832 | * @fires Model#fetching:collection
|
833 | * @fires Model#fetched:collection
|
834 | *
|
835 | * @throws {Collection.EmptyError}
|
836 | *
|
837 | * Rejects the promise in the event of an empty response if the `require: true` option.
|
838 | *
|
839 | * @returns {Promise<Collection>} A promise resolving to the fetched {@link Collection collection}.
|
840 | *
|
841 | */
|
842 | fetchAll: function fetchAll(options) {
|
843 | var _this = this;
|
844 |
|
845 | var collection = this.all();
|
846 | return collection.once('fetching', function (__, columns, opts) {
|
847 | /**
|
848 | * Fired before a {@link Model#fetchAll fetchAll} operation. A promise
|
849 | * may be returned from the event handler for async behaviour.
|
850 | *
|
851 | * @event Model#fetching:collection
|
852 | * @param {Model} collection The collection that has been fetched.
|
853 | * @param {string[]} columns The columns being retrieved by the query.
|
854 | * @param {Object} options Options object passed to {@link Model#fetchAll fetchAll}.
|
855 | * @returns {Promise}
|
856 | */
|
857 | return _this.triggerThen('fetching:collection', collection, columns, opts);
|
858 | }).once('fetched', function (__, resp, opts) {
|
859 | /**
|
860 | * Fired after a {@link Model#fetchAll fetchAll} operation. A promise
|
861 | * may be returned from the event handler for async behaviour.
|
862 | *
|
863 | * @event Model#fetched:collection
|
864 | * @param {Model} collection The collection that has been fetched.
|
865 | * @param {Object} resp The Knex query response.
|
866 | * @param {Object} options Options object passed to {@link Model#fetchAll fetchAll}.
|
867 | * @returns {Promise}
|
868 | */
|
869 | return _this.triggerThen('fetched:collection', collection, resp, opts);
|
870 | }).fetch(options);
|
871 | },
|
872 |
|
873 |
|
874 | /**
|
875 | * @method Model#load
|
876 | * @description
|
877 | * The load method takes an array of relations to eager load attributes onto a
|
878 | * {@link Model}, in a similar way that the `withRelated` property works on
|
879 | * {@link Model#fetch fetch}. Dot separated attributes may be used to specify deep
|
880 | * eager loading.
|
881 | *
|
882 | * @example
|
883 | * new Posts().fetch().then(function(collection) {
|
884 | * collection.at(0)
|
885 | * .load(['author', 'content', 'comments.tags'])
|
886 | * .then(function(model) {
|
887 | * JSON.stringify(model);
|
888 | * });
|
889 | * });
|
890 | *
|
891 | * {
|
892 | * title: 'post title',
|
893 | * author: {...},
|
894 | * content: {...},
|
895 | * comments: [
|
896 | * {tags: [...]}, {tags: [...]}
|
897 | * ]
|
898 | * }
|
899 | *
|
900 | * @param {string|string[]} relations The relation, or relations, to be loaded.
|
901 | * @param {Object=} options Hash of options.
|
902 | * @param {Transaction=} options.transacting
|
903 | * Optionally run the query in a transaction.
|
904 | * @returns {Promise<Model>} A promise resolving to this {@link Model model}
|
905 | */
|
906 | load: _promise2.default.method(function (relations, options) {
|
907 | var columns = this.format((0, _extends3.default)({}, this.attributes));
|
908 | var withRelated = _lodash2.default.isArray(relations) ? relations : [relations];
|
909 | return this._handleEager([columns], (0, _extends3.default)({}, options, { shallow: true, withRelated: withRelated })).return(this);
|
910 | }),
|
911 |
|
912 | /**
|
913 | * @method Model#save
|
914 | * @description
|
915 | *
|
916 | * This method is used to perform either an insert or update query using the
|
917 | * model's set {@link Model#attributes attributes}.
|
918 | *
|
919 | * If the model {@link Model#isNew isNew}, any {@link Model#defaults defaults}
|
920 | * will be set and an `insert` query will be performed. Otherwise it will
|
921 | * `update` the record with a corresponding ID. It is also possible to
|
922 | * set default attributes on an `update` by passing the `{defaults: true}`
|
923 | * option in the second argument to the `save` call. This will also use the
|
924 | * same {@link Model#defaults defaults} as the `insert` operation.
|
925 | *
|
926 | * The type of operation to perform (either `insert` or `update`) can be
|
927 | * overriden with the `method` option:
|
928 | *
|
929 | * // This forces an insert with the specified id instead of the expected
|
930 | * // update
|
931 | * new Post({name: 'New Article', id: 34})
|
932 | * .save(null, {method: 'insert'})
|
933 | * .then(function(model) {
|
934 | * // ...
|
935 | * });
|
936 | *
|
937 | * If you only wish to update with the params passed to the save, you may pass
|
938 | * a `{patch: true}` option in the second argument to `save`:
|
939 | *
|
940 | * // UPDATE authors SET "bio" = 'Short user bio' WHERE "id" = 1
|
941 | * new Author({id: 1, first_name: 'User'})
|
942 | * .save({bio: 'Short user bio'}, {patch: true})
|
943 | * .then(function(model) {
|
944 | * // ...
|
945 | * });
|
946 | *
|
947 | * Several events fired on the model when saving: a {@link Model#creating
|
948 | * "creating"}, or {@link Model#updating "updating"} event if the model is
|
949 | * being inserted or updated, and a "saving" event in either case. To
|
950 | * prevent saving the model (with validation, etc.), throwing an error inside
|
951 | * one of these event listeners will stop saving the model and reject the
|
952 | * promise. A {@link Model#created "created"}, or {@link Model#"updated"}
|
953 | * event is fired after the model is saved, as well as a {@link Model#saved
|
954 | * "saved"} event either way. If you wish to modify the query when the {@link
|
955 | * Model#saving "saving"} event is fired, the knex query object should is
|
956 | * available in `options.query`.
|
957 | *
|
958 | * // Save with no arguments
|
959 | * Model.forge({id: 5, firstName: 'John', lastName: 'Smith'}).save().then(function() {
|
960 | * //...
|
961 | * });
|
962 | *
|
963 | * // Or add attributes during save
|
964 | * Model.forge({id: 5}).save({firstName: 'John', lastName: 'Smith'}).then(function() {
|
965 | * //...
|
966 | * });
|
967 | *
|
968 | * // Or, if you prefer, for a single attribute
|
969 | * Model.forge({id: 5}).save('name', 'John Smith').then(function() {
|
970 | * //...
|
971 | * });
|
972 | *
|
973 | * @param {string=} key Attribute name.
|
974 | * @param {string=} val Attribute value.
|
975 | * @param {Object=} attrs A hash of attributes.
|
976 | * @param {Object=} options
|
977 | * @param {Transaction=} options.transacting
|
978 | * Optionally run the query in a transaction.
|
979 | * @param {string=} options.method
|
980 | * Explicitly select a save method, either `"update"` or `"insert"`.
|
981 | * @param {Boolean} [options.defaults=false]
|
982 | * Whether to assign or not {@link Model#defaults default} attribute values
|
983 | * on a model when performing an update or create operation.
|
984 | * @param {Boolean} [options.patch=false]
|
985 | * Only save attributes supplied in arguments to `save`.
|
986 | * @param {Boolean} [options.require=true]
|
987 | * Throw a {@link Model.NoRowsUpdatedError} if no records are affected by save.
|
988 | *
|
989 | * @fires Model#saving
|
990 | * @fires Model#creating
|
991 | * @fires Model#updating
|
992 | * @fires Model#created
|
993 | * @fires Model#updated
|
994 | * @fires Model#saved
|
995 | *
|
996 | * @throws {Model.NoRowsUpdatedError}
|
997 | *
|
998 | * @returns {Promise<Model>} A promise resolving to the saved and updated model.
|
999 | */
|
1000 | save: _promise2.default.method(function (key, val, options) {
|
1001 | var attrs = void 0;
|
1002 |
|
1003 | // Handle both `"key", value` and `{key: value}` -style arguments.
|
1004 | if (key == null || (typeof key === 'undefined' ? 'undefined' : (0, _typeof3.default)(key)) === "object") {
|
1005 | attrs = key || {};
|
1006 | options = _lodash2.default.clone(val) || {};
|
1007 | } else {
|
1008 | (attrs = {})[key] = val;
|
1009 | options = options ? _lodash2.default.clone(options) : {};
|
1010 | }
|
1011 |
|
1012 | return _promise2.default.bind(this).then(function () {
|
1013 | return this.saveMethod(options);
|
1014 | }).then(function (method) {
|
1015 |
|
1016 | // Determine whether which kind of save we will do, update or insert.
|
1017 | options.method = method;
|
1018 |
|
1019 | // If the object is being created, we merge any defaults here rather than
|
1020 | // during object creation.
|
1021 | if (method === 'insert' || options.defaults) {
|
1022 | var defaults = _lodash2.default.result(this, 'defaults');
|
1023 | if (defaults) {
|
1024 | attrs = _lodash2.default.defaultsDeep({}, attrs, this.attributes, defaults);
|
1025 | }
|
1026 | }
|
1027 |
|
1028 | // Set the attributes on the model. Note that we do this before adding
|
1029 | // timestamps, as `timestamp` calls `set` internally.
|
1030 | this.set(attrs, { silent: true });
|
1031 |
|
1032 | var _getTimestampKeys = this.getTimestampKeys(),
|
1033 | _getTimestampKeys2 = (0, _slicedToArray3.default)(_getTimestampKeys, 2),
|
1034 | createdAtKey = _getTimestampKeys2[0],
|
1035 | updatedAtKey = _getTimestampKeys2[1];
|
1036 |
|
1037 | // Now set timestamps if appropriate. Extend `attrs` so that the
|
1038 | // timestamps will be provided for a patch operation.
|
1039 |
|
1040 |
|
1041 | if (this.hasTimestamps) {
|
1042 | //If some of the new attributes are value for update_at or created_at columns disable the possibility for the timestamp function to update the columns
|
1043 | var editUpdatedAt = attrs[updatedAtKey] ? false : true;
|
1044 | var editCreatedAt = attrs[createdAtKey] ? false : true;
|
1045 | var additionalOptions = {
|
1046 | silent: true,
|
1047 | editUpdatedAt: editUpdatedAt,
|
1048 | editCreatedAt: editCreatedAt
|
1049 | };
|
1050 | _lodash2.default.extend(attrs, this.timestamp(_lodash2.default.extend(options, additionalOptions)));
|
1051 | }
|
1052 |
|
1053 | // If there are any save constraints, set them on the model.
|
1054 | if (this.relatedData && this.relatedData.type !== 'morphTo') {
|
1055 | _helpers2.default.saveConstraints(this, this.relatedData);
|
1056 | }
|
1057 |
|
1058 | // Gives access to the `query` object in the `options`, in case we need it
|
1059 | // in any event handlers.
|
1060 | var sync = this.sync(options);
|
1061 | options.query = sync.query;
|
1062 |
|
1063 | /**
|
1064 | * Saving event.
|
1065 | *
|
1066 | * Fired before an `insert` or `update` query. A promise may be
|
1067 | * returned from the event handler for async behaviour. Throwing an
|
1068 | * exception from the handler will cancel the save.
|
1069 | *
|
1070 | * @event Model#saving
|
1071 | * @param {Model} model
|
1072 | * The model firing the event. Its attributes are already changed but
|
1073 | * not commited to the database yet.
|
1074 | * @param {Object} attrs
|
1075 | * Attributes that will be inserted or updated.
|
1076 | *
|
1077 | * **Note**: There's currently a bug that leads to attrs only
|
1078 | * containing attributes that were passed as argument to
|
1079 | * {@link Model#save save}. You can work around this by accessing
|
1080 | * `model.changed` which does contain all the attributes that will be
|
1081 | * inserted or updated.
|
1082 | * @param {Object} options Options object passed to {@link Model#save save}.
|
1083 | * @returns {Promise}
|
1084 | */
|
1085 |
|
1086 | /**
|
1087 | * Creating event.
|
1088 | *
|
1089 | * Fired before `insert` query. A promise may be
|
1090 | * returned from the event handler for async behaviour. Throwing an
|
1091 | * exception from the handler will cancel the save operation.
|
1092 | *
|
1093 | * @event Model#creating
|
1094 | * @param {Model} model The model firing the event.
|
1095 | * @param {Object} attrs
|
1096 | * Attributes that will be inserted.
|
1097 | *
|
1098 | * **Note**: There's currently a bug that leads to attrs only
|
1099 | * containing attributes that were passed as argument to
|
1100 | * {@link Model#save save}. You can work around this by accessing
|
1101 | * `model.changed` which does contain all the attributes that will be
|
1102 | * inserted.
|
1103 | * @param {Object} options Options object passed to {@link Model#save save}.
|
1104 | * @returns {Promise}
|
1105 | */
|
1106 |
|
1107 | /**
|
1108 | * Updating event.
|
1109 | *
|
1110 | * Fired before `update` query. A promise may be
|
1111 | * returned from the event handler for async behaviour. Throwing an
|
1112 | * exception from the handler will cancel the save operation.
|
1113 | *
|
1114 | * @event Model#updating
|
1115 | * @param {Model} model
|
1116 | * The model firing the event. Its attributes are already changed but
|
1117 | * not commited to the database yet.
|
1118 | * @param {Object} attrs
|
1119 | * Attributes that will be updated.
|
1120 | *
|
1121 | * **Note**: There's currently a bug that leads to attrs only
|
1122 | * containing attributes that were passed as argument to
|
1123 | * {@link Model#save save}. You can work around this by accessing
|
1124 | * `model.changed` which does contain all the attributes that will be
|
1125 | * updated.
|
1126 | * @param {Object} options Options object passed to {@link Model#save save}.
|
1127 | * @returns {Promise}
|
1128 | */
|
1129 | return this.triggerThen(method === 'insert' ? 'saving creating' : 'saving updating', this, attrs, options).bind(this).then(function () {
|
1130 | return sync[options.method](method === 'update' && options.patch ? attrs : this.attributes);
|
1131 | }).then(function (resp) {
|
1132 | // After a successful database save, the id is updated if the model was created
|
1133 | if (method === 'insert' && this.id == null) {
|
1134 | var updatedCols = {};
|
1135 | updatedCols[this.idAttribute] = this.id = resp[0];
|
1136 | var updatedAttrs = this.parse(updatedCols);
|
1137 | _lodash2.default.assign(this.attributes, updatedAttrs);
|
1138 | } else if (method === 'update' && resp === 0) {
|
1139 | if (options.require !== false) {
|
1140 | throw new this.constructor.NoRowsUpdatedError('No Rows Updated');
|
1141 | }
|
1142 | }
|
1143 |
|
1144 | // In case we need to reference the `previousAttributes` for the this
|
1145 | // in the following event handlers.
|
1146 | options.previousAttributes = this._previousAttributes;
|
1147 |
|
1148 | this._reset();
|
1149 |
|
1150 | /**
|
1151 | * Saved event.
|
1152 | *
|
1153 | * Fired after an `insert` or `update` query.
|
1154 | *
|
1155 | * @event Model#saved
|
1156 | * @param {Model} model The model firing the event.
|
1157 | * @param {(Array|Number)} response
|
1158 | * A list containing the id of the newly created model in case of an
|
1159 | * `insert` or a number representing the affected rows in the case of
|
1160 | * an `update` query.
|
1161 | * @param {Object} options Options object passed to {@link Model#save save}.
|
1162 | * @returns {Promise}
|
1163 | */
|
1164 |
|
1165 | /**
|
1166 | * Created event.
|
1167 | *
|
1168 | * Fired after an `insert` query.
|
1169 | *
|
1170 | * @event Model#created
|
1171 | * @param {Model} model The model firing the event.
|
1172 | * @param {Array} newId A list containing the id of the newly created model.
|
1173 | * @param {Object} options Options object passed to {@link Model#save save}.
|
1174 | * @returns {Promise}
|
1175 | */
|
1176 |
|
1177 | /**
|
1178 | * Updated event.
|
1179 | *
|
1180 | * Fired after an `update` query.
|
1181 | *
|
1182 | * @event Model#updated
|
1183 | * @param {Model} model The model firing the event.
|
1184 | * @param {Number} affectedRows Number of rows affected by the update.
|
1185 | * @param {Object} options Options object passed to {@link Model#save save}.
|
1186 | * @returns {Promise}
|
1187 | */
|
1188 | return this.triggerThen(method === 'insert' ? 'created saved' : 'updated saved', this, resp, options);
|
1189 | });
|
1190 | }).return(this);
|
1191 | }),
|
1192 |
|
1193 | /**
|
1194 | * `destroy` performs a `delete` on the model, using the model's {@link
|
1195 | * Model#idAttribute idAttribute} to constrain the query.
|
1196 | *
|
1197 | * A {@link Model#destroying "destroying"} event is triggered on the model before being
|
1198 | * destroyed. To prevent destroying the model (with validation, etc.), throwing an error
|
1199 | * inside one of these event listeners will stop destroying the model and
|
1200 | * reject the promise.
|
1201 | *
|
1202 | * A {@link Model#destroyed "destroyed"} event is fired after the model's
|
1203 | * removal is completed.
|
1204 | *
|
1205 | * @method Model#destroy
|
1206 | *
|
1207 | * @param {Object=} options Hash of options.
|
1208 | * @param {Transaction=} options.transacting Optionally run the query in a transaction.
|
1209 | * @param {Boolean} [options.require=true]
|
1210 | * Throw a {@link Model.NoRowsDeletedError} if no records are affected by destroy. This is
|
1211 | * the default behavior as of version 0.13.0.
|
1212 | *
|
1213 | * @example
|
1214 | *
|
1215 | * new User({id: 1})
|
1216 | * .destroy()
|
1217 | * .then(function(model) {
|
1218 | * // ...
|
1219 | * });
|
1220 | *
|
1221 | * @fires Model#destroying
|
1222 | * @fires Model#destroyed
|
1223 | *
|
1224 | * @throws {Model.NoRowsDeletedError}
|
1225 | *
|
1226 | * @returns {Promise<Model>} A promise resolving to the destroyed and thus
|
1227 | * empty model, i.e. all attributes are `undefined`.
|
1228 | */
|
1229 | destroy: _promise2.default.method(function (options) {
|
1230 | options = options ? _lodash2.default.clone(options) : {};
|
1231 | var sync = this.sync(options);
|
1232 | options.query = sync.query;
|
1233 | return _promise2.default.bind(this).then(function () {
|
1234 |
|
1235 | /**
|
1236 | * Destroying event.
|
1237 | *
|
1238 | * Fired before a `delete` query. A promise may be returned from the event
|
1239 | * handler for async behaviour. Throwing an exception from the handler
|
1240 | * will reject the promise and cancel the deletion.
|
1241 | *
|
1242 | * @event Model#destroying
|
1243 | * @param {Model} model The model firing the event.
|
1244 | * @param {Object} options Options object passed to {@link Model#destroy destroy}.
|
1245 | * @returns {Promise}
|
1246 | */
|
1247 | return this.triggerThen('destroying', this, options);
|
1248 | }).then(function () {
|
1249 | return sync.del();
|
1250 | }).then(function (affectedRows) {
|
1251 | if (options.require !== false && affectedRows === 0) {
|
1252 | throw new this.constructor.NoRowsDeletedError('No Rows Deleted');
|
1253 | }
|
1254 | this.clear();
|
1255 |
|
1256 | /**
|
1257 | * Destroyed event.
|
1258 | *
|
1259 | * Fired after a `delete` query. A promise may be returned from the event
|
1260 | * handler for async behaviour.
|
1261 | *
|
1262 | * @event Model#destroyed
|
1263 | * @param {Model} model The model firing the event.
|
1264 | * @param {Object} options Options object passed to {@link Model#destroy destroy}.
|
1265 | * @returns {Promise}
|
1266 | */
|
1267 | return this.triggerThen('destroyed', this, options);
|
1268 | }).then(this._reset);
|
1269 | }),
|
1270 |
|
1271 | /**
|
1272 | * Used to reset the internal state of the current query builder instance.
|
1273 | * This method is called internally each time a database action is completed
|
1274 | * by {@link Sync}
|
1275 | *
|
1276 | * @method Model#resetQuery
|
1277 | * @returns {Model} Self, this method is chainable.
|
1278 | */
|
1279 | resetQuery: function resetQuery() {
|
1280 | this._knex = null;
|
1281 | return this;
|
1282 | },
|
1283 |
|
1284 |
|
1285 | /**
|
1286 | * The `query` method is used to tap into the underlying Knex query builder
|
1287 | * instance for the current model. If called with no arguments, it will
|
1288 | * return the query builder directly. Otherwise, it will call the specified
|
1289 | * method on the query builder, applying any additional arguments from the
|
1290 | * `model.query` call. If the method argument is a function, it will be
|
1291 | * called with the Knex query builder as the context and the first argument,
|
1292 | * returning the current model.
|
1293 | *
|
1294 | * @example
|
1295 | *
|
1296 | * model
|
1297 | * .query('where', 'other_id', '=', '5')
|
1298 | * .fetch()
|
1299 | * .then(function(model) {
|
1300 | * // ...
|
1301 | * });
|
1302 | *
|
1303 | * model
|
1304 | * .query({where: {other_id: '5'}, orWhere: {key: 'value'}})
|
1305 | * .fetch()
|
1306 | * .then(function(model) {
|
1307 | * // ...
|
1308 | * });
|
1309 | *
|
1310 | * model.query(function(qb) {
|
1311 | * qb.where('other_person', 'LIKE', '%Demo').orWhere('other_id', '>', 10);
|
1312 | * }).fetch()
|
1313 | * .then(function(model) {
|
1314 | * // ...
|
1315 | * });
|
1316 | *
|
1317 | * let qb = model.query();
|
1318 | * qb.where({id: 1}).select().then(function(resp) {
|
1319 | * // ...
|
1320 | * });
|
1321 | *
|
1322 | * @method Model#query
|
1323 | * @param {function|Object|...string=} arguments The query method.
|
1324 | * @returns {Model|QueryBuilder}
|
1325 | * Will return this model or, if called with no arguments, the underlying query builder.
|
1326 | *
|
1327 | * @see {@link http://knexjs.org/#Builder Knex `QueryBuilder`}
|
1328 | */
|
1329 | query: function query() {
|
1330 | return _helpers2.default.query(this, _lodash2.default.toArray(arguments));
|
1331 | },
|
1332 |
|
1333 |
|
1334 | /**
|
1335 | * The where method is used as convenience for the most common {@link
|
1336 | * Model#query query} method, adding a where clause to the builder. Any
|
1337 | * additional knex methods may be accessed using {@link Model#query query}.
|
1338 | *
|
1339 | * Accepts either key, value syntax, or a hash of attributes.
|
1340 | *
|
1341 | * @example
|
1342 | *
|
1343 | * model.where('favorite_color', '<>', 'green').fetch().then(function() { //...
|
1344 | * // or
|
1345 | * model.where('favorite_color', 'red').fetch().then(function() { //...
|
1346 | * // or
|
1347 | * model.where({favorite_color: 'red', shoe_size: 12}).fetch().then(function() { //...
|
1348 | *
|
1349 | * @method Model#where
|
1350 | * @param {Object|...string} method
|
1351 | *
|
1352 | * Either `key, [operator], value` syntax, or a hash of attributes to
|
1353 | * match. Note that these must be formatted as they are in the database,
|
1354 | * not how they are stored after {@link Model#parse}.
|
1355 | *
|
1356 | * @returns {Model} Self, this method is chainable.
|
1357 | *
|
1358 | * @see Model#query
|
1359 | */
|
1360 | where: function where() {
|
1361 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
1362 | args[_key] = arguments[_key];
|
1363 | }
|
1364 |
|
1365 | return this.query.apply(this, ['where'].concat(args));
|
1366 | },
|
1367 |
|
1368 |
|
1369 | /**
|
1370 | * @method Model#orderBy
|
1371 | * @since 0.9.3
|
1372 | * @description
|
1373 | *
|
1374 | * Specifies the column to sort on and sort order.
|
1375 | *
|
1376 | * The order parameter is optional, and defaults to 'ASC'. You may
|
1377 | * also specify 'DESC' order by prepending a hyphen to the sort column
|
1378 | * name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`.
|
1379 | *
|
1380 | * Unless specified using dot notation (i.e., "table.column"), the default
|
1381 | * table will be the table name of the model `orderBy` was called on.
|
1382 | *
|
1383 | * @example
|
1384 | *
|
1385 | * Car.forge().orderBy('color', 'ASC').fetchAll()
|
1386 | * .then(function (rows) { // ...
|
1387 | *
|
1388 | * @param sort {string}
|
1389 | * Column to sort on
|
1390 | * @param order {string}
|
1391 | * Ascending ('ASC') or descending ('DESC') order
|
1392 | */
|
1393 | orderBy: function orderBy() {
|
1394 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
|
1395 | args[_key2] = arguments[_key2];
|
1396 | }
|
1397 |
|
1398 | return _helpers2.default.orderBy.apply(_helpers2.default, [this].concat(args));
|
1399 | },
|
1400 |
|
1401 |
|
1402 | /* Ensure that QueryBuilder is copied on clone. */
|
1403 | clone: function clone() {
|
1404 | // This needs to use the direct apply method because the spread operator
|
1405 | // incorrectly converts to `clone.apply(ModelBase.prototype, arguments)`
|
1406 | // instead of `apply(this, arguments)`
|
1407 | var cloned = BookshelfModel.__super__.clone.apply(this, arguments);
|
1408 | if (this._knex != null) {
|
1409 | cloned._knex = cloned._builder(this._knex.clone());
|
1410 | }
|
1411 | return cloned;
|
1412 | },
|
1413 |
|
1414 |
|
1415 | /**
|
1416 | * Creates and returns a new Bookshelf.Sync instance.
|
1417 | *
|
1418 | * @method Model#sync
|
1419 | * @private
|
1420 | * @returns Sync
|
1421 | */
|
1422 | sync: function sync(options) {
|
1423 | return new _sync2.default(this, options);
|
1424 | },
|
1425 |
|
1426 |
|
1427 | /**
|
1428 | * Helper for setting up the `morphOne` or `morphMany` relations.
|
1429 | *
|
1430 | * @method Model#_morphOneOrMany
|
1431 | * @private
|
1432 | */
|
1433 | _morphOneOrMany: function _morphOneOrMany(Target, morphName, columnNames, morphValue, type) {
|
1434 | if (!_lodash2.default.isArray(columnNames)) {
|
1435 | // Shift by one place
|
1436 | morphValue = columnNames;
|
1437 | columnNames = null;
|
1438 | }
|
1439 | if (!morphName || !Target) throw new Error('The polymorphic `name` and `Target` are required.');
|
1440 | return this._relation(type, Target, { morphName: morphName, morphValue: morphValue, columnNames: columnNames }).init(this);
|
1441 | },
|
1442 |
|
1443 |
|
1444 | /**
|
1445 | * @name Model#_handleResponse
|
1446 | * @private
|
1447 | * @description
|
1448 | *
|
1449 | * Handles the response data for the model, returning from the model's fetch call.
|
1450 | *
|
1451 | * @param {Object} Response from Knex query.
|
1452 | *
|
1453 | * @todo: need to check on Backbone's status there, ticket #2636
|
1454 | * @todo: {silent: true, parse: true}, for parity with collection#set
|
1455 | */
|
1456 | _handleResponse: function _handleResponse(response) {
|
1457 | var relatedData = this.relatedData;
|
1458 |
|
1459 | this.set(this.parse(response[0]), { silent: true }).formatTimestamps()._reset();
|
1460 |
|
1461 | if (relatedData && relatedData.isJoined()) {
|
1462 | relatedData.parsePivot([this]);
|
1463 | }
|
1464 | },
|
1465 |
|
1466 |
|
1467 | /**
|
1468 | * @name Model#_handleEager
|
1469 | * @private
|
1470 | * @description
|
1471 | *
|
1472 | * Handles the related data loading on the model.
|
1473 | *
|
1474 | * @param {Object} Response from Knex query.
|
1475 | */
|
1476 | _handleEager: function _handleEager(response, options) {
|
1477 | return new _eager2.default([this], response, this).fetch(options);
|
1478 | }
|
1479 | }, {
|
1480 | extended: function extended(child) {
|
1481 | /**
|
1482 | * @class Model.NotFoundError
|
1483 | * @description
|
1484 | *
|
1485 | * Thrown when no records are found by {@link Model#fetch fetch} or
|
1486 | * {@link Model#refresh} when called with the
|
1487 | * `{require: true}` option.
|
1488 | */
|
1489 | child.NotFoundError = (0, _createError2.default)(this.NotFoundError);
|
1490 |
|
1491 | /**
|
1492 | * @class Model.NoRowsUpdatedError
|
1493 | * @description
|
1494 | *
|
1495 | * Thrown when no records are saved by {@link Model#save save}
|
1496 | * unless called with the `{require: false}` option.
|
1497 | */
|
1498 | child.NoRowsUpdatedError = (0, _createError2.default)(this.NoRowsUpdatedError);
|
1499 |
|
1500 | /**
|
1501 | * @class Model.NoRowsDeletedError
|
1502 | * @description
|
1503 | *
|
1504 | * Thrown when no record is deleted by {@link Model#destroy destroy}
|
1505 | * if called with the `{require: true}` option.
|
1506 | */
|
1507 | child.NoRowsDeletedError = (0, _createError2.default)(this.NoRowsDeletedError);
|
1508 | }
|
1509 | });
|
1510 |
|
1511 | BookshelfModel.NotFoundError = _errors2.default.NotFoundError;
|
1512 | BookshelfModel.NoRowsUpdatedError = _errors2.default.NoRowsUpdatedError;
|
1513 | BookshelfModel.NoRowsDeletedError = _errors2.default.NoRowsDeletedError;
|
1514 |
|
1515 | module.exports = BookshelfModel; |
\ | No newline at end of file |