Comparing version 0.9.4 to 0.9.5
@@ -7,5 +7,3 @@ /** | ||
* | ||
* version 0.9.4 | ||
* | ||
*/ | ||
module.exports = require('./lib/bookshelf').default; |
## Change Log | ||
**0.9.5** — <small>_May 15, 2016_</small> — [Diff](https://github.com/tgriesser/bookshelf/compare/0.9.4...0.9.5) | ||
* Add pagination plugin. #1183 | ||
* Fire {@link Model#event:fetched} on eagerly loaded relations. #1206 | ||
* Correct cloning of {@link Model#belongsToMany} decorated relations. #1222 | ||
* Update Knex to 0.11.x. #1227 | ||
* Update minimum lodash version. #1230 | ||
**0.9.4** — <small>_April 3, 2016_</small> — [Diff](https://github.com/tgriesser/bookshelf/compare/0.9.3...0.9.4) | ||
@@ -4,0 +12,0 @@ |
@@ -92,3 +92,12 @@ 'use strict'; | ||
// be documented as a valid argument for consumer code. | ||
var collectionProps = ['model', 'comparator', 'relatedData']; | ||
// | ||
// RE: 'attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot' | ||
// It's okay to whitelist also given method references to be copied when cloning | ||
// a collection. These methods are present only when `relatedData` is present and | ||
// its `type` is 'belongsToMany'. So it is safe to put them in the list and use them | ||
// without any additional verification. | ||
// These should not be documented as a valid arguments for consumer code. | ||
var collectionProps = ['model', 'comparator', 'relatedData', | ||
// `belongsToMany` pivotal collection properties | ||
'attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot']; | ||
@@ -196,7 +205,7 @@ // Copied over from Backbone. | ||
if (options.parse) models = this.parse(models, options); | ||
var i = undefined, | ||
l = undefined, | ||
id = undefined, | ||
model = undefined, | ||
attrs = undefined; | ||
var i = void 0, | ||
l = void 0, | ||
id = void 0, | ||
model = void 0, | ||
attrs = void 0; | ||
var at = options.at; | ||
@@ -203,0 +212,0 @@ var targetModel = this.model; |
@@ -73,7 +73,7 @@ 'use strict'; | ||
var pendingDeferred = []; | ||
for (var relationName in handled) { | ||
pendingDeferred.push(this.eagerFetch(relationName, handled[relationName], _lodash2.default.extend({}, options, { | ||
for (var _relationName in handled) { | ||
pendingDeferred.push(this.eagerFetch(_relationName, handled[_relationName], _lodash2.default.extend({}, options, { | ||
isEager: true, | ||
withRelated: subRelated[relationName], | ||
_beforeFn: withRelated[relationName] || _lodash.noop | ||
withRelated: subRelated[_relationName], | ||
_beforeFn: withRelated[_relationName] || _lodash.noop | ||
}))); | ||
@@ -80,0 +80,0 @@ } |
@@ -60,3 +60,3 @@ 'use strict'; | ||
var Events = (function (_EventEmitter) { | ||
var Events = function (_EventEmitter) { | ||
(0, _inherits3.default)(Events, _EventEmitter); | ||
@@ -72,2 +72,3 @@ | ||
/** | ||
@@ -220,5 +221,5 @@ * @method Events#on | ||
return Events; | ||
})(EventEmitter); | ||
}(EventEmitter); | ||
exports.default = Events; | ||
exports.default = Events; |
@@ -38,2 +38,3 @@ 'use strict'; | ||
var PIVOT_PREFIX = '_pivot_'; | ||
@@ -76,3 +77,3 @@ var DEFAULT_TIMESTAMP_KEYS = ['created_at', 'updated_at']; | ||
* // Do something before the data is fetched from the database | ||
* }) | ||
* }); | ||
* | ||
@@ -86,4 +87,4 @@ * @see Events#on | ||
* | ||
* customer.off('fetched fetching') | ||
* ship.off() // This will remove all event listeners | ||
* customer.off('fetched fetching'); | ||
* ship.off(); // This will remove all event listeners | ||
* | ||
@@ -97,3 +98,3 @@ * @see Events#off | ||
* | ||
* ship.trigger('fetched') | ||
* ship.trigger('fetched'); | ||
* | ||
@@ -169,3 +170,3 @@ * @see Events#trigger | ||
* @description Get the current value of an attribute from the model. | ||
* @example note.get("title") | ||
* @example note.get("title"); | ||
* | ||
@@ -195,3 +196,3 @@ * @param {string} attribute - The name of the attribute to retrieve. | ||
if (key == null) return this; | ||
var attrs = undefined; | ||
var attrs = void 0; | ||
@@ -289,2 +290,3 @@ // Handle both `"key", value` and `{key: value}` -style arguments. | ||
if (!shallow) { | ||
@@ -372,3 +374,3 @@ | ||
* return _.reduce(attrs, function(memo, val, key) { | ||
* memo[_.str.camelize(key)] = val; | ||
* memo[_.camelCase(key)] = val; | ||
* return memo; | ||
@@ -529,2 +531,3 @@ * }, {}); | ||
if (updatedAtKey) { | ||
@@ -659,3 +662,2 @@ attributes[updatedAtKey] = now; | ||
* var Customer = bookshelf.Model.extend({ | ||
* | ||
* initialize: function() { | ||
@@ -672,15 +674,12 @@ * this.on('saving', this.validateSave); | ||
* }, | ||
* | ||
* }, { | ||
* | ||
* login: Promise.method(function(email, password) { | ||
* if (!email || !password) throw new Error('Email and password are both required'); | ||
* return new this({email: email.toLowerCase().trim()}).fetch({require: true}).tap(function(customer) { | ||
* return bcrypt.compareAsync(customer.get('password'), password) | ||
* .then(function(res) { | ||
* if (!res) throw new Error('Invalid password'); | ||
* }); | ||
* return bcrypt.compareAsync(password, customer.get('password')) | ||
* .then(function(res) { | ||
* if (!res) throw new Error('Invalid password'); | ||
* }); | ||
* }); | ||
* }) | ||
* | ||
* }); | ||
@@ -705,5 +704,5 @@ * | ||
* set: function() { | ||
* ... | ||
* // ... | ||
* bookshelf.Model.prototype.set.apply(this, arguments); | ||
* ... | ||
* // ... | ||
* } | ||
@@ -710,0 +709,0 @@ * }); |
@@ -32,3 +32,3 @@ 'use strict'; | ||
var RelationBase = (function () { | ||
var RelationBase = function () { | ||
function RelationBase(type, Target, options) { | ||
@@ -47,2 +47,3 @@ (0, _classCallCheck3.default)(this, RelationBase); | ||
(0, _createClass3.default)(RelationBase, [{ | ||
@@ -73,3 +74,3 @@ key: 'instance', | ||
return RelationBase; | ||
})(); // Base Relation | ||
}(); // Base Relation | ||
// --------------- | ||
@@ -79,2 +80,3 @@ | ||
(0, _lodash.assign)(RelationBase, { extend: _extend2.default }); |
@@ -49,6 +49,7 @@ 'use strict'; | ||
// All core modules required for the bookshelf instance. | ||
function Bookshelf(knex) { | ||
var bookshelf = { | ||
VERSION: '0.9.4' | ||
VERSION: require('../package.json').version | ||
}; | ||
@@ -105,3 +106,3 @@ | ||
* // ... | ||
* }) | ||
* }); | ||
* | ||
@@ -116,2 +117,3 @@ * @param {(Model[])=} models | ||
/** | ||
@@ -138,2 +140,3 @@ * @method Model.count | ||
/** | ||
@@ -183,3 +186,3 @@ * @method Model.fetchAll | ||
* {name: 'Person2'} | ||
* ]) | ||
* ]); | ||
* | ||
@@ -232,3 +235,2 @@ * Promise.all(accounts.invoke('save')).then(function() { | ||
* ], function(info) { | ||
* | ||
* // Some validation could take place here. | ||
@@ -257,2 +259,3 @@ * return new Book(info).save({'shelf_id': model.id}, {transacting: t}); | ||
/** | ||
@@ -351,2 +354,3 @@ * @callback Bookshelf~transactionCallback | ||
// We've supplemented `Events` with a `triggerThen` method to allow for | ||
@@ -353,0 +357,0 @@ // asynchronous event handling via promises. We also mix this into the |
@@ -329,3 +329,5 @@ 'use strict'; | ||
* let qb = collection.query(); | ||
* qb.where({id: 1}).select().then(function(resp) {... | ||
* qb.where({id: 1}).select().then(function(resp) { | ||
* // ... | ||
* }); | ||
* | ||
@@ -335,3 +337,5 @@ * collection.query(function(qb) { | ||
* }).fetch() | ||
* .then(function(collection) {... | ||
* .then(function(collection) { | ||
* // ... | ||
* }); | ||
* | ||
@@ -342,3 +346,3 @@ * collection | ||
* .then(function(collection) { | ||
* ... | ||
* // ... | ||
* }); | ||
@@ -357,2 +361,35 @@ * | ||
/** | ||
* @method Collection#orderBy | ||
* @since 0.9.3 | ||
* @description | ||
* | ||
* Specifies the column to sort on and sort order. | ||
* | ||
* The order parameter is optional, and defaults to 'ASC'. You may | ||
* also specify 'DESC' order by prepending a hyphen to the sort column | ||
* name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`. | ||
* | ||
* Unless specified using dot notation (i.e., "table.column"), the default | ||
* table will be the table name of the model `orderBy` was called on. | ||
* | ||
* @example | ||
* | ||
* Cars.forge().orderBy('color', 'ASC').fetch() | ||
* .then(function (rows) { // ... | ||
* | ||
* @param sort {string} | ||
* Column to sort on | ||
* @param order {string} | ||
* Ascending ('ASC') or descending ('DESC') order | ||
*/ | ||
orderBy: function orderBy() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return _helpers2.default.orderBy.apply(_helpers2.default, [this].concat(args)); | ||
}, | ||
/** | ||
* @method Collection#query | ||
@@ -375,2 +412,3 @@ * @private | ||
/** | ||
@@ -377,0 +415,0 @@ * @method Collection#_handleResponse |
@@ -52,2 +52,3 @@ 'use strict'; | ||
var getAttributeUnique = function getAttributeUnique(models, attribute) { | ||
@@ -64,3 +65,3 @@ return (0, _lodash.uniq)((0, _lodash.map)(models, function (m) { | ||
var EagerRelation = (function (_EagerBase) { | ||
var EagerRelation = function (_EagerBase) { | ||
(0, _inherits3.default)(EagerRelation, _EagerBase); | ||
@@ -76,2 +77,3 @@ | ||
// Handles an eager loaded fetch, passing the name of the item we're fetching | ||
@@ -115,2 +117,3 @@ // for, and any options needed for the current fetch. | ||
var parentsByType = (0, _lodash.groupBy)(this.parent, function (model) { | ||
@@ -140,18 +143,26 @@ return model.get(typeColumn); | ||
value: function _eagerLoadHelper(response, relationName, handled, options) { | ||
var _this4 = this; | ||
var relatedModels = this.pushModels(relationName, handled, response); | ||
var relatedData = handled.relatedData; | ||
// If there is a response, fetch additional nested eager relations, if any. | ||
if (response.length > 0 && options.withRelated) { | ||
var relatedModel = relatedData.createModel(); | ||
return _promise2.default.try(function () { | ||
// If there is a response, fetch additional nested eager relations, if any. | ||
if (response.length > 0 && options.withRelated) { | ||
var relatedModel = relatedData.createModel(); | ||
// If this is a `morphTo` relation, we need to do additional processing | ||
// to ensure we don't try to load any relations that don't look to exist. | ||
if (relatedData.type === 'morphTo') { | ||
var withRelated = this._filterRelated(relatedModel, options); | ||
if (withRelated.length === 0) return; | ||
options = _lodash2.default.extend({}, options, { withRelated: withRelated }); | ||
// If this is a `morphTo` relation, we need to do additional processing | ||
// to ensure we don't try to load any relations that don't look to exist. | ||
if (relatedData.type === 'morphTo') { | ||
var withRelated = _this4._filterRelated(relatedModel, options); | ||
if (withRelated.length === 0) return; | ||
options = _lodash2.default.extend({}, options, { withRelated: withRelated }); | ||
} | ||
return new EagerRelation(relatedModels, response, relatedModel).fetch(options).return(response); | ||
} | ||
return new EagerRelation(relatedModels, response, relatedModel).fetch(options).return(response); | ||
} | ||
}).tap(function () { | ||
return _promise2.default.map(relatedModels, function (model) { | ||
return model.triggerThen('fetched', model, model.attributes, options); | ||
}); | ||
}); | ||
} | ||
@@ -178,4 +189,4 @@ | ||
return EagerRelation; | ||
})(_eager2.default); | ||
}(_eager2.default); | ||
exports.default = EagerRelation; |
@@ -84,6 +84,39 @@ 'use strict'; | ||
helpers.warn(a + ' has been deprecated, please use ' + b + ' instead'); | ||
}, | ||
orderBy: function orderBy(obj, sort, order) { | ||
var tableName = void 0; | ||
var idAttribute = void 0; | ||
if (obj.model) { | ||
tableName = obj.model.prototype.tableName; | ||
idAttribute = obj.model.prototype.idAttribute ? obj.model.prototype.idAttribute : 'id'; | ||
} else { | ||
tableName = obj.constructor.prototype.tableName; | ||
idAttribute = obj.constructor.prototype.idAttribute ? obj.constructor.prototype.idAttribute : 'id'; | ||
} | ||
var _sort = void 0; | ||
if (sort && sort.indexOf('-') === 0) { | ||
_sort = sort.slice(1); | ||
} else if (sort) { | ||
_sort = sort; | ||
} else { | ||
_sort = idAttribute; | ||
} | ||
var _order = order || (sort && sort.indexOf('-') === 0 ? 'DESC' : 'ASC'); | ||
if (_sort.indexOf('.') === -1) { | ||
_sort = tableName + '.' + _sort; | ||
} | ||
return obj.query(function (qb) { | ||
qb.orderBy(_sort, _order); | ||
}); | ||
} | ||
}; | ||
module.exports = helpers; |
@@ -73,3 +73,3 @@ 'use strict'; | ||
* | ||
* let Books = bookshelf.Model.extend({ | ||
* let Book = bookshelf.Model.extend({ | ||
* tableName: 'documents', | ||
@@ -116,3 +116,3 @@ * constructor: function() { | ||
* new Patient({id: 1}).related('record').fetch().then(function(model) { | ||
* ... | ||
* // ... | ||
* }); | ||
@@ -122,3 +122,3 @@ * | ||
* new Patient({id: 1}).record().fetch().then(function(model) { | ||
* ... | ||
* // ... | ||
* }); | ||
@@ -145,2 +145,3 @@ * | ||
/** | ||
@@ -181,2 +182,3 @@ * The `hasMany` relation specifies that this model has one or more rows in | ||
/** | ||
@@ -225,2 +227,3 @@ * The `belongsTo` relationship is used when a model is a member of | ||
/** | ||
@@ -324,2 +327,3 @@ * Defines a many-to-many relation, where the current model is joined to one | ||
/** | ||
@@ -377,2 +381,3 @@ * The {@link Model#morphOne morphOne} is used to signify a {@link oneToOne | ||
/** | ||
@@ -430,2 +435,3 @@ * {@link Model#morphMany morphMany} is essentially the same as a {@link | ||
/** | ||
@@ -467,10 +473,10 @@ * The {@link Model#morphTo morphTo} relation is used to specify the inverse | ||
if (!_lodash2.default.isString(morphName)) throw new Error('The `morphTo` name must be specified.'); | ||
var columnNames = undefined, | ||
candidates = undefined; | ||
var columnNames = void 0, | ||
candidates = void 0; | ||
if (_lodash2.default.isArray(arguments[1])) { | ||
columnNames = arguments[1]; | ||
candidates = _lodash2.default.rest(arguments, 2); | ||
candidates = _lodash2.default.drop(arguments, 2); | ||
} else { | ||
columnNames = null; | ||
candidates = _lodash2.default.rest(arguments); | ||
candidates = _lodash2.default.drop(arguments); | ||
} | ||
@@ -480,2 +486,3 @@ return this._relation('morphTo', null, { morphName: morphName, columnNames: columnNames, candidates: candidates }).init(this); | ||
/** | ||
@@ -492,3 +499,2 @@ * Helps to create dynamic relations between {@link Model models} and {@link | ||
* let Book = bookshelf.Model.extend({ | ||
* | ||
* tableName: 'books', | ||
@@ -505,7 +511,5 @@ * | ||
* } | ||
* | ||
* }); | ||
* | ||
* let Chapter = bookshelf.Model.extend({ | ||
* | ||
* tableName: 'chapters', | ||
@@ -516,7 +520,5 @@ * | ||
* } | ||
* | ||
* }); | ||
* | ||
* let Paragraph = bookshelf.Model.extend({ | ||
* | ||
* tableName: 'paragraphs', | ||
@@ -532,3 +534,2 @@ * | ||
* } | ||
* | ||
* }); | ||
@@ -561,2 +562,3 @@ * | ||
/** | ||
@@ -585,2 +587,3 @@ * @method Model#refresh | ||
/** | ||
@@ -652,3 +655,3 @@ * Fetches a {@link Model model} from the database, using any {@link | ||
* @param {string|string[]} [options.columns='*'] | ||
* Specify columns to be retireved. | ||
* Specify columns to be retrieved. | ||
* @param {Transaction} [options.transacting] | ||
@@ -676,2 +679,3 @@ * Optionally run the query in a transaction. | ||
_doFetch: _promise2.default.method(function (attributes, options) { | ||
@@ -735,2 +739,3 @@ options = options ? _lodash2.default.clone(options) : {}; | ||
/** | ||
@@ -760,2 +765,3 @@ * @method Model#count | ||
/** | ||
@@ -823,2 +829,3 @@ * Fetches a collection of {@link Model models} from the database, using any | ||
/** | ||
@@ -935,3 +942,3 @@ * @method Model#load | ||
save: _promise2.default.method(function (key, val, options) { | ||
var attrs = undefined; | ||
var attrs = void 0; | ||
@@ -1135,3 +1142,2 @@ // Handle both `"key", value` and `{key: value}` -style arguments. | ||
* @param {Model} model The model firing the event. | ||
* @param {Object} attrs Model firing the event. | ||
* @param {Object} options Options object passed to {@link Model#save save}. | ||
@@ -1178,2 +1184,3 @@ * @returns {Promise} | ||
/** | ||
@@ -1207,6 +1214,10 @@ * The `query` method is used to tap into the underlying Knex query builder | ||
* }).fetch() | ||
* .then(function(model) { // ... | ||
* .then(function(model) { | ||
* // ... | ||
* }); | ||
* | ||
* let qb = model.query(); | ||
* qb.where({id: 1}).select().then(function(resp) { // ... | ||
* qb.where({id: 1}).select().then(function(resp) { | ||
* // ... | ||
* }); | ||
* | ||
@@ -1224,2 +1235,3 @@ * @method Model#query | ||
/** | ||
@@ -1259,2 +1271,36 @@ * The where method is used as convenience for the most common {@link | ||
/** | ||
* @method Model#orderBy | ||
* @since 0.9.3 | ||
* @description | ||
* | ||
* Specifies the column to sort on and sort order. | ||
* | ||
* The order parameter is optional, and defaults to 'ASC'. You may | ||
* also specify 'DESC' order by prepending a hyphen to the sort column | ||
* name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`. | ||
* | ||
* Unless specified using dot notation (i.e., "table.column"), the default | ||
* table will be the table name of the model `orderBy` was called on. | ||
* | ||
* @example | ||
* | ||
* Car.forge().orderBy('color', 'ASC').fetchAll() | ||
* .then(function (rows) { // ... | ||
* | ||
* @param sort {string} | ||
* Column to sort on | ||
* @param order {string} | ||
* Ascending ('ASC') or descending ('DESC') order | ||
*/ | ||
orderBy: function orderBy() { | ||
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { | ||
args[_key2] = arguments[_key2]; | ||
} | ||
return _helpers2.default.orderBy.apply(_helpers2.default, [this].concat(args)); | ||
}, | ||
/* Ensure that QueryBuilder is copied on clone. */ | ||
@@ -1269,2 +1315,3 @@ clone: function clone() { | ||
/** | ||
@@ -1281,2 +1328,3 @@ * Creates and returns a new Bookshelf.Sync instance. | ||
/** | ||
@@ -1298,2 +1346,3 @@ * Helper for setting up the `morphOne` or `morphMany` relations. | ||
/** | ||
@@ -1319,2 +1368,3 @@ * @name Model#_handleResponse | ||
/** | ||
@@ -1321,0 +1371,0 @@ * @name Model#_handleEager |
@@ -19,2 +19,6 @@ 'use strict'; | ||
var DEFAULT_LIMIT = 10; | ||
var DEFAULT_OFFSET = 0; | ||
var DEFAULT_PAGE = 1; | ||
/** | ||
@@ -46,199 +50,188 @@ * Exports a plugin to pass into the bookshelf instance, i.e.: | ||
bookshelf.Model = bookshelf.Model.extend({ | ||
/** | ||
* @method Model#fetchPage | ||
* @belongsTo Model | ||
* | ||
* Similar to {@link Model#fetchAll}, but fetches a single page of results | ||
* as specified by the limit (page size) and offset or page number. | ||
* | ||
* Any options that may be passed to {@link Model#fetchAll} may also be passed | ||
* in the options to this method. | ||
* | ||
* To perform pagination, you may include *either* an `offset` and `limit`, **or** | ||
* a `page` and `pageSize`. | ||
* | ||
* By default, with no parameters or missing parameters, `fetchPage` will use an | ||
* options object of `{page: 1, pageSize: 10}` | ||
* | ||
* | ||
* Below is an example showing the user of a JOIN query with sort/ordering, | ||
* pagination, and related models. | ||
* | ||
* @example | ||
* | ||
* Car | ||
* .query(function (qb) { | ||
* qb.innerJoin('manufacturers', 'cars.manufacturer_id', 'manufacturers.id'); | ||
* qb.groupBy('cars.id'); | ||
* qb.where('manufacturers.country', '=', 'Sweden'); | ||
* }) | ||
* .orderBy('-productionYear') // Same as .orderBy('cars.productionYear', 'DESC') | ||
* .fetchPage({ | ||
* pageSize: 15, // Defaults to 10 if not specified | ||
* page: 3, // Defaults to 1 if not specified | ||
* | ||
* // OR | ||
* // limit: 15, | ||
* // offset: 30, | ||
* | ||
* withRelated: ['engine'] // Passed to Model#fetchAll | ||
* }) | ||
* .then(function (results) { | ||
* console.log(results); // Paginated results object with metadata example below | ||
* }) | ||
* | ||
* // Pagination results: | ||
* | ||
* { | ||
* models: [<Car>], // Regular bookshelf Collection | ||
* // other standard Collection attributes | ||
* ... | ||
* pagination: { | ||
* rowCount: 53, // Total number of rows found for the query before pagination | ||
* pageCount: 4, // Total number of pages of results | ||
* page: 3, // The requested page number | ||
* pageSze: 15, // The requested number of rows per page | ||
* | ||
* // OR, if limit/offset pagination is used instead of page/pageSize: | ||
* // offset: 30, // The requested offset | ||
* // limit: 15 // The requested limit | ||
* } | ||
* } | ||
* | ||
* @param options {object} | ||
* The pagination options, plus any additional options that will be passed to | ||
* {@link Model#fetchAll} | ||
* @returns {Promise<Model|null>} | ||
*/ | ||
function fetchPage() { | ||
var _this = this; | ||
/** | ||
* @method Model#orderBy | ||
* @since 0.9.3 | ||
* @description | ||
* | ||
* Specifies the column to sort on and sort order. | ||
* | ||
* The order parameter is optional, and defaults to 'ASC'. You may | ||
* also specify 'DESC' order by prepending a hyphen to the sort column | ||
* name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`. | ||
* | ||
* Unless specified using dot notation (i.e., "table.column"), the default | ||
* table will be the table name of the model `orderBy` was called on. | ||
* | ||
* @example | ||
* | ||
* Cars.forge().orderBy('color', 'ASC').fetchAll() | ||
* .then(function (rows) { // ... | ||
* | ||
* @param sort {string} | ||
* Column to sort on | ||
* @param order {string} | ||
* Ascending ('ASC') or descending ('DESC') order | ||
*/ | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var page = options.page; | ||
var pageSize = options.pageSize; | ||
var limit = options.limit; | ||
var offset = options.offset; | ||
var fetchOptions = (0, _objectWithoutProperties3.default)(options, ['page', 'pageSize', 'limit', 'offset']); | ||
orderBy: function orderBy(sort, order) { | ||
var tableName = this.constructor.prototype.tableName; | ||
var idAttribute = this.constructor.prototype.idAttribute ? this.constructor.prototype.idAttribute : 'id'; | ||
var _sort = undefined; | ||
var usingPageSize = false; // usingPageSize = false means offset/limit, true means page/pageSize | ||
var _page = void 0; | ||
var _pageSize = void 0; | ||
var _limit = void 0; | ||
var _offset = void 0; | ||
if (sort && sort.startsWith('-')) { | ||
_sort = sort.slice(1); | ||
} else if (sort) { | ||
_sort = sort; | ||
} else { | ||
_sort = idAttribute; | ||
} | ||
function ensureIntWithDefault(val, def) { | ||
if (!val) { | ||
return def; | ||
} | ||
var _order = order || (sort && sort.startsWith('-') ? 'DESC' : 'ASC'); | ||
var _val = parseInt(val); | ||
if (Number.isNaN(_val)) { | ||
return def; | ||
} | ||
if (_sort.indexOf('.') === -1) { | ||
_sort = tableName + '.' + _sort; | ||
} | ||
return _val; | ||
} | ||
return this.query(function (qb) { | ||
qb.orderBy(_sort, _order); | ||
}); | ||
}, | ||
if (!limit && !offset) { | ||
usingPageSize = true; | ||
/** | ||
* @method Model#fetchPage | ||
* @since 0.9.3 | ||
* @description | ||
* | ||
* Similar to {@link Model#fetchAll}, but fetches a single page of results | ||
* as specified by the limit (page size) and offset or page number. | ||
* | ||
* Any options that may be passed to {@link Model#fetchAll} may also be passed | ||
* in the options to this method. | ||
* | ||
* To perform pagination, include a `limit` and _either_ `offset` or `page`. | ||
* | ||
* The defaults are page 1 (offset 0) and limit 10 when no parameters or invalid | ||
* parameters are passed. | ||
* | ||
* Below is an example showing the user of a JOIN query with sort/ordering, | ||
* pagination, and related models. | ||
* | ||
* @example | ||
* | ||
* Car | ||
* .query(function (qb) { | ||
* qb.innerJoin('manufacturers', 'cars.manufacturer_id', 'manufacturers.id'); | ||
* qb.groupBy('cars.id'); | ||
* qb.where('manufacturers.country', '=', 'Sweden'); | ||
* }) | ||
* .orderBy('-productionYear') // Same as .orderBy('cars.productionYear', 'DESC') | ||
* .fetchPage({ | ||
* limit: 15, // Defaults to 10 if not specified | ||
* page: 3, // Defaults to 1 if not specified; same as {offset: 30} with limit of 15. | ||
* withRelated: ['engine'] // Passed to Model#fetchAll | ||
* }) | ||
* .then(function (results) { | ||
* console.log(results); // Paginated results object with metadata example below | ||
* }) | ||
* | ||
* // Pagination results: | ||
* | ||
* { | ||
* models: [<Car>], // Regular bookshelf Collection | ||
* // other standard Collection attributes | ||
* ... | ||
* pagination: { | ||
* rowCount: 15, // Would be less than 15 on the last page of results | ||
* total: 53, // Total number of rows found for the query before pagination | ||
* limit: 15, // The requested number of rows per page, same as rowCount except final page | ||
* page: 3, // The requested page number | ||
* offset: 30 // The requested offset, calculated from the page/limit if not provided | ||
* } | ||
* } | ||
* | ||
* @param options {object} | ||
* The pagination options, plus any additional options that will be passed to | ||
* {@link Model#fetchAll} | ||
* @returns {Promise<Model|null>} | ||
*/ | ||
fetchPage: function fetchPage() { | ||
var _this = this; | ||
_pageSize = ensureIntWithDefault(pageSize, DEFAULT_LIMIT); | ||
_page = ensureIntWithDefault(page, DEFAULT_PAGE); | ||
var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; | ||
var limit = options.limit; | ||
var page = options.page; | ||
var offset = options.offset; | ||
var fetchOptions = (0, _objectWithoutProperties3.default)(options, ['limit', 'page', 'offset']); | ||
_limit = _pageSize; | ||
_offset = _limit * (_page - 1); | ||
} else { | ||
_pageSize = _limit; // not used, just for eslint `const` error | ||
_limit = ensureIntWithDefault(limit, DEFAULT_LIMIT); | ||
_offset = ensureIntWithDefault(offset, DEFAULT_OFFSET); | ||
} | ||
var _limit = parseInt(limit); | ||
var _page = parseInt(page); | ||
var _offset = parseInt(offset); | ||
var tableName = this.constructor.prototype.tableName; | ||
var idAttribute = this.constructor.prototype.idAttribute ? this.constructor.prototype.idAttribute : 'id'; | ||
if (Number.isNaN(_limit) || !Number.isInteger(_limit) || _limit < 0) { | ||
_limit = 10; | ||
} | ||
var paginate = function paginate() { | ||
// const pageQuery = clone(this.query()); | ||
var pager = _this.constructor.forge(); | ||
if (page && Number.isInteger(_page) && _page > 0) { | ||
// Request by page number, calculate offset | ||
_offset = _limit * (_page - 1); | ||
} else if (offset && Number.isInteger(_offset) && _offset >= 0) { | ||
// Request by offset, calculate page | ||
_page = Math.floor(_offset / _limit) + 1; | ||
} else { | ||
// Defaults for erroneous or not defined page/offset | ||
_page = 1; | ||
_offset = 0; | ||
} | ||
return pager.query(function (qb) { | ||
(0, _lodash.assign)(qb, _this.query().clone()); | ||
qb.limit.apply(qb, [_limit]); | ||
qb.offset.apply(qb, [_offset]); | ||
return null; | ||
}).fetchAll(fetchOptions); | ||
}; | ||
var tableName = this.constructor.prototype.tableName; | ||
var idAttribute = this.constructor.prototype.idAttribute ? this.constructor.prototype.idAttribute : 'id'; | ||
var count = function count() { | ||
var notNeededQueries = ['orderByBasic', 'orderByRaw', 'groupByBasic', 'groupByRaw']; | ||
var counter = _this.constructor.forge(); | ||
var paginate = function paginate() { | ||
// const pageQuery = clone(this.query()); | ||
var pager = _this.constructor.forge(); | ||
return counter.query(function (qb) { | ||
(0, _lodash.assign)(qb, _this.query().clone()); | ||
return pager.query(function (qb) { | ||
Object.assign(qb, _this.query().clone()); | ||
qb.limit.apply(qb, [_limit]); | ||
qb.offset.apply(qb, [_offset]); | ||
return null; | ||
}).fetchAll(fetchOptions); | ||
}; | ||
// Remove grouping and ordering. Ordering is unnecessary | ||
// for a count, and grouping returns the entire result set | ||
// What we want instead is to use `DISTINCT` | ||
(0, _lodash.remove)(qb._statements, function (statement) { | ||
return notNeededQueries.indexOf(statement.type) > -1 || statement.grouping === 'columns'; | ||
}); | ||
qb.countDistinct.apply(qb, [tableName + '.' + idAttribute]); | ||
}).fetchAll().then(function (result) { | ||
var count = function count() { | ||
var notNeededQueries = ['orderByBasic', 'orderByRaw', 'groupByBasic', 'groupByRaw']; | ||
var counter = _this.constructor.forge(); | ||
var metadata = usingPageSize ? { page: _page, pageSize: _limit } : { offset: _offset, limit: _limit }; | ||
return counter.query(function (qb) { | ||
Object.assign(qb, _this.query().clone()); | ||
if (result && result.length == 1) { | ||
// We shouldn't have to do this, instead it should be | ||
// result.models[0].get('count') | ||
// but SQLite uses a really strange key name. | ||
var _count = result.models[0]; | ||
var keys = Object.keys(_count.attributes); | ||
if (keys.length === 1) { | ||
var key = Object.keys(_count.attributes)[0]; | ||
metadata.rowCount = parseInt(_count.attributes[key]); | ||
} | ||
} | ||
// Remove grouping and ordering. Ordering is unnecessary | ||
// for a count, and grouping returns the entire result set | ||
// What we want instead is to use `DISTINCT` | ||
qb.countDistinct.apply(qb, [tableName + '.' + idAttribute]); | ||
(0, _lodash.remove)(qb._statements, function (statement) { | ||
return notNeededQueries.indexOf(statement.type) > -1; | ||
}); | ||
}).fetchAll().then(function (result) { | ||
var metadata = { page: _page, limit: _limit, offset: _offset }; | ||
return metadata; | ||
}); | ||
}; | ||
if (result && result.length == 1) { | ||
// We shouldn't have to do this, instead it should be | ||
// result.models[0].get('count') | ||
// but SQLite uses a really strange key name. | ||
var _count = result.models[0]; | ||
var keys = Object.keys(_count.attributes); | ||
if (keys.length === 1) { | ||
var key = Object.keys(_count.attributes)[0]; | ||
metadata.total = _count.attributes[key]; | ||
} | ||
} | ||
return _bluebird2.default.join(paginate(), count()).then(function (_ref) { | ||
var _ref2 = (0, _slicedToArray3.default)(_ref, 2); | ||
return metadata; | ||
}); | ||
}; | ||
var rows = _ref2[0]; | ||
var metadata = _ref2[1]; | ||
return _bluebird2.default.join(paginate(), count()).then(function (_ref) { | ||
var _ref2 = (0, _slicedToArray3.default)(_ref, 2); | ||
var pageCount = Math.ceil(metadata.rowCount / _limit); | ||
var pageData = (0, _lodash.assign)(metadata, { pageCount: pageCount }); | ||
return (0, _lodash.assign)(rows, { pagination: pageData }); | ||
}); | ||
} | ||
var rows = _ref2[0]; | ||
var metadata = _ref2[1]; | ||
bookshelf.Model.prototype.fetchPage = fetchPage; | ||
var pageData = Object.assign(metadata, { rowCount: rows.length }); | ||
return Object.assign(rows, { pagination: pageData }); | ||
}); | ||
} | ||
}); | ||
bookshelf.Model.fetchPage = function () { | ||
var _forge; | ||
return (_forge = this.forge()).fetchPage.apply(_forge, arguments); | ||
}; | ||
bookshelf.Collection.prototype.fetchPage = function () { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return fetchPage.apply.apply(fetchPage, [this.model.forge()].concat(args)); | ||
}; | ||
}; |
@@ -57,5 +57,5 @@ 'use strict'; | ||
if (typeof input === 'string') { | ||
return bookshelf.collection(input) || bookshelf.model(input) || (function () { | ||
return bookshelf.collection(input) || bookshelf.model(input) || function () { | ||
throw new Error('The model ' + input + ' could not be resolved from the registry plugin.'); | ||
})(); | ||
}(); | ||
} | ||
@@ -75,3 +75,3 @@ return input; | ||
// The first argument is always a model, so resolve it and call the original method. | ||
return original.apply(this, [resolveModel(Target)].concat(_lodash2.default.rest(arguments))); | ||
return original.apply(this, [resolveModel(Target)].concat(_lodash2.default.drop(arguments))); | ||
}; | ||
@@ -84,3 +84,3 @@ }); | ||
Model.prototype.morphTo = function (relationName) { | ||
return morphTo.apply(this, [relationName].concat(_lodash2.default.map(_lodash2.default.rest(arguments), function (model) { | ||
return morphTo.apply(this, [relationName].concat(_lodash2.default.map(_lodash2.default.drop(arguments), function (model) { | ||
return resolveModel(model); | ||
@@ -93,4 +93,4 @@ }, this))); | ||
Collection.prototype.through = function (Target) { | ||
return collectionThrough.apply(this, [resolveModel(Target)].concat(_lodash2.default.rest(arguments))); | ||
return collectionThrough.apply(this, [resolveModel(Target)].concat(_lodash2.default.drop(arguments))); | ||
}; | ||
}; |
@@ -34,4 +34,4 @@ 'use strict'; | ||
for (var virtualName in virtuals) { | ||
var getter = undefined, | ||
setter = undefined; | ||
var getter = void 0, | ||
setter = void 0; | ||
if (virtuals[virtualName].get) { | ||
@@ -112,3 +112,3 @@ getter = virtuals[virtualName].get; | ||
save: function save(key, value, options) { | ||
var attrs = undefined; | ||
var attrs = void 0; | ||
@@ -141,3 +141,3 @@ // Handle both `"key", value` and `{key: value}` -style arguments. | ||
// `this.patchAttributes` instead of `this.attributes`. | ||
_lodash2.default.each(attrs, (function (value, key) { | ||
_lodash2.default.each(attrs, function (value, key) { | ||
@@ -149,3 +149,3 @@ if (setVirtual.call(this, value, key)) { | ||
} | ||
}).bind(this)); | ||
}.bind(this)); | ||
@@ -152,0 +152,0 @@ // Now add any changes that occurred during the update. |
@@ -224,3 +224,3 @@ 'use strict'; | ||
whereClauses: function whereClauses(knex, response) { | ||
var key = undefined; | ||
var key = void 0; | ||
@@ -235,5 +235,5 @@ if (this.isJoined()) { | ||
} else { | ||
var column = this.isInverse() ? this.targetIdAttribute : this.key('foreignKey'); | ||
var _column = this.isInverse() ? this.targetIdAttribute : this.key('foreignKey'); | ||
key = this.targetTableName + '.' + column; | ||
key = this.targetTableName + '.' + _column; | ||
} | ||
@@ -316,3 +316,3 @@ | ||
(0, _collection.each)(parentModels, function (model) { | ||
var groupedKey = undefined; | ||
var groupedKey = void 0; | ||
if (!_this.isInverse()) { | ||
@@ -339,2 +339,3 @@ groupedKey = model.id; | ||
parsePivot: function parsePivot(models) { | ||
@@ -399,3 +400,3 @@ var _this2 = this; | ||
var singularMemo = (function () { | ||
var singularMemo = function () { | ||
var cache = Object.create(null); | ||
@@ -408,3 +409,3 @@ return function (arg) { | ||
}; | ||
})(); | ||
}(); | ||
@@ -459,2 +460,3 @@ // Specific to many-to-many relationships, these methods are mixed | ||
/** | ||
@@ -492,2 +494,3 @@ * Detach one or more related objects from their pivot tables. If a model or | ||
/** | ||
@@ -494,0 +497,0 @@ * The `updatePivot` method is used exclusively on {@link Model#belongsToMany |
@@ -94,2 +94,15 @@ 'use strict'; | ||
}).then(function () { | ||
options.query = knex; | ||
/** | ||
* Counting event. | ||
* | ||
* Fired before a `count` query. A promise may be | ||
* returned from the event handler for async behaviour. | ||
* | ||
* @event Model#counting | ||
* @param {Model} model The model firing the event. | ||
* @param {Object} options Options object passed to {@link Model#count count}. | ||
* @returns {Promise} | ||
*/ | ||
return this.syncing.triggerThen('counting', this.syncing, options); | ||
@@ -131,3 +144,3 @@ }).then(function () { | ||
if (relatedData.isThrough()) { | ||
var _ret = (function () { | ||
var _ret = function () { | ||
fks[relatedData.key('foreignKey')] = relatedData.parentFk; | ||
@@ -141,3 +154,3 @@ var through = new relatedData.throughTarget(fks); | ||
}; | ||
})(); | ||
}(); | ||
@@ -144,0 +157,0 @@ if ((typeof _ret === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret)) === "object") return _ret.v; |
{ | ||
"name": "bookshelf", | ||
"version": "0.9.4", | ||
"version": "0.9.5", | ||
"description": "A lightweight ORM for PostgreSQL, MySQL, and SQLite3", | ||
@@ -35,3 +35,3 @@ "main": "bookshelf.js", | ||
"inherits": "~2.0.1", | ||
"lodash": "^3.7.0" | ||
"lodash": "^3.8.0" | ||
}, | ||
@@ -50,3 +50,3 @@ "devDependencies": { | ||
"jsdoc": "^3.4.0", | ||
"knex": "^0.10.0", | ||
"knex": "^0.11.0", | ||
"minimist": "^1.1.0", | ||
@@ -63,3 +63,3 @@ "mocha": "^2.0.1", | ||
"peerDependencies": { | ||
"knex": ">=0.6.10 <0.11.0" | ||
"knex": ">=0.6.10 <0.12.0" | ||
}, | ||
@@ -66,0 +66,0 @@ "author": { |
@@ -95,9 +95,5 @@ # [bookshelf.js](http://bookshelfjs.org) [![Build Status](https://travis-ci.org/tgriesser/bookshelf.svg?branch=master)](https://travis-ci.org/tgriesser/bookshelf) | ||
User.where('id', 1).fetch({withRelated: ['posts.tags']}).then(function(user) { | ||
console.log(user.related('posts').toJSON()); | ||
}).catch(function(err) { | ||
console.error(err); | ||
}); | ||
@@ -111,2 +107,3 @@ ``` | ||
- [Visibility](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Visibility): Specify a whitelist/blacklist of model attributes when serialized toJSON. | ||
- [Pagination](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Pagination): Adds `fetchPage` methods to use for pagination in place of `fetch` and `fetchAll` | ||
@@ -113,0 +110,0 @@ ## Support |
@@ -69,3 +69,14 @@ // Base Collection | ||
// be documented as a valid argument for consumer code. | ||
const collectionProps = ['model', 'comparator', 'relatedData']; | ||
// | ||
// RE: 'attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot' | ||
// It's okay to whitelist also given method references to be copied when cloning | ||
// a collection. These methods are present only when `relatedData` is present and | ||
// its `type` is 'belongsToMany'. So it is safe to put them in the list and use them | ||
// without any additional verification. | ||
// These should not be documented as a valid arguments for consumer code. | ||
const collectionProps = [ | ||
'model', 'comparator', 'relatedData', | ||
// `belongsToMany` pivotal collection properties | ||
'attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot' | ||
]; | ||
@@ -72,0 +83,0 @@ // Copied over from Backbone. |
@@ -46,3 +46,3 @@ // Base Model | ||
* // Do something before the data is fetched from the database | ||
* }) | ||
* }); | ||
* | ||
@@ -56,4 +56,4 @@ * @see Events#on | ||
* | ||
* customer.off('fetched fetching') | ||
* ship.off() // This will remove all event listeners | ||
* customer.off('fetched fetching'); | ||
* ship.off(); // This will remove all event listeners | ||
* | ||
@@ -67,3 +67,3 @@ * @see Events#off | ||
* | ||
* ship.trigger('fetched') | ||
* ship.trigger('fetched'); | ||
* | ||
@@ -139,3 +139,3 @@ * @see Events#trigger | ||
* @description Get the current value of an attribute from the model. | ||
* @example note.get("title") | ||
* @example note.get("title"); | ||
* | ||
@@ -337,3 +337,3 @@ * @param {string} attribute - The name of the attribute to retrieve. | ||
* return _.reduce(attrs, function(memo, val, key) { | ||
* memo[_.str.camelize(key)] = val; | ||
* memo[_.camelCase(key)] = val; | ||
* return memo; | ||
@@ -611,3 +611,2 @@ * }, {}); | ||
* var Customer = bookshelf.Model.extend({ | ||
* | ||
* initialize: function() { | ||
@@ -624,15 +623,12 @@ * this.on('saving', this.validateSave); | ||
* }, | ||
* | ||
* }, { | ||
* | ||
* login: Promise.method(function(email, password) { | ||
* if (!email || !password) throw new Error('Email and password are both required'); | ||
* return new this({email: email.toLowerCase().trim()}).fetch({require: true}).tap(function(customer) { | ||
* return bcrypt.compareAsync(customer.get('password'), password) | ||
* .then(function(res) { | ||
* if (!res) throw new Error('Invalid password'); | ||
* }); | ||
* return bcrypt.compareAsync(password, customer.get('password')) | ||
* .then(function(res) { | ||
* if (!res) throw new Error('Invalid password'); | ||
* }); | ||
* }); | ||
* }) | ||
* | ||
* }); | ||
@@ -657,5 +653,5 @@ * | ||
* set: function() { | ||
* ... | ||
* // ... | ||
* bookshelf.Model.prototype.set.apply(this, arguments); | ||
* ... | ||
* // ... | ||
* } | ||
@@ -662,0 +658,0 @@ * }); |
@@ -28,3 +28,3 @@ import _ from 'lodash'; | ||
const bookshelf = { | ||
VERSION: '0.9.4' | ||
VERSION: require('../package.json').version | ||
}; | ||
@@ -83,3 +83,3 @@ | ||
* // ... | ||
* }) | ||
* }); | ||
* | ||
@@ -159,3 +159,3 @@ * @param {(Model[])=} models | ||
* {name: 'Person2'} | ||
* ]) | ||
* ]); | ||
* | ||
@@ -209,3 +209,2 @@ * Promise.all(accounts.invoke('save')).then(function() { | ||
* ], function(info) { | ||
* | ||
* // Some validation could take place here. | ||
@@ -212,0 +211,0 @@ * return new Book(info).save({'shelf_id': model.id}, {transacting: t}); |
@@ -311,3 +311,5 @@ import _ from 'lodash'; | ||
* let qb = collection.query(); | ||
* qb.where({id: 1}).select().then(function(resp) {... | ||
* qb.where({id: 1}).select().then(function(resp) { | ||
* // ... | ||
* }); | ||
* | ||
@@ -317,3 +319,5 @@ * collection.query(function(qb) { | ||
* }).fetch() | ||
* .then(function(collection) {... | ||
* .then(function(collection) { | ||
* // ... | ||
* }); | ||
* | ||
@@ -324,3 +328,3 @@ * collection | ||
* .then(function(collection) { | ||
* ... | ||
* // ... | ||
* }); | ||
@@ -339,2 +343,30 @@ * | ||
/** | ||
* @method Collection#orderBy | ||
* @since 0.9.3 | ||
* @description | ||
* | ||
* Specifies the column to sort on and sort order. | ||
* | ||
* The order parameter is optional, and defaults to 'ASC'. You may | ||
* also specify 'DESC' order by prepending a hyphen to the sort column | ||
* name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`. | ||
* | ||
* Unless specified using dot notation (i.e., "table.column"), the default | ||
* table will be the table name of the model `orderBy` was called on. | ||
* | ||
* @example | ||
* | ||
* Cars.forge().orderBy('color', 'ASC').fetch() | ||
* .then(function (rows) { // ... | ||
* | ||
* @param sort {string} | ||
* Column to sort on | ||
* @param order {string} | ||
* Ascending ('ASC') or descending ('DESC') order | ||
*/ | ||
orderBy (...args) { | ||
return Helpers.orderBy(this, ...args); | ||
}, | ||
/** | ||
* @method Collection#query | ||
@@ -341,0 +373,0 @@ * @private |
@@ -77,15 +77,19 @@ // EagerRelation | ||
// If there is a response, fetch additional nested eager relations, if any. | ||
if (response.length > 0 && options.withRelated) { | ||
const relatedModel = relatedData.createModel(); | ||
return Promise.try(() => { | ||
// If there is a response, fetch additional nested eager relations, if any. | ||
if (response.length > 0 && options.withRelated) { | ||
const relatedModel = relatedData.createModel(); | ||
// If this is a `morphTo` relation, we need to do additional processing | ||
// to ensure we don't try to load any relations that don't look to exist. | ||
if (relatedData.type === 'morphTo') { | ||
const withRelated = this._filterRelated(relatedModel, options); | ||
if (withRelated.length === 0) return; | ||
options = _.extend({}, options, {withRelated: withRelated}); | ||
// If this is a `morphTo` relation, we need to do additional processing | ||
// to ensure we don't try to load any relations that don't look to exist. | ||
if (relatedData.type === 'morphTo') { | ||
const withRelated = this._filterRelated(relatedModel, options); | ||
if (withRelated.length === 0) return; | ||
options = _.extend({}, options, {withRelated: withRelated}); | ||
} | ||
return new EagerRelation(relatedModels, response, relatedModel).fetch(options).return(response); | ||
} | ||
return new EagerRelation(relatedModels, response, relatedModel).fetch(options).return(response); | ||
} | ||
}).tap(() => { | ||
return Promise.map(relatedModels, (model) => model.triggerThen('fetched', model, model.attributes, options)); | ||
}); | ||
} | ||
@@ -92,0 +96,0 @@ |
@@ -86,2 +86,40 @@ /* eslint no-console: 0 */ | ||
helpers.warn(a + ' has been deprecated, please use ' + b + ' instead') | ||
}, | ||
orderBy (obj, sort, order) { | ||
let tableName; | ||
let idAttribute; | ||
if (obj.model) { | ||
tableName = obj.model.prototype.tableName; | ||
idAttribute = obj.model.prototype.idAttribute ? | ||
obj.model.prototype.idAttribute : 'id'; | ||
} else { | ||
tableName = obj.constructor.prototype.tableName; | ||
idAttribute = obj.constructor.prototype.idAttribute ? | ||
obj.constructor.prototype.idAttribute : 'id'; | ||
} | ||
let _sort; | ||
if (sort && sort.indexOf('-') === 0) { | ||
_sort = sort.slice(1); | ||
} else if (sort) { | ||
_sort = sort; | ||
} else { | ||
_sort = idAttribute; | ||
} | ||
const _order = order || ( | ||
(sort && sort.indexOf('-') === 0) ? 'DESC' : 'ASC' | ||
); | ||
if (_sort.indexOf('.') === -1) { | ||
_sort = `${tableName}.${_sort}`; | ||
} | ||
return obj.query(qb => { | ||
qb.orderBy(_sort, _order); | ||
}); | ||
} | ||
@@ -88,0 +126,0 @@ |
@@ -40,3 +40,3 @@ import _, { assign, isArray } from 'lodash'; | ||
* | ||
* let Books = bookshelf.Model.extend({ | ||
* let Book = bookshelf.Model.extend({ | ||
* tableName: 'documents', | ||
@@ -83,3 +83,3 @@ * constructor: function() { | ||
* new Patient({id: 1}).related('record').fetch().then(function(model) { | ||
* ... | ||
* // ... | ||
* }); | ||
@@ -89,3 +89,3 @@ * | ||
* new Patient({id: 1}).record().fetch().then(function(model) { | ||
* ... | ||
* // ... | ||
* }); | ||
@@ -430,6 +430,6 @@ * | ||
columnNames = arguments[1]; | ||
candidates = _.rest(arguments, 2); | ||
candidates = _.drop(arguments, 2); | ||
} else { | ||
columnNames = null; | ||
candidates = _.rest(arguments); | ||
candidates = _.drop(arguments); | ||
} | ||
@@ -450,3 +450,2 @@ return this._relation('morphTo', null, {morphName, columnNames, candidates}).init(this); | ||
* let Book = bookshelf.Model.extend({ | ||
* | ||
* tableName: 'books', | ||
@@ -463,7 +462,5 @@ * | ||
* } | ||
* | ||
* }); | ||
* | ||
* let Chapter = bookshelf.Model.extend({ | ||
* | ||
* tableName: 'chapters', | ||
@@ -474,7 +471,5 @@ * | ||
* } | ||
* | ||
* }); | ||
* | ||
* let Paragraph = bookshelf.Model.extend({ | ||
* | ||
* tableName: 'paragraphs', | ||
@@ -490,3 +485,2 @@ * | ||
* } | ||
* | ||
* }); | ||
@@ -610,3 +604,3 @@ * | ||
* @param {string|string[]} [options.columns='*'] | ||
* Specify columns to be retireved. | ||
* Specify columns to be retrieved. | ||
* @param {Transaction} [options.transacting] | ||
@@ -1102,3 +1096,2 @@ * Optionally run the query in a transaction. | ||
* @param {Model} model The model firing the event. | ||
* @param {Object} attrs Model firing the event. | ||
* @param {Object} options Options object passed to {@link Model#save save}. | ||
@@ -1173,6 +1166,10 @@ * @returns {Promise} | ||
* }).fetch() | ||
* .then(function(model) { // ... | ||
* .then(function(model) { | ||
* // ... | ||
* }); | ||
* | ||
* let qb = model.query(); | ||
* qb.where({id: 1}).select().then(function(resp) { // ... | ||
* qb.where({id: 1}).select().then(function(resp) { | ||
* // ... | ||
* }); | ||
* | ||
@@ -1220,2 +1217,30 @@ * @method Model#query | ||
/** | ||
* @method Model#orderBy | ||
* @since 0.9.3 | ||
* @description | ||
* | ||
* Specifies the column to sort on and sort order. | ||
* | ||
* The order parameter is optional, and defaults to 'ASC'. You may | ||
* also specify 'DESC' order by prepending a hyphen to the sort column | ||
* name. `orderBy("date", 'DESC')` is the same as `orderBy("-date")`. | ||
* | ||
* Unless specified using dot notation (i.e., "table.column"), the default | ||
* table will be the table name of the model `orderBy` was called on. | ||
* | ||
* @example | ||
* | ||
* Car.forge().orderBy('color', 'ASC').fetchAll() | ||
* .then(function (rows) { // ... | ||
* | ||
* @param sort {string} | ||
* Column to sort on | ||
* @param order {string} | ||
* Ascending ('ASC') or descending ('DESC') order | ||
*/ | ||
orderBy(...args) { | ||
return Helpers.orderBy(this, ...args); | ||
}, | ||
/* Ensure that QueryBuilder is copied on clone. */ | ||
@@ -1222,0 +1247,0 @@ clone() { |
@@ -67,3 +67,3 @@ import _ from 'lodash'; | ||
// The first argument is always a model, so resolve it and call the original method. | ||
return original.apply(this, [resolveModel(Target)].concat(_.rest(arguments))); | ||
return original.apply(this, [resolveModel(Target)].concat(_.drop(arguments))); | ||
}; | ||
@@ -76,3 +76,3 @@ }); | ||
Model.prototype.morphTo = function(relationName) { | ||
return morphTo.apply(this, [relationName].concat(_.map(_.rest(arguments), function(model) { | ||
return morphTo.apply(this, [relationName].concat(_.map(_.drop(arguments), function(model) { | ||
return resolveModel(model); | ||
@@ -85,5 +85,5 @@ }, this))); | ||
Collection.prototype.through = function(Target) { | ||
return collectionThrough.apply(this, [resolveModel(Target)].concat(_.rest(arguments))); | ||
return collectionThrough.apply(this, [resolveModel(Target)].concat(_.drop(arguments))); | ||
}; | ||
}; |
@@ -81,2 +81,15 @@ // Sync | ||
}).then(function() { | ||
options.query = knex; | ||
/** | ||
* Counting event. | ||
* | ||
* Fired before a `count` query. A promise may be | ||
* returned from the event handler for async behaviour. | ||
* | ||
* @event Model#counting | ||
* @param {Model} model The model firing the event. | ||
* @param {Object} options Options object passed to {@link Model#count count}. | ||
* @returns {Promise} | ||
*/ | ||
return this.syncing.triggerThen('counting', this.syncing, options); | ||
@@ -83,0 +96,0 @@ }).then(function() { |
@@ -17,4 +17,5 @@ var Promise = require('../lib/base/promise').default; | ||
process.stderr.on('data', function(data) { | ||
console.log(data); | ||
// http://bluebirdjs.com/docs/api/error-management-configuration.html#global-rejection-events | ||
process.on("unhandledRejection", function(reason, promise) { | ||
console.error(reason); | ||
}); | ||
@@ -21,0 +22,0 @@ |
@@ -52,3 +52,3 @@ var _ = require('lodash'); | ||
} | ||
require('./integration/model')(bookshelf); | ||
@@ -61,2 +61,3 @@ require('./integration/collection')(bookshelf); | ||
require('./integration/plugins/registry')(bookshelf); | ||
require('./integration/plugins/pagination')(bookshelf); | ||
}); | ||
@@ -63,0 +64,0 @@ |
@@ -169,2 +169,24 @@ var Promise = global.testPromise; | ||
describe('orderBy', function () { | ||
it('orders the results by column', function () { | ||
var asc = new Site({id: 1}) | ||
.authors() | ||
.orderBy('first_name', 'ASC') | ||
.fetch(); | ||
var desc = new Site({id: 1}) | ||
.authors() | ||
.orderBy('first_name', 'DESC') | ||
.fetch(); | ||
return Promise.join(asc, desc).then(function (result) { | ||
var r0 = result[0].toJSON().reverse(); | ||
var r1 = result[1].toJSON(); | ||
expect(r0).to.eql(r1); | ||
}) | ||
}) | ||
}) | ||
describe('sync', function() { | ||
@@ -171,0 +193,0 @@ |
@@ -433,2 +433,27 @@ var _ = require('lodash'); | ||
describe('orderBy', function () { | ||
it('returns results in the correct order', function () { | ||
var asc = Models.Customer.forge().orderBy('id', 'ASC').fetchAll() | ||
.then(function (result) { | ||
return result.toJSON().map(function (row) { return row.id }); | ||
}); | ||
var desc = Models.Customer.forge().orderBy('id', 'DESC').fetchAll() | ||
.then(function (result) { | ||
return result.toJSON().map(function (row) { return row.id }); | ||
}) | ||
return Promise.join(asc, desc) | ||
.then(function (results) { | ||
expect(results[0].reverse()).to.eql(results[1]); | ||
}); | ||
}); | ||
it('returns DESC order results with a minus sign', function () { | ||
return Models.Customer.forge().orderBy('-id').fetchAll().then(function (results) { | ||
expect(parseInt(results.models[0].get('id'))).to.equal(4); | ||
}); | ||
}) | ||
}); | ||
describe('save', function() { | ||
@@ -435,0 +460,0 @@ |
@@ -150,2 +150,49 @@ var _ = require('lodash'); | ||
describe('emits \'fetching\' and \'fetched\' events for eagerly loaded relations with', function () { | ||
afterEach(function () { | ||
delete Site.prototype.initialize; | ||
}); | ||
it('withRelated option', function () { | ||
var countFetching = 0; | ||
var countFetched = 0; | ||
Site.prototype.initialize = function () { | ||
this.on('fetching', function () { | ||
countFetching++; | ||
}); | ||
this.on('fetched', function () { | ||
countFetched++; | ||
}); | ||
}; | ||
return Blog.forge({id: 1}).fetch({withRelated: ['site']}) | ||
.then(function() { | ||
equal(countFetching, 1); | ||
equal(countFetched, 1); | ||
}); | ||
}); | ||
it('load() method', function () { | ||
var countFetching = 0; | ||
var countFetched = 0; | ||
Site.prototype.initialize = function () { | ||
this.on('fetching', function () { | ||
countFetching++; | ||
}); | ||
this.on('fetched', function () { | ||
countFetched++; | ||
}); | ||
}; | ||
return Blog.where({id: 1}).fetch() | ||
.then(function (blog) { | ||
return blog.load('site') | ||
.then(function() { | ||
equal(countFetching, 1); | ||
equal(countFetched, 1); | ||
}); | ||
}) | ||
}); | ||
}); | ||
}); | ||
@@ -420,2 +467,16 @@ | ||
it('keeps the pivotal helper methods when cloning a collection having `relatedData` with `type` "belongsToMany", #1197', function() { | ||
var pivotalProps = ['attach', 'detach', 'updatePivot', 'withPivot', '_processPivot', '_processPlainPivot', '_processModelPivot']; | ||
var author = new Author({id: 1}); | ||
var posts = author.related('posts'); | ||
pivotalProps.forEach(function (prop) { | ||
expect(posts[prop]).to.be.an.instanceof(Function); | ||
}); | ||
var clonedAuthor = author.clone() | ||
var clonedPosts = clonedAuthor.related('posts'); | ||
pivotalProps.forEach(function (prop) { | ||
expect(clonedPosts[prop]).to.equal(posts[prop]); | ||
}); | ||
}); | ||
}); | ||
@@ -422,0 +483,0 @@ |
One-to-one associations can be created with {@link Model#belongsTo belongsTo}, {@link Model#hasOne hasOne}, and {@link Model#morphOne morphOne} relation types. | ||
var Book, Summary; | ||
Book = bookshelf.Model.extend({ | ||
var Book = bookshelf.Model.extend({ | ||
tableName: 'books', | ||
@@ -12,3 +10,3 @@ summary: function() { | ||
Summary = bookshelf.Model.extend({ | ||
var Summary = bookshelf.Model.extend({ | ||
tableName: 'summaries', | ||
@@ -15,0 +13,0 @@ book: function() { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1980206
139
19245
160
+ Addedarr-diff@2.0.0(transitive)
+ Addedarr-flatten@1.1.0(transitive)
+ Addedarray-unique@0.2.1(transitive)
+ Addedbluebird@3.7.2(transitive)
+ Addedbraces@1.8.5(transitive)
+ Addeddetect-file@0.1.0(transitive)
+ Addedexpand-brackets@0.1.5(transitive)
+ Addedexpand-range@1.8.2(transitive)
+ Addedexpand-tilde@1.2.2(transitive)
+ Addedextend@3.0.2(transitive)
+ Addedextglob@0.3.2(transitive)
+ Addedfilename-regex@2.0.1(transitive)
+ Addedfill-range@2.2.4(transitive)
+ Addedfindup-sync@0.4.3(transitive)
+ Addedfor-in@1.0.2(transitive)
+ Addedfor-own@0.1.5(transitive)
+ Addedfs-exists-sync@0.1.0(transitive)
+ Addedfunction-bind@1.1.2(transitive)
+ Addedglob-base@0.3.0(transitive)
+ Addedglob-parent@2.0.0(transitive)
+ Addedglobal-modules@0.2.3(transitive)
+ Addedglobal-prefix@0.1.5(transitive)
+ Addedhasown@2.0.2(transitive)
+ Addedhomedir-polyfill@1.0.3(transitive)
+ Addedini@1.3.8(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedis-core-module@2.15.1(transitive)
+ Addedis-dotfile@1.0.3(transitive)
+ Addedis-equal-shallow@0.1.3(transitive)
+ Addedis-extendable@0.1.1(transitive)
+ Addedis-extglob@1.0.0(transitive)
+ Addedis-glob@2.0.1(transitive)
+ Addedis-number@2.1.04.0.0(transitive)
+ Addedis-posix-bracket@0.1.1(transitive)
+ Addedis-primitive@2.0.0(transitive)
+ Addedis-windows@0.2.0(transitive)
+ Addedisarray@1.0.0(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedisobject@2.1.0(transitive)
+ Addedkind-of@3.2.26.0.3(transitive)
+ Addedknex@0.11.10(transitive)
+ Addedliftoff@2.2.5(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedmath-random@1.0.4(transitive)
+ Addedmicromatch@2.3.11(transitive)
+ Addednode-uuid@1.4.8(transitive)
+ Addednormalize-path@2.1.1(transitive)
+ Addedobject.omit@2.0.1(transitive)
+ Addedos-homedir@1.0.2(transitive)
+ Addedparse-glob@3.0.4(transitive)
+ Addedparse-passwd@1.0.0(transitive)
+ Addedpath-parse@1.0.7(transitive)
+ Addedpreserve@0.2.0(transitive)
+ Addedrandomatic@3.1.1(transitive)
+ Addedrechoir@0.6.2(transitive)
+ Addedregex-cache@0.4.4(transitive)
+ Addedremove-trailing-separator@1.1.0(transitive)
+ Addedrepeat-element@1.1.4(transitive)
+ Addedrepeat-string@1.6.1(transitive)
+ Addedresolve@1.22.8(transitive)
+ Addedresolve-dir@0.1.1(transitive)
+ Addedsupports-preserve-symlinks-flag@1.0.0(transitive)
+ Addedwhich@1.3.1(transitive)
- Removedbalanced-match@1.0.2(transitive)
- Removedbrace-expansion@1.1.11(transitive)
- Removedconcat-map@0.0.1(transitive)
- Removedextend@2.0.2(transitive)
- Removedfindup-sync@0.2.1(transitive)
- Removedglob@4.3.5(transitive)
- Removedinflight@1.0.6(transitive)
- Removedknex@0.10.0(transitive)
- Removedliftoff@2.0.3(transitive)
- Removedminimatch@2.0.10(transitive)
- Removedonce@1.4.0(transitive)
- Removedresolve@1.1.7(transitive)
- Removedwrappy@1.0.2(transitive)
Updatedlodash@^3.8.0