function Bookshelf(knex) {
var bookshelf = {
VERSION: require('../package.json').version
};
var Model = bookshelf.Model = _model2.default.extend({
_builder: builderFn,
// The `Model` constructor is referenced as a property on the `Bookshelf`
// instance, mixing in the correct `builder` method, as well as the
// `relation` method, passing in the correct `Model` & `Collection`
// constructors for later reference.
_relation: function _relation(type, Target, options) {
if (type !== 'morphTo' && !(0, _lodash.isFunction)(Target)) {
throw new Error('A valid target model must be defined for the ' + (0, _lodash.result)(this, 'tableName') + ' ' + type + '
relation');
}
return new Relation(type, Target, options);
}
}, {
/**
* @method Model.forge
* @belongsTo Model
* @description
*
* A simple helper function to instantiate a new Model without needing `new`.
*
* @param {Object=} attributes Initial values for this model's attributes.
* @param {Object=} options Hash of options.
* @param {string=} options.tableName Initial value for {@linkcode Model#tableName tableName}.
* @param {boolean=} [options.hasTimestamps=false]
*
* Initial value for {@linkcode Model#hasTimestamps hasTimestamps}.
*
* @param {boolean} [options.parse=false]
*
* Convert attributes by {@linkcode Model#parse parse} before being
* {@linkcode Model#set set} on the `model`.
*/
forge: forge,
/**
* @method Model.collection
* @belongsTo Model
* @description
*
* A simple static helper to instantiate a new {@link Collection}, setting
* the current `model` as the collection's target.
*
* @example
*
* Customer.collection().fetch().then(function(collection) {
* // ...
* });
*
* @param {(Model[])=} models
* @param {Object=} options
* @returns {Collection}
*/
collection: function collection(models, options) {
return new bookshelf.Collection(models || [], (0, _lodash.extend)({}, options, { model: this }));
},
/**
* @method Model.count
* @belongsTo Model
* @since 0.8.2
* @description
*
* Gets the number of matching records in the database, respecting any
* previous calls to {@link Model#query query}. If a `column` is provided,
* records with a null value in that column will be excluded from the count.
*
* @param {string} [column='*']
* Specify a column to count - rows with null values in this column will be excluded.
* @param {Object=} options
* Hash of options.
* @returns {Promise<Number>}
* A promise resolving to the number of matching rows.
*/
count: function count(column, options) {
return this.forge().count(column, options);
},
/**
* @method Model.fetchAll
* @belongsTo Model
* @description
*
* Simple helper function for retrieving all instances of the given model.
*
* @see Model#fetchAll
* @returns {Promise<Collection>}
*/
fetchAll: function fetchAll(options) {
return this.forge().fetchAll(options);
}
});
var Collection = bookshelf.Collection = _collection2.default.extend({
_builder: builderFn
}, {
/**
* @method Collection.forge
* @belongsTo Collection
* @description
*
* A simple helper function to instantiate a new Collection without needing
* new.
*
* @param {(Object[]|Model[])=} [models]
* Set of models (or attribute hashes) with which to initialize the
* collection.
* @param {Object} options Hash of options.
*
* @example
*
* var Promise = require('bluebird');
* var Accounts = bookshelf.Collection.extend({
* model: Account
* });
*
* var accounts = Accounts.forge([
* {name: 'Person1'},
* {name: 'Person2'}
* ]);
*
* Promise.all(accounts.invoke('save')).then(funct ...
n/a
initialize = function (knex) { _helpers2.default.warn("Bookshelf.initialize is deprecated, pass knex directly: require('bookshelf')(knex)"); return new Bookshelf(knex); }
n/a
model = function () { return Parent.apply(this, arguments); }
...
* Optionally run the query in a transaction.
*
* @throws {Model.NotFoundError}
* @returns {Promise<Model|null>}
* A promise resolving to the fetched {@link Model model} or `null` if none exists.
*/
fetchOne: _promise2.default.method(function (options) {
var model = new this.model();
model._knex = this.query().clone();
this.resetQuery();
if (this.relatedData) model.relatedData = this.relatedData;
return model.fetch(options);
}),
/**
...
function Sync(syncing, options) { options = options || {}; this.query = syncing.query(); this.syncing = syncing.resetQuery(); this.options = options; if (options.debug) this.query.debug(); if (options.transacting) this.query.transacting(options.transacting); }
...
* @param {Object=} options
* @param {bool} [options.require=false] Trigger a {@link Collection.EmptyError} if no records are found.
* @param {string|string[]} [options.withRelated=[]] A relation, or list of relations, to be eager loaded as part of the `fetch
` operation.
* @returns {Promise<Collection>}
*/
fetch: _promise2.default.method(function (options) {
options = options ? (0, _lodash.clone)(options) : {};
return this.sync(options).select().bind(this).tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.EmptyError('EmptyResponse');
}
})
// Now, load all of the data onto the collection as necessary.
.tap(this._handleResponse)
...
default = function () { return Parent.apply(this, arguments); }
...
*
* @returns {Promise<Collection>} A promise resolving to this {@link
* Collection collection}
*/
load: _promise2.default.method(function (relations, options) {
if (!(0, _lodash.isArray)(relations)) relations = [relations];
options = (0, _lodash.extend)({}, options, { shallow: true, withRelated: relations });
return new _eager2.default(this.models, this.toJSON(options), new this.model()).fetch
(options).return(this);
}),
/**
* @method Collection#create
* @description
*
* Convenience method to create a new {@link Model model} instance within a
...
function EagerRelation() { (0, _classCallCheck3.default)(this, EagerRelation); return (0, _possibleConstructorReturn3.default)(this, (EagerRelation.__proto__ || Object.getPrototypeOf(EagerRelation)).apply( this, arguments)); }
...
*
* @returns {Promise<Collection>} A promise resolving to this {@link
* Collection collection}
*/
load: _promise2.default.method(function (relations, options) {
if (!(0, _lodash.isArray)(relations)) relations = [relations];
options = (0, _lodash.extend)({}, options, { shallow: true, withRelated: relations });
return new _eager2.default(this.models, this.toJSON(options), new this.model()).fetch
(options).return(this);
}),
/**
* @method Collection#create
* @description
*
* Convenience method to create a new {@link Model model} instance within a
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
* @param {string|string[]} [options.withRelated=[]] A relation, or list of relations, to be eager loaded as part of the `fetch
` operation.
* @returns {Promise<Collection>}
*/
fetch: _promise2.default.method(function (options) {
options = options ? (0, _lodash.clone)(options) : {};
return this.sync(options).select().bind(this).tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.EmptyError('EmptyResponse');
}
})
// Now, load all of the data onto the collection as necessary.
.tap(this._handleResponse)
// If the "withRelated" is specified, we also need to eager load all of the
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
* @returns {Promise}
*/
return this.triggerThen('destroying', this, options);
}).then(function () {
return sync.del();
}).then(function (resp) {
if (options.require && resp === 0) {
throw new this.constructor.NoRowsDeletedError('No Rows Deleted');
}
this.clear();
/**
* Destroyed event.
*
* Fired before a `delete` query. A promise may be returned from the event
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
if (method === 'insert' && this.id == null) {
var updatedCols = {};
updatedCols[this.idAttribute] = this.id = resp[0];
var updatedAttrs = this.parse(updatedCols);
_lodash2.default.assign(this.attributes, updatedAttrs);
} else if (method === 'update' && resp === 0) {
if (options.require !== false) {
throw new this.constructor.NoRowsUpdatedError('No Rows Updated');
}
}
// In case we need to reference the `previousAttributes` for the this
// in the following event handlers.
options.previousAttributes = this._previousAttributes;
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
// Run the `first` call on the `sync` object to fetch a single model.
return this.sync(options).first(attributes).bind(this)
// Jump the rest of the chain if the response doesn't exist...
.tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.NotFoundError('EmptyResponse');
}
})
// Now, load all of the data into the model as necessary.
.tap(this._handleResponse)
// If the "withRelated" is specified, we also need to eager load all of the
...
function Events() { (0, _classCallCheck3.default)(this, Events); return (0, _possibleConstructorReturn3.default)(this, (Events.__proto__ || Object.getPrototypeOf(Events)).apply(this, arguments )); }
...
*
* @returns {Promise<Collection>} A promise resolving to this {@link
* Collection collection}
*/
load: _promise2.default.method(function (relations, options) {
if (!(0, _lodash.isArray)(relations)) relations = [relations];
options = (0, _lodash.extend)({}, options, { shallow: true, withRelated: relations });
return new _eager2.default(this.models, this.toJSON(options), new this.model()).fetch
(options).return(this);
}),
/**
* @method Collection#create
* @description
*
* Convenience method to create a new {@link Model model} instance within a
...
function deprecate(a, b) { helpers.warn(a + ' has been deprecated, please use ' + b + ' instead'); }
n/a
function error(msg) { console.log(chalk.red(msg)); }
...
var Tag = bookshelf.Model.extend({
tableName: 'tags'
})
User.where('id', 1).fetch({withRelated: ['posts.tags']}).then(function(user) {
console.log(user.related('posts').toJSON());
}).catch(function(err) {
console.error(err);
});
```
## Plugins
* [Registry](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Model-Registry): Register models in a central location so that
you can refer to them using a string in relations instead of having to require it every time. Helps deal with the challenges of
circular module dependencies in Node.
* [Virtuals](https://github.com/tgriesser/bookshelf/wiki/Plugin:-Virtuals): Define virtual properties on your model to compute new
values.
...
function morphCandidate(candidates, foreignTable) { var Target = _.find(candidates, function (Candidate) { return _.result(Candidate.prototype, 'tableName') === foreignTable; }); if (!Target) { throw new Error('The target polymorphic model was not found'); } return Target; }
...
_columnNames$2 = _columnNames[1],
idColumn = _columnNames$2 === undefined ? morphName + '_id' : _columnNames$2;
var parentsByType = _lodash2.default.groupBy(this.parent, function (model) {
return model.get(typeColumn);
});
var TargetByType = _lodash2.default.mapValues(parentsByType, function (parents, type) {
return _helpers2.default.morphCandidate(relatedData.candidates, type);
});
return _promise2.default.all(_lodash2.default.map(parentsByType, function (parents, type) {
var Target = TargetByType[type];
var idAttribute = _lodash2.default.result(Target.prototype, 'idAttribute');
var ids = getAttributeUnique(parents, idColumn);
...
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); }); }
...
* 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
*/
...
function query(obj, args) { // Ensure the object has a query builder. if (!obj._knex) { var tableName = _.result(obj, 'tableName'); obj._knex = obj._builder(tableName); } // If there are no arguments, return the query builder. if (args.length === 0) return obj._knex; var method = args[0]; if (_.isFunction(method)) { // `method` is a query builder callback. Call it on the query builder // object. method.call(obj._knex, obj._knex); } else if (_.isObject(method)) { // `method` is an object. Use keys as methods and values as arguments to // the query builder. for (var key in method) { var target = _.isArray(method[key]) ? method[key] : [method[key]]; obj._knex[key].apply(obj._knex, target); } } else { // Otherwise assume that the `method` is string name of a query builder // method, and use the remaining args as arguments to that method. obj._knex[method].apply(obj._knex, args.slice(1)); } return obj; }
...
* Get the number of records in the collection's table.
*
* @example
*
* // select count(*) from shareholders where company_id = 1 and share > 0.1;
* Company.forge({id:1})
* .shareholders()
* .query('where', 'share', '>', '0.1')
* .count()
* .then(function(count) {
* assert(count === 3);
* });
*
* @param {string} [column='*']
* Specify a column to count - rows with null values in this column will be excluded.
...
function saveConstraints(model, relatedData) { var data = {}; if (relatedData && !relatedData.isThrough() && relatedData.type !== 'belongsToMany' && relatedData.type !== 'belongsTo') { data[relatedData.key('foreignKey')] = relatedData.parentFk || model.get(relatedData.key('foreignKey')); if (relatedData.isMorph()) data[relatedData.key('morphKey')] = relatedData.key('morphValue'); } return model.set(model.parse(data)); }
...
// If we've already added things on the query chain,
// these are likely intended for the model.
if (this._knex) {
model._knex = this._knex;
this.resetQuery();
}
return _helpers2.default.saveConstraints(model, relatedData).save(null, options).bind
(this).then(function () {
if (relatedData && relatedData.type === 'belongsToMany') {
return this.attach(model, (0, _lodash.omit)(options, 'query'));
}
}).then(function () {
this.add(model, options);
}).return(model);
}),
...
function warn(msg) { console.log(chalk.yellow(msg)); }
...
},
warn: function warn(msg) {
console.log(chalk.yellow(msg));
},
deprecate: function deprecate(a, b) {
helpers.warn(a + ' has been deprecated, please use ' + b + ' instead
x27;);
},
orderBy: function orderBy(obj, sort, order) {
var tableName = void 0;
var idAttribute = void 0;
...
model = function () { return Parent.apply(this, arguments); }
...
* Optionally run the query in a transaction.
*
* @throws {Model.NotFoundError}
* @returns {Promise<Model|null>}
* A promise resolving to the fetched {@link Model model} or `null` if none exists.
*/
fetchOne: _promise2.default.method(function (options) {
var model = new this.model();
model._knex = this.query().clone();
this.resetQuery();
if (this.relatedData) model.relatedData = this.relatedData;
return model.fetch(options);
}),
/**
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
* @returns {Promise}
*/
return this.triggerThen('destroying', this, options);
}).then(function () {
return sync.del();
}).then(function (resp) {
if (options.require && resp === 0) {
throw new this.constructor.NoRowsDeletedError('No Rows Deleted');
}
this.clear();
/**
* Destroyed event.
*
* Fired before a `delete` query. A promise may be returned from the event
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
if (method === 'insert' && this.id == null) {
var updatedCols = {};
updatedCols[this.idAttribute] = this.id = resp[0];
var updatedAttrs = this.parse(updatedCols);
_lodash2.default.assign(this.attributes, updatedAttrs);
} else if (method === 'update' && resp === 0) {
if (options.require !== false) {
throw new this.constructor.NoRowsUpdatedError('No Rows Updated');
}
}
// In case we need to reference the `previousAttributes` for the this
// in the following event handlers.
options.previousAttributes = this._previousAttributes;
...
function ErrorCtor(message, obj) { attachProps(this, properties); attachProps(this, obj); this.message = (message || this.message); if (message instanceof Error) { this.message = message.message; this.stack = message.stack; } else if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } }
...
// Run the `first` call on the `sync` object to fetch a single model.
return this.sync(options).first(attributes).bind(this)
// Jump the rest of the chain if the response doesn't exist...
.tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.NotFoundError('EmptyResponse');
}
})
// Now, load all of the data into the model as necessary.
.tap(this._handleResponse)
// If the "withRelated" is specified, we also need to eager load all of the
...
function extend(protoProps, staticProps) { var Parent = this; // The constructor function for the new subclass is either defined by you // (the "constructor" property in your `extend` definition), or defaulted // by us to simply call the parent's constructor. var Child = protoProps && protoProps.hasOwnProperty('constructor') ? protoProps.constructor : function () { return Parent.apply(this, arguments); }; (0, _lodash.assign)(Child, Parent, staticProps); // Set the prototype chain to inherit from `Parent`. Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }); if (protoProps) { (0, _lodash.assign)(Child.prototype, protoProps); } // Give child access to the parent prototype as part of "super" Child.__super__ = Parent.prototype; // If there is an "extended" function set on the parent, // call it with the extended child object. if ((0, _lodash.isFunction)(Parent.extended)) Parent.extended(Child); return Child; }
...
database : 'myapp_test',
charset : 'utf8'
}
});
var bookshelf = require('bookshelf')(knex);
var User = bookshelf.Model.extend({
tableName: 'users'
});
```
This initialization should likely only ever happen once in your application. As it creates a connection pool for the current database
, you should use the `bookshelf` instance returned throughout your library. You'll need to store this instance created by the
initialize somewhere in the application so you can reference it. A common pattern to follow is to initialize the client in a module
so you can easily reference it later:
```js
...
function extended(child) {
/**
* @class Model.NotFoundError
* @description
*
* Thrown when no records are found by {@link Model#fetch fetch} or
* {@link Model#refresh} when called with the
* `{require: true}` option.
*/
child.NotFoundError = (0, _createError2.default)(this.NotFoundError);
/**
* @class Model.NoRowsUpdatedError
* @description
*
* Thrown when no records are saved by {@link Model#save save}
* unless called with the `{require: false}` option.
*/
child.NoRowsUpdatedError = (0, _createError2.default)(this.NoRowsUpdatedError);
/**
* @class Model.NoRowsDeletedError
* @description
*
* Thrown when no record is deleted by {@link Model#destroy destroy}
* if called with the `{require: true}` option.
*/
child.NoRowsDeletedError = (0, _createError2.default)(this.NoRowsDeletedError);
}
...
}
// Give child access to the parent prototype as part of "super"
Child.__super__ = Parent.prototype;
// If there is an "extended" function set on the parent,
// call it with the extended child object.
if ((0, _lodash.isFunction)(Parent.extended)) Parent.extended(Child);
return Child;
};
...
function Events() { (0, _classCallCheck3.default)(this, Events); return (0, _possibleConstructorReturn3.default)(this, (Events.__proto__ || Object.getPrototypeOf(Events)).apply(this, arguments )); }
n/a
_doFetch = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
*/
refresh: function refresh(options) {
// If this is new, we use all its attributes. Otherwise we just grab the
// primary key.
var attributes = this.isNew() ? this.attributes : _lodash2.default.pick(this.attributes, this.idAttribute);
return this._doFetch(attributes, options);
},
/**
* Fetches a {@link Model model} from the database, using any {@link
* Model#attributes attributes} currently set on the model to form a `select`
* query.
...
function _handleEager(response, options) { return new _eager2.default([this], response, this).fetch(options); }
...
// If the "withRelated" is specified, we also need to eager load all of the
// data on the collection, as a side-effect, before we ultimately jump into the
// next step of the collection. Since the `columns` are only relevant to the current
// level, ensure those are omitted from the options.
.tap(function (response) {
if (options.withRelated) {
return this._handleEager(response, (0, _lodash.omit)(options, 'columns'));
}
}).tap(function (response) {
/**
* @event Collection#fetched
*
* @description
...
function _handleResponse(response) { var relatedData = this.relatedData; this.set(this.parse(response[0]), { silent: true })._reset(); if (relatedData && relatedData.isJoined()) { relatedData.parsePivot([this]); } }
n/a
function _morphOneOrMany(Target, morphName, columnNames, morphValue, type) { if (!_lodash2.default.isArray(columnNames)) { // Shift by one place morphValue = columnNames; columnNames = null; } if (!morphName || !Target) throw new Error('The polymorphic `name` and `Target` are required.'); return this._relation(type, Target, { morphName: morphName, morphValue: morphValue, columnNames: columnNames }).init(this); }
...
* The string value associated with this relationship. Stored in the `_type`
* column of the polymorphic table. Defaults to `Target#{@link
* Model#tableName tableName}`.
*
* @returns {Model} The related model.
*/
morphOne: function morphOne(Target, name, columnNames, morphValue) {
return this._morphOneOrMany(Target, name, columnNames, morphValue, 'morphOne
x27;);
},
/**
* {@link Model#morphMany morphMany} is essentially the same as a {@link
* Model#morphOne morphOne}, but creating a {@link Collection collection}
* rather than a {@link Model model} (similar to a {@link Model#hasOne
...
function all() { var collection = this.constructor.collection(); collection._knex = this.query().clone(); this.resetQuery(); if (this.relatedData) collection.relatedData = this.relatedData; return collection; }
...
var parentsByType = _lodash2.default.groupBy(this.parent, function (model) {
return model.get(typeColumn);
});
var TargetByType = _lodash2.default.mapValues(parentsByType, function (parents, type) {
return _helpers2.default.morphCandidate(relatedData.candidates, type);
});
return _promise2.default.all(_lodash2.default.map(parentsByType, function (parents
, type) {
var Target = TargetByType[type];
var idAttribute = _lodash2.default.result(Target.prototype, 'idAttribute');
var ids = getAttributeUnique(parents, idColumn);
return Target.query('whereIn', idAttribute, ids).sync(options).select().tap(function (response) {
var clone = relatedData.instance('morphTo', Target, { morphName: morphName, columnNames: columnNames });
return _this3._eagerLoadHelper(response, relationName, { relatedData: clone }, options);
...
function belongsTo(Target, foreignKey, foreignKeyTarget) { return this._relation('belongsTo', Target, { foreignKey: foreignKey, foreignKeyTarget: foreignKeyTarget }).init(this); }
...
* Model#belongsTo belongsTo} relationship is used for a model that is a
* member of another Target model, referenced by the foreignKey in the current
* model.
*
* let Book = bookshelf.Model.extend({
* tableName: 'books',
* author: function() {
* return this.belongsTo(Author);
* }
* });
*
* // select * from `books` where id = 1
* // select * from `authors` where id = book.author_id
* Book.where({id: 1}).fetch({withRelated: ['author']}).then(function(book) {
* console.log(JSON.stringify(book.related('author')));
...
function belongsToMany(Target, joinTableName, foreignKey, otherKey, foreignKeyTarget, otherKeyTarget) { return this._relation('belongsToMany', Target, { joinTableName: joinTableName, foreignKey: foreignKey, otherKey: otherKey, foreignKeyTarget: foreignKeyTarget, otherKeyTarget : otherKeyTarget }).init(this); }
...
return this.hasMany(Posts);
}
});
var Posts = bookshelf.Model.extend({
tableName: 'messages',
tags: function() {
return this.belongsToMany(Tag);
}
});
var Tag = bookshelf.Model.extend({
tableName: 'tags'
})
...
function clone() { // This needs to use the direct apply method because the spread operator // incorrectly converts to `clone.apply(ModelBase.prototype, arguments)` // instead of `apply(this, arguments)` var cloned = BookshelfModel.__super__.clone.apply(this, arguments); if (this._knex != null) { cloned._knex = cloned._builder(this._knex.clone()); } return cloned; }
...
*
* @throws {Model.NotFoundError}
* @returns {Promise<Model|null>}
* A promise resolving to the fetched {@link Model model} or `null` if none exists.
*/
fetchOne: _promise2.default.method(function (options) {
var model = new this.model();
model._knex = this.query().clone();
this.resetQuery();
if (this.relatedData) model.relatedData = this.relatedData;
return model.fetch(options);
}),
/**
* @method Collection#load
...
function count(column, options) { return this.all().count(column, options); }
...
*
* @example
*
* // select count(*) from shareholders where company_id = 1 and share > 0.1;
* Company.forge({id:1})
* .shareholders()
* .query('where', 'share', '>', '0.1')
* .count()
* .then(function(count) {
* assert(count === 3);
* });
*
* @param {string} [column='*']
* Specify a column to count - rows with null values in this column will be excluded.
* @param {Object=} options
...
destroy = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
Make sure you check that the type is correct for the initial parameters passed to the initial model being fetched. For example `
new Model({id: '1'}).load([relations...])` will not return the same as `Model({id: 1}).load([relations...])` - notice
that the id is a string in one case and a number in the other. This can be a common mistake if retrieving the id from a url parameter
.
This is only an issue if you're eager loading data with load without first fetching the original model. `Model({id: '1
'}).fetch({withRelated: [relations...]})` should work just fine.
### My process won't exit after my script is finished, why?
The issue here is that Knex, the database abstraction layer used by Bookshelf, uses connection pooling and thus keeps the database
connection open. If you want your process to exit after your script has finished, you will have to call `.destroy(cb)` on the `knex` property of your `Bookshelf` instance or on the `Knex` instance passed during initialization
. More information about connection pooling can be found over at the [Knex docs](http://knexjs.org/#Installation-pooling).
### How do I debug?
If you pass `{debug: true}` as one of the options in your initialize settings, you can see all of the query calls being made. Sometimes
you need to dive a bit further into the various calls and see what all is going on behind the scenes. I'd recommend [node-
inspector](https://github.com/dannycoates/node-inspector), which allows you to debug code with `debugger` statements like you would
in the browser.
Bookshelf uses its own copy of the "bluebird" promise library, you can read up here for more on debugging these promises
... but in short, adding:
...
function fetch(options) { // Fetch uses all set attributes. return this._doFetch(this.attributes, options); }
...
}
});
var Tag = bookshelf.Model.extend({
tableName: 'tags'
})
User.where('id', 1).fetch({withRelated: ['posts.tags']}).then(function
(user) {
console.log(user.related('posts').toJSON());
}).catch(function(err) {
console.error(err);
});
```
## Plugins
...
function fetchAll(options) {
var _this = this;
var collection = this.all();
return collection.once('fetching', function (__, columns, opts) {
/**
* Fired before a {@link Model#fetchAll fetchAll} operation. A promise
* may be returned from the event handler for async behaviour.
*
* @event Model#"fetching:collection"
* @param {Model} collection The collection that has been fetched.
* @param {string[]} columns The columns being retrieved by the query.
* @param {Object} options Options object passed to {@link Model#fetchAll fetchAll}.
* @returns {Promise}
*/
return _this.triggerThen('fetching:collection', collection, columns, opts);
}).once('fetched', function (__, resp, opts) {
/**
* Fired after a {@link Model#fetchAll fetchAll} operation. A promise
* may be returned from the event handler for async behaviour.
*
* @event Model#"fetched:collection"
* @param {Model} collection The collection that has been fetched.
* @param {Object} resp The Knex query response.
* @param {Object} options Options object passed to {@link Model#fetchAll fetchAll}.
* @returns {Promise}
*/
return _this.triggerThen('fetched:collection', collection, resp, opts);
}).fetch(options);
}
...
* 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
*/
...
function hasMany(Target, foreignKey, foreignKeyTarget) { return this._relation('hasMany', Target, { foreignKey: foreignKey, foreignKeyTarget: foreignKeyTarget }).init(this); }
...
```js
var knex = require('knex')({client: 'mysql', connection: process.env.MYSQL_DATABASE_CONNECTION });
var bookshelf = require('bookshelf')(knex);
var User = bookshelf.Model.extend({
tableName: 'users',
posts: function() {
return this.hasMany(Posts);
}
});
var Posts = bookshelf.Model.extend({
tableName: 'messages',
tags: function() {
return this.belongsToMany(Tag);
...
function hasOne(Target, foreignKey, foreignKeyTarget) { return this._relation('hasOne', Target, { foreignKey: foreignKey, foreignKeyTarget: foreignKeyTarget }).init(this); }
...
* let Record = bookshelf.Model.extend({
* tableName: 'health_records'
* });
*
* let Patient = bookshelf.Model.extend({
* tableName: 'patients',
* record: function() {
* return this.hasOne(Record);
* }
* });
*
* // select * from `health_records` where `patient_id` = 1;
* new Patient({id: 1}).related('record').fetch().then(function(model) {
* // ...
* });
...
load = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
### Can I use standard node.js style callbacks?
Yes - you can call `.asCallback(function(err, resp) {` on any "sync" method and use the standard `(err, result)` style
callback interface if you prefer.
### My relations don't seem to be loading, what's up?
Make sure you check that the type is correct for the initial parameters passed to the initial model being fetched. For example `
new Model({id: '1'}).load([relations...])` will not return the same as `Model
({id: 1}).load([relations...])` - notice that the id is a string in one case and a number in the other. This can be a common mistake
if retrieving the id from a url parameter.
This is only an issue if you're eager loading data with load without first fetching the original model. `Model({id: '1
'}).fetch({withRelated: [relations...]})` should work just fine.
### My process won't exit after my script is finished, why?
The issue here is that Knex, the database abstraction layer used by Bookshelf, uses connection pooling and thus keeps the database
connection open. If you want your process to exit after your script has finished, you will have to call `.destroy(cb)` on the `
knex` property of your `Bookshelf` instance or on the `Knex` instance passed during initialization. More information about connection
pooling can be found over at the [Knex docs](http://knexjs.org/#Installation-pooling).
...
function morphMany(Target, name, columnNames, morphValue) { return this._morphOneOrMany(Target, name, columnNames, morphValue, 'morphMany'); }
...
* and `imageable_id`. The `morphValue` may be optionally set to
* store/retrieve a different value in the `_type` column than the `Target`'s
* {@link Model#tableName tableName}.
*
* let Post = bookshelf.Model.extend({
* tableName: 'posts',
* photos: function() {
* return this.morphMany(Photo, 'imageable');
* }
* });
*
* And with custom columnNames:
*
* let Post = bookshelf.Model.extend({
* tableName: 'posts',
...
function morphOne(Target, name, columnNames, morphValue) { return this._morphOneOrMany(Target, name, columnNames, morphValue, 'morphOne'); }
...
* below the table names would be `imageable_type` and `imageable_id`. The
* `morphValue` may be optionally set to store/retrieve a different value in
* the `_type` column than the {@link Model#tableName}.
*
* let Site = bookshelf.Model.extend({
* tableName: 'sites',
* photo: function() {
* return this.morphOne(Photo, 'imageable');
* }
* });
*
* And with custom `columnNames`:
*
* let Site = bookshelf.Model.extend({
* tableName: 'sites',
...
function morphTo(morphName) { if (!_lodash2.default.isString(morphName)) throw new Error('The `morphTo` name must be specified.'); var columnNames = void 0, candidates = void 0; if (_lodash2.default.isArray(arguments[1])) { columnNames = arguments[1]; candidates = _lodash2.default.drop(arguments, 2); } else { columnNames = null; candidates = _lodash2.default.drop(arguments); } return this._relation('morphTo', null, { morphName: morphName, columnNames: columnNames, candidates: candidates }).init(this); }
...
* morphMany} relations, where the `targets` must be passed to signify which
* {@link Model models} are the potential opposite end of the {@link
* polymorphicRelation polymorphic relation}.
*
* let Photo = bookshelf.Model.extend({
* tableName: 'photos',
* imageable: function() {
* return this.morphTo('imageable', Site, Post);
* }
* });
*
* And with custom columnNames:
*
* let Photo = bookshelf.Model.extend({
* tableName: 'photos',
...
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)); }
...
* 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
*/
...
function query() { return _helpers2.default.query(this, _lodash2.default.toArray(arguments)); }
...
* Get the number of records in the collection's table.
*
* @example
*
* // select count(*) from shareholders where company_id = 1 and share > 0.1;
* Company.forge({id:1})
* .shareholders()
* .query('where', 'share', '>', '0.1')
* .count()
* .then(function(count) {
* assert(count === 3);
* });
*
* @param {string} [column='*']
* Specify a column to count - rows with null values in this column will be excluded.
...
function refresh(options) { // If this is new, we use all its attributes. Otherwise we just grab the // primary key. var attributes = this.isNew() ? this.attributes : _lodash2.default.pick(this.attributes, this.idAttribute); return this._doFetch(attributes, options); }
n/a
function resetQuery() { this._knex = null; return this; }
...
* @throws {Model.NotFoundError}
* @returns {Promise<Model|null>}
* A promise resolving to the fetched {@link Model model} or `null` if none exists.
*/
fetchOne: _promise2.default.method(function (options) {
var model = new this.model();
model._knex = this.query().clone();
this.resetQuery();
if (this.relatedData) model.relatedData = this.relatedData;
return model.fetch(options);
}),
/**
* @method Collection#load
* @description
...
save = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
*
* When used on a relation, `create` will automatically set foreign key
* attributes before persisting the `Model`.
*
* ```
* const { courses, ...attributes } = req.body;
*
* Student.forge(attributes).save().tap(student =>
* Promise.map(courses, course => student.related('courses').create(course))
* ).then(student =>
* res.status(200).send(student)
* ).catch(error =>
* res.status(500).send(error.message)
* );
* ```
...
function sync(options) { return new _sync2.default(this, options); }
...
* @param {Object=} options
* @param {bool} [options.require=false] Trigger a {@link Collection.EmptyError} if no records are found.
* @param {string|string[]} [options.withRelated=[]] A relation, or list of relations, to be eager loaded as part of the `fetch
` operation.
* @returns {Promise<Collection>}
*/
fetch: _promise2.default.method(function (options) {
options = options ? (0, _lodash.clone)(options) : {};
return this.sync(options).select().bind(this).tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.EmptyError('EmptyResponse');
}
})
// Now, load all of the data onto the collection as necessary.
.tap(this._handleResponse)
...
function through(Interim, throughForeignKey, otherKey, throughForeignKeyTarget, otherKeyTarget) { return this.relatedData.through(this, Interim, { throughForeignKey: throughForeignKey, otherKey: otherKey, throughForeignKeyTarget: throughForeignKeyTarget, otherKeyTarget: otherKeyTarget }); }
...
*
* Column in this collection model which `otherKey` references, if other
* than `id` / `{@link Model#idAttribute idAttribute}`.
*
* @returns {Collection}
*/
through: function through(Interim, throughForeignKey, otherKey, throughForeignKeyTarget, otherKeyTarget) {
return this.relatedData.through(this, Interim, {
throughForeignKey: throughForeignKey, otherKey: otherKey, throughForeignKeyTarget: throughForeignKeyTarget, otherKeyTarget:
otherKeyTarget
});
},
/**
* @method Collection#fetch
* @description
...
function where() { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return this.query.apply(this, ['where'].concat(args)); }
...
}
});
var Tag = bookshelf.Model.extend({
tableName: 'tags'
})
User.where('id', 1).fetch({withRelated: ['posts.tags']}).then(function
(user) {
console.log(user.related('posts').toJSON());
}).catch(function(err) {
console.error(err);
});
```
## Plugins
...
function Promise(executor) { if (executor !== INTERNAL) { check(this, executor); } this._bitField = 0; this._fulfillmentHandler0 = undefined; this._rejectionHandler0 = undefined; this._promise0 = undefined; this._receiver0 = undefined; this._resolveFromExecutor(executor); this._promiseCreated(); this._fireEvent("promiseCreated", this); }
...
*
* @returns {Promise<Collection>} A promise resolving to this {@link
* Collection collection}
*/
load: _promise2.default.method(function (relations, options) {
if (!(0, _lodash.isArray)(relations)) relations = [relations];
options = (0, _lodash.extend)({}, options, { shallow: true, withRelated: relations });
return new _eager2.default(this.models, this.toJSON(options), new this.model()).fetch
(options).return(this);
}),
/**
* @method Collection#create
* @description
*
* Convenience method to create a new {@link Model model} instance within a
...
default = function () { return Parent.apply(this, arguments); }
...
*
* @returns {Promise<Collection>} A promise resolving to this {@link
* Collection collection}
*/
load: _promise2.default.method(function (relations, options) {
if (!(0, _lodash.isArray)(relations)) relations = [relations];
options = (0, _lodash.extend)({}, options, { shallow: true, withRelated: relations });
return new _eager2.default(this.models, this.toJSON(options), new this.model()).fetch
(options).return(this);
}),
/**
* @method Collection#create
* @description
*
* Convenience method to create a new {@link Model model} instance within a
...
function Sync(syncing, options) { options = options || {}; this.query = syncing.query(); this.syncing = syncing.resetQuery(); this.options = options; if (options.debug) this.query.debug(); if (options.transacting) this.query.transacting(options.transacting); }
...
* @param {Object=} options
* @param {bool} [options.require=false] Trigger a {@link Collection.EmptyError} if no records are found.
* @param {string|string[]} [options.withRelated=[]] A relation, or list of relations, to be eager loaded as part of the `fetch
` operation.
* @returns {Promise<Collection>}
*/
fetch: _promise2.default.method(function (options) {
options = options ? (0, _lodash.clone)(options) : {};
return this.sync(options).select().bind(this).tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.EmptyError('EmptyResponse');
}
})
// Now, load all of the data onto the collection as necessary.
.tap(this._handleResponse)
...
count = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
*
* @example
*
* // select count(*) from shareholders where company_id = 1 and share > 0.1;
* Company.forge({id:1})
* .shareholders()
* .query('where', 'share', '>', '0.1')
* .count()
* .then(function(count) {
* assert(count === 3);
* });
*
* @param {string} [column='*']
* Specify a column to count - rows with null values in this column will be excluded.
* @param {Object=} options
...
del = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
* @event Model#destroying
* @param {Model} model The model firing the event.
* @param {Object} options Options object passed to {@link Model#save save}.
* @returns {Promise}
*/
return this.triggerThen('destroying', this, options);
}).then(function () {
return sync.del();
}).then(function (resp) {
if (options.require && resp === 0) {
throw new this.constructor.NoRowsDeletedError('No Rows Deleted');
}
this.clear();
/**
...
first = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
},
_doFetch: _promise2.default.method(function (attributes, options) {
options = options ? _lodash2.default.clone(options) : {};
// Run the `first` call on the `sync` object to fetch a single model.
return this.sync(options).first(attributes).bind(this)
// Jump the rest of the chain if the response doesn't exist...
.tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.NotFoundError('EmptyResponse');
}
})
...
insert = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
throw new Error('No rows were updated');
}
return numUpdated;
});
}
return this.triggerThen('creating', this, data, options).then(function () {
return builder.insert(data).then(function () {
collection.add(item);
});
});
}),
// Loads or prepares a pivot model based on the constraints and deals with
// pivot model changes by calling the appropriate Bookshelf Model API
...
function prefixFields(fields) { var tableName = this.syncing.tableName; var prefixed = {}; for (var key in fields) { prefixed[tableName + '.' + key] = fields[key]; } return prefixed; }
...
//
// NOTE: `_.omit` returns an empty object, even if attributes are null.
var whereAttributes = _lodash2.default.omitBy(attributes, _lodash2.default.isPlainObject);
if (!_lodash2.default.isEmpty(whereAttributes)) {
// Format and prefix attributes.
var formatted = this.prefixFields(model.format(whereAttributes));
query.where(formatted);
}
// Limit to a single result.
query.limit(1);
return this.select();
...
select = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
* @param {Object=} options
* @param {bool} [options.require=false] Trigger a {@link Collection.EmptyError} if no records are found.
* @param {string|string[]} [options.withRelated=[]] A relation, or list of relations, to be eager loaded as part of the `fetch
` operation.
* @returns {Promise<Collection>}
*/
fetch: _promise2.default.method(function (options) {
options = options ? (0, _lodash.clone)(options) : {};
return this.sync(options).select().bind(this).tap(function (response) {
if (!response || response.length === 0) {
throw new this.constructor.EmptyError('EmptyResponse');
}
})
// Now, load all of the data onto the collection as necessary.
.tap(this._handleResponse)
...
update = function () { var ret = new Promise(INTERNAL); ret._captureStackTrace(); ret._pushContext(); var value = tryCatch(fn).apply(this, arguments); var promiseCreated = ret._popContext(); debug.checkForgottenReturns( value, promiseCreated, "Promise.method", ret); ret._resolveFromSyncValue(value); return ret; }
...
var model = collection.get(data[relatedData.key('otherKey')]);
if (model) {
collection.remove(model);
}
});
}
if (method === 'update') {
return builder.where(data).update(item).then(function (numUpdated) {
if (options && options.require === true && numUpdated === 0) {
throw new Error('No rows were updated');
}
return numUpdated;
});
}
...