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