@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/.eslintrc.json +18 -0
- package/.nycrc.yml +3 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +764 -0
- package/LICENSE +22 -0
- package/README.md +32 -0
- package/bookshelf.js +8 -0
- package/changes.json +14 -0
- package/lib/base/collection.js +802 -0
- package/lib/base/eager.js +111 -0
- package/lib/base/events.js +129 -0
- package/lib/base/model.js +995 -0
- package/lib/base/relation.js +69 -0
- package/lib/bookshelf.js +558 -0
- package/lib/collection.js +545 -0
- package/lib/constants.js +2 -0
- package/lib/eager.js +122 -0
- package/lib/errors.js +36 -0
- package/lib/extend.js +41 -0
- package/lib/helpers.js +212 -0
- package/lib/model.js +1568 -0
- package/lib/relation.js +947 -0
- package/lib/sync.js +244 -0
- package/package.json +111 -0
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;
|