1 | ;
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 |
|
7 | var _lodash = require('lodash');
|
8 |
|
9 | var _sync = require('./sync');
|
10 |
|
11 | var _sync2 = _interopRequireDefault(_sync);
|
12 |
|
13 | var _helpers = require('./helpers');
|
14 |
|
15 | var _helpers2 = _interopRequireDefault(_helpers);
|
16 |
|
17 | var _eager = require('./eager');
|
18 |
|
19 | var _eager2 = _interopRequireDefault(_eager);
|
20 |
|
21 | var _errors = require('./errors');
|
22 |
|
23 | var _errors2 = _interopRequireDefault(_errors);
|
24 |
|
25 | var _collection = require('./base/collection');
|
26 |
|
27 | var _collection2 = _interopRequireDefault(_collection);
|
28 |
|
29 | var _promise = require('./base/promise');
|
30 |
|
31 | var _promise2 = _interopRequireDefault(_promise);
|
32 |
|
33 | var _createError = require('create-error');
|
34 |
|
35 | var _createError2 = _interopRequireDefault(_createError);
|
36 |
|
37 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
38 |
|
39 | /**
|
40 | * @class Collection
|
41 | * @extends CollectionBase
|
42 | * @inheritdoc
|
43 | * @classdesc
|
44 | *
|
45 | * Collections are ordered sets of models returned from the database, from a
|
46 | * {@link Model#fetchAll fetchAll} call. They may be used with a suite of
|
47 | * {@link http://lodash.com/ Lodash} methods.
|
48 | *
|
49 | * @constructor
|
50 | * @description
|
51 | *
|
52 | * When creating a {@link Collection}, you may choose to pass in the initial
|
53 | * array of {@link Model models}. The collection's {@link Collection#comparator
|
54 | * comparator} may be included as an option. Passing `false` as the comparator
|
55 | * option will prevent sorting. If you define an {@link Collection#initialize
|
56 | * initialize} function, it will be invoked when the collection is created.
|
57 | *
|
58 | * @example
|
59 | * let tabs = new TabSet([tab1, tab2, tab3]);
|
60 | *
|
61 | * @param {(Model[])=} models Initial array of models.
|
62 | * @param {Object=} options
|
63 | * @param {Boolean} [options.comparator=false]
|
64 | * {@link Collection#comparator Comparator} for collection, or `false` to disable sorting.
|
65 | */
|
66 | var BookshelfCollection = _collection2.default.extend({
|
67 |
|
68 | /**
|
69 | * @method Collection#through
|
70 | * @description
|
71 | * Used to define passthrough relationships - `hasOne`, `hasMany`, `belongsTo`
|
72 | * or `belongsToMany`, "through" an `Interim` model or collection.
|
73 | *
|
74 | * @param {Model} Interim Pivot model.
|
75 | *
|
76 | * @param {string=} throughForeignKey
|
77 | *
|
78 | * Foreign key in this collection model. By default, the `foreignKey` is assumed to
|
79 | * be the singular form of the `Target` model's tableName, followed by `_id` /
|
80 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
81 | *
|
82 | * @param {string=} otherKey
|
83 | *
|
84 | * Foreign key in the `Interim` model. By default, the `otherKey` is assumed to
|
85 | * be the singular form of this model's tableName, followed by `_id` /
|
86 | * `_{{{@link Model#idAttribute idAttribute}}}`.
|
87 | *
|
88 | * @param {string=} throughForeignKeyTarget
|
89 | *
|
90 | * Column in the `Target` model which `throughForeignKey` references, if other
|
91 | * than `Target` model's `id` / `{@link Model#idAttribute idAttribute}`.
|
92 | *
|
93 | * @param {string=} otherKeyTarget
|
94 | *
|
95 | * Column in this collection model which `otherKey` references, if other
|
96 | * than `id` / `{@link Model#idAttribute idAttribute}`.
|
97 | *
|
98 | * @returns {Collection}
|
99 | */
|
100 | through: function through(Interim, throughForeignKey, otherKey, throughForeignKeyTarget, otherKeyTarget) {
|
101 | return this.relatedData.through(this, Interim, {
|
102 | throughForeignKey: throughForeignKey, otherKey: otherKey, throughForeignKeyTarget: throughForeignKeyTarget, otherKeyTarget: otherKeyTarget
|
103 | });
|
104 | },
|
105 |
|
106 | /**
|
107 | * @method Collection#fetch
|
108 | * @description
|
109 | * Fetch the default set of models for this collection from the database,
|
110 | * resetting the collection when they arrive. If you wish to trigger an error
|
111 | * if the fetched collection is empty, pass `{require: true}` as one of the
|
112 | * options to the {@link Collection#fetch fetch} call. A {@link
|
113 | * Collection#fetched "fetched"} event will be fired when records are
|
114 | * successfully retrieved. If you need to constrain the query performed by
|
115 | * `fetch`, you can call the {@link Collection#query query} method before
|
116 | * calling `fetch`.
|
117 | *
|
118 | * *If you'd like to only fetch specific columns, you may specify a `columns`
|
119 | * property in the options for the `fetch` call.*
|
120 | *
|
121 | * The `withRelated` option may be specified to fetch the models of the
|
122 | * collection, eager loading any specified {@link Relation relations} named on
|
123 | * the model. A single property, or an array of properties can be specified as
|
124 | * a value for the `withRelated` property. The results of these relation
|
125 | * queries will be loaded into a relations property on the respective models,
|
126 | * may be retrieved with the {@link Model#related related} method.
|
127 | *
|
128 | * @fires Collection#fetched
|
129 | * @throws {Collection.EmptyError}
|
130 | * Upon a sucessful query resulting in no records returns. Only fired if `require: true` is passed as an option.
|
131 | *
|
132 | * @param {Object=} options
|
133 | * @param {Boolean} [options.require=false] Trigger a {@link Collection.EmptyError} if no records are found.
|
134 | * @param {string|string[]} [options.withRelated=[]] A relation, or list of relations, to be eager loaded as part of the `fetch` operation.
|
135 | * @returns {Promise<Collection>}
|
136 | */
|
137 | fetch: _promise2.default.method(function (options) {
|
138 | options = options ? (0, _lodash.clone)(options) : {};
|
139 | return this.sync(options).select().bind(this).tap(function (response) {
|
140 | if (!response || response.length === 0) {
|
141 | throw new this.constructor.EmptyError('EmptyResponse');
|
142 | }
|
143 | })
|
144 |
|
145 | // Now, load all of the data onto the collection as necessary.
|
146 | .tap(this._handleResponse)
|
147 |
|
148 | // If the "withRelated" is specified, we also need to eager load all of the
|
149 | // data on the collection, as a side-effect, before we ultimately jump into the
|
150 | // next step of the collection. Since the `columns` are only relevant to the current
|
151 | // level, ensure those are omitted from the options.
|
152 | .tap(function (response) {
|
153 | if (options.withRelated) {
|
154 | return this._handleEager(response, (0, _lodash.omit)(options, 'columns'));
|
155 | }
|
156 | }).tap(function (response) {
|
157 |
|
158 | /**
|
159 | * @event Collection#fetched
|
160 | *
|
161 | * @description
|
162 | * Fired after a `fetch` operation. A promise may be returned from the
|
163 | * event handler for async behaviour.
|
164 | *
|
165 | * @param {Collection} collection The collection performing the {@link Collection#fetch}.
|
166 | * @param {Object} response Knex query response.
|
167 | * @param {Object} options Options object passed to {@link Collection#fetch fetch}.
|
168 | * @returns {Promise}
|
169 | */
|
170 | return this.triggerThen('fetched', this, response, options);
|
171 | }).catch(this.constructor.EmptyError, function (err) {
|
172 | if (options.require) {
|
173 | throw err;
|
174 | }
|
175 | this.reset([], { silent: true });
|
176 | }).return(this);
|
177 | }),
|
178 |
|
179 | /**
|
180 | * @method Collection#count
|
181 | * @since 0.8.2
|
182 | * @description
|
183 | *
|
184 | * Get the number of records in the collection's table.
|
185 | *
|
186 | * @example
|
187 | *
|
188 | * // select count(*) from shareholders where company_id = 1 and share > 0.1;
|
189 | * Company.forge({id:1})
|
190 | * .shareholders()
|
191 | * .query('where', 'share', '>', '0.1')
|
192 | * .count()
|
193 | * .then(function(count) {
|
194 | * assert(count === 3);
|
195 | * });
|
196 | *
|
197 | * @param {string} [column='*']
|
198 | * Specify a column to count - rows with null values in this column will be excluded.
|
199 | * @param {Object=} options
|
200 | * Hash of options.
|
201 | * @returns {Promise<Number>}
|
202 | * A promise resolving to the number of matching rows.
|
203 | */
|
204 | count: _promise2.default.method(function (column, options) {
|
205 | if (!(0, _lodash.isString)(column)) {
|
206 | options = column;
|
207 | column = undefined;
|
208 | }
|
209 | if (options) options = (0, _lodash.clone)(options);
|
210 | return this.sync(options).count(column);
|
211 | }),
|
212 |
|
213 | /**
|
214 | * @method Collection#fetchOne
|
215 | * @description
|
216 | *
|
217 | * Fetch and return a single {@link Model model} from the collection,
|
218 | * maintaining any {@link Relation relation} data from the collection, and
|
219 | * any {@link Collection#query query} parameters that have already been passed
|
220 | * to the collection. Especially helpful on relations, where you would only
|
221 | * like to return a single model from the associated collection.
|
222 | *
|
223 | * @example
|
224 | *
|
225 | * // select * from authors where site_id = 1 and id = 2 limit 1;
|
226 | * new Site({id:1})
|
227 | * .authors()
|
228 | * .query({where: {id: 2}})
|
229 | * .fetchOne()
|
230 | * .then(function(model) {
|
231 | * // ...
|
232 | * });
|
233 | *
|
234 | * @param {Object=} options
|
235 | * @param {Boolean} [options.require=false]
|
236 | * If `true`, will reject the returned response with a {@link
|
237 | * Model.NotFoundError NotFoundError} if no result is found.
|
238 | * @param {(string|string[])} [options.columns='*']
|
239 | * Limit the number of columns fetched.
|
240 | * @param {Transaction} options.transacting
|
241 | * Optionally run the query in a transaction.
|
242 | *
|
243 | * @throws {Model.NotFoundError}
|
244 | * @returns {Promise<Model|null>}
|
245 | * A promise resolving to the fetched {@link Model model} or `null` if none exists.
|
246 | */
|
247 | fetchOne: _promise2.default.method(function (options) {
|
248 | var model = new this.model();
|
249 | model._knex = this.query().clone();
|
250 | this.resetQuery();
|
251 | if (this.relatedData) model.relatedData = this.relatedData;
|
252 | return model.fetch(options);
|
253 | }),
|
254 |
|
255 | /**
|
256 | * @method Collection#load
|
257 | * @description
|
258 | * `load` is used to eager load relations onto a Collection, in a similar way
|
259 | * that the `withRelated` property works on {@link Collection#fetch fetch}.
|
260 | * Nested eager loads can be specified by separating the nested relations with
|
261 | * `'.'`.
|
262 | *
|
263 | * @param {string|string[]} relations The relation, or relations, to be loaded.
|
264 | * @param {Object=} options Hash of options.
|
265 | * @param {Transaction=} options.transacting
|
266 | *
|
267 | * @returns {Promise<Collection>} A promise resolving to this {@link
|
268 | * Collection collection}
|
269 | */
|
270 | load: _promise2.default.method(function (relations, options) {
|
271 | if (!(0, _lodash.isArray)(relations)) relations = [relations];
|
272 | options = (0, _lodash.extend)({}, options, { shallow: true, withRelated: relations });
|
273 | return new _eager2.default(this.models, this.toJSON(options), new this.model()).fetch(options).return(this);
|
274 | }),
|
275 |
|
276 | /**
|
277 | * @method Collection#create
|
278 | * @description
|
279 | *
|
280 | * Convenience method to create a new {@link Model model} instance within a
|
281 | * collection. Equivalent to instantiating a model with a hash of {@link
|
282 | * Model#attributes attributes}, {@link Model#save saving} the model to the
|
283 | * database then adding the model to the collection.
|
284 | *
|
285 | * When used on a relation, `create` will automatically set foreign key
|
286 | * attributes before persisting the `Model`.
|
287 | *
|
288 | * ```
|
289 | * const { courses, ...attributes } = req.body;
|
290 | *
|
291 | * Student.forge(attributes).save().tap(student =>
|
292 | * Promise.map(courses, course => student.related('courses').create(course))
|
293 | * ).then(student =>
|
294 | * res.status(200).send(student)
|
295 | * ).catch(error =>
|
296 | * res.status(500).send(error.message)
|
297 | * );
|
298 | * ```
|
299 | *
|
300 | * @param {Object} model A set of attributes to be set on the new model.
|
301 | * @param {Object=} options
|
302 | * @param {Transaction=} options.transacting
|
303 | *
|
304 | * @returns {Promise<Model>} A promise resolving with the new {@link Modle
|
305 | * model}.
|
306 | */
|
307 | create: _promise2.default.method(function (model, options) {
|
308 | options = options != null ? (0, _lodash.clone)(options) : {};
|
309 | var relatedData = this.relatedData;
|
310 |
|
311 | model = this._prepareModel(model, options);
|
312 |
|
313 | // If we've already added things on the query chain,
|
314 | // these are likely intended for the model.
|
315 | if (this._knex) {
|
316 | model._knex = this._knex;
|
317 | this.resetQuery();
|
318 | }
|
319 | return _helpers2.default.saveConstraints(model, relatedData).save(null, options).bind(this).then(function () {
|
320 | if (relatedData && relatedData.type === 'belongsToMany') {
|
321 | return this.attach(model, (0, _lodash.omit)(options, 'query'));
|
322 | }
|
323 | }).then(function () {
|
324 | this.add(model, options);
|
325 | }).return(model);
|
326 | }),
|
327 |
|
328 | /**
|
329 | * @method Collection#resetQuery
|
330 | * @description
|
331 | * Used to reset the internal state of the current query builder instance.
|
332 | * This method is called internally each time a database action is completed
|
333 | * by {@link Sync}.
|
334 | *
|
335 | * @returns {Collection} Self, this method is chainable.
|
336 | */
|
337 | resetQuery: function resetQuery() {
|
338 | this._knex = null;
|
339 | return this;
|
340 | },
|
341 |
|
342 | /**
|
343 | * @method Collection#query
|
344 | * @description
|
345 | *
|
346 | * `query` is used to tap into the underlying Knex query builder instance for
|
347 | * the current collection. If called with no arguments, it will return the
|
348 | * query builder directly. Otherwise, it will call the specified `method` on
|
349 | * the query builder, applying any additional arguments from the
|
350 | * `collection.query` call. If the `method` argument is a function, it will be
|
351 | * called with the Knex query builder as the context and the first argument.
|
352 | *
|
353 | * @example
|
354 | *
|
355 | * let qb = collection.query();
|
356 | * qb.where({id: 1}).select().then(function(resp) {
|
357 | * // ...
|
358 | * });
|
359 | *
|
360 | * collection.query(function(qb) {
|
361 | * qb.where('id', '>', 5).andWhere('first_name', '=', 'Test');
|
362 | * }).fetch()
|
363 | * .then(function(collection) {
|
364 | * // ...
|
365 | * });
|
366 | *
|
367 | * collection
|
368 | * .query('where', 'other_id', '=', '5')
|
369 | * .fetch()
|
370 | * .then(function(collection) {
|
371 | * // ...
|
372 | * });
|
373 | *
|
374 | * @param {function|Object|...string=} arguments The query method.
|
375 | * @returns {Collection|QueryBuilder}
|
376 | * Will return this model or, if called with no arguments, the underlying query builder.
|
377 | *
|
378 | * @see {@link http://knexjs.org/#Builder Knex `QueryBuilder`}
|
379 | */
|
380 | query: function query() {
|
381 | return _helpers2.default.query(this, (0, _lodash.toArray)(arguments));
|
382 | },
|
383 |
|
384 | /**
|
385 | * @method Collection#orderBy
|
386 | * @since 0.9.3
|
387 | * @description
|
388 | *
|
389 | * Specifies the column to sort on and sort order.
|
390 | *
|
391 | * The order parameter is optional, and defaults to 'ASC'. You may
|
392 | * also specify 'DESC' order by prepending a hyphen to the sort column
|
393 | * name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`.
|
394 | *
|
395 | * Unless specified using dot notation (i.e., "table.column"), the default
|
396 | * table will be the table name of the model `orderBy` was called on.
|
397 | *
|
398 | * @example
|
399 | *
|
400 | * Cars.forge().orderBy('color', 'ASC').fetch()
|
401 | * .then(function (rows) { // ...
|
402 | *
|
403 | * @param sort {string}
|
404 | * Column to sort on
|
405 | * @param order {string}
|
406 | * Ascending ('ASC') or descending ('DESC') order
|
407 | */
|
408 | orderBy: function orderBy() {
|
409 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
|
410 | args[_key] = arguments[_key];
|
411 | }
|
412 |
|
413 | return _helpers2.default.orderBy.apply(_helpers2.default, [this].concat(args));
|
414 | },
|
415 |
|
416 |
|
417 | /**
|
418 | * @method Collection#query
|
419 | * @private
|
420 | * @description Creates and returns a new `Bookshelf.Sync` instance.
|
421 | */
|
422 | sync: function sync(options) {
|
423 | return new _sync2.default(this, options);
|
424 | },
|
425 |
|
426 | /* Ensure that QueryBuilder is copied on clone. */
|
427 | clone: function clone() {
|
428 | var cloned = BookshelfCollection.__super__.clone.apply(this, arguments);
|
429 | if (this._knex != null) {
|
430 | cloned._knex = cloned._builder(this._knex.clone());
|
431 | }
|
432 | return cloned;
|
433 | },
|
434 |
|
435 |
|
436 | /**
|
437 | * @method Collection#_handleResponse
|
438 | * @private
|
439 | * @description
|
440 | * Handles the response data for the collection, returning from the
|
441 | * collection's `fetch` call.
|
442 | */
|
443 | _handleResponse: function _handleResponse(response) {
|
444 | var relatedData = this.relatedData;
|
445 |
|
446 |
|
447 | this.set(response, { silent: true, parse: true }).invokeMap('formatTimestamps');
|
448 | this.invokeMap('_reset');
|
449 |
|
450 | if (relatedData && relatedData.isJoined()) {
|
451 | relatedData.parsePivot(this.models);
|
452 | }
|
453 | },
|
454 |
|
455 | /**
|
456 | * @method Collection#_handleEager
|
457 | * @private
|
458 | * @description
|
459 | * Handle the related data loading on the collection.
|
460 | */
|
461 | _handleEager: function _handleEager(response, options) {
|
462 | return new _eager2.default(this.models, response, new this.model()).fetch(options);
|
463 | }
|
464 |
|
465 | }, {
|
466 |
|
467 | extended: function extended(child) {
|
468 | /**
|
469 | * @class Collection.EmptyError
|
470 | * @description
|
471 | * Thrown when no records are found by {@link Collection#fetch fetch},
|
472 | * {@link Model#fetchAll}, or {@link Model.fetchAll} when called with
|
473 | * the `{require: true}` option.
|
474 | */
|
475 | child.EmptyError = (0, _createError2.default)(this.EmptyError);
|
476 | }
|
477 |
|
478 | });
|
479 |
|
480 | BookshelfCollection.EmptyError = _errors2.default.EmptyError;
|
481 |
|
482 | exports.default = BookshelfCollection; |
\ | No newline at end of file |