Socket
Socket
Sign inDemoInstall

bookshelf

Package Overview
Dependencies
Maintainers
1
Versions
87
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bookshelf - npm Package Compare versions

Comparing version 0.7.9 to 0.8.0

build/bookshelf.js

159

bookshelf.js

@@ -1,2 +0,2 @@

// Bookshelf.js 0.7.9
// Bookshelf.js 0.8.0
// ---------------

@@ -8,63 +8,65 @@

// http://bookshelfjs.org
var _ = require('lodash');
var inherits = require('inherits');
var semver = require('semver');
var helpers = require('./lib/helpers')
var Bookshelf = function() {
return Bookshelf.initialize.apply(null, arguments);
};
// We've supplemented `Events` with a `triggerThen`
// method to allow for asynchronous event handling via promises. We also
// mix this into the prototypes of the main objects in the library.
var Events = require('./lib/base/events');
// Constructor for a new `Bookshelf` object, it accepts
// an active `knex` instance and initializes the appropriate
// `Model` and `Collection` constructors for use in the current instance.
Bookshelf.initialize = function(knex) {
// All core modules required for the bookshelf instance.
var BookshelfModel = require('./lib/model');
var BookshelfCollection = require('./lib/collection');
var BookshelfRelation = require('./lib/relation');
var Errors = require('./lib/errors');
function Bookshelf(knex) {
var bookshelf = {
VERSION: '0.7.9'
VERSION: '0.8.0'
};
var _ = require('lodash');
var inherits = require('inherits');
var semver = require('semver');
var range = '>=0.6.10 <0.9.0';
if (!semver.satisfies(knex.VERSION, range)) {
throw new Error('The knex version is ' + knex.VERSION + ' which does not satisfy the Bookshelf\'s requirement ' + range);
}
// We've supplemented `Events` with a `triggerThen`
// method to allow for asynchronous event handling via promises. We also
// mix this into the prototypes of the main objects in the library.
var Events = require('./lib/base/events');
var Model = bookshelf.Model = BookshelfModel.extend({
_builder: builderFn,
// All core modules required for the bookshelf instance.
var BookshelfModel = require('./lib/model');
var BookshelfCollection = require('./lib/collection');
var BookshelfRelation = require('./lib/relation');
var Errors = require('./lib/errors');
// 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(type, Target, options) {
if (type !== 'morphTo' && !_.isFunction(Target)) {
throw new Error('A valid target model must be defined for the ' +
_.result(this, 'tableName') + ' ' + type + ' relation');
}
return new Relation(type, Target, options);
}
// If the knex isn't a `Knex` instance, we'll assume it's
// a compatible config object and pass it through to create a new instance.
// This behavior is now deprecated.
if (_.isPlainObject(knex)) {
console.warn('Initializing Bookshelf with a config object is deprecated, please pass an initialized knex.js instance.');
knex = require('knex')(knex);
}
}, {
var range = '>=0.6.10';
if (!semver.satisfies(knex.VERSION, range)) {
throw new Error('The knex version is ' + knex.VERSION + ' which does not satisfy the Bookshelf\'s requirement ' + range);
}
forge: forge,
var Model = bookshelf.Model = function() {
BookshelfModel.apply(this, arguments);
};
var Collection = bookshelf.Collection = function() {
BookshelfCollection.apply(this, arguments);
};
inherits(Model, BookshelfModel);
inherits(Collection, BookshelfCollection);
collection: function(rows, options) {
return new Collection((rows || []), _.extend({}, options, {model: this}));
},
_.extend(Model, BookshelfModel);
_.extend(Collection, BookshelfCollection);
fetchAll: function(options) {
return this.forge().fetchAll(options);
}
})
Model.prototype._builder =
Collection.prototype._builder = function(tableName) {
var builder = knex(tableName);
var instance = this;
return builder.on('query', function(data) {
instance.trigger('query', data);
});
};
var Collection = bookshelf.Collection = BookshelfCollection.extend({
_builder: builderFn
}, {
forge: forge
});

@@ -76,25 +78,7 @@ // The collection also references the correct `Model`, specified above, for creating

function Relation() {
BookshelfRelation.apply(this, arguments);
}
inherits(Relation, BookshelfRelation);
Relation.prototype.Model = Model;
Relation.prototype.Collection = Collection;
var Relation = BookshelfRelation.extend({
Model: Model,
Collection: Collection
})
// 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.
Model.prototype._relation = function(type, Target, options) {
if (type !== 'morphTo' && !_.isFunction(Target)) {
throw new Error('A valid target model must be defined for the ' +
_.result(this, 'tableName') + ' ' + type + ' relation');
}
return new Relation(type, Target, options);
};
// Shortcut for creating a new collection with the current collection.
Model.collection = function(rows, options) {
return new Collection((rows || []), _.extend({}, options, {model: this}));
};
// A `Bookshelf` instance may be used as a top-level pub-sub bus, as it mixes in the

@@ -117,3 +101,5 @@ // `Events` object. It also contains the version number, and a `Transaction` method

} catch (e) {
require(plugin)(this, options);
if (!process.browser) {
require(plugin)(this, options)
}
}

@@ -139,8 +125,16 @@ } else if (_.isArray(plugin)) {

// and more chainable.
Model.forge = Collection.forge = function() {
function forge() {
var inst = Object.create(this.prototype);
var obj = this.apply(inst, arguments);
return (Object(obj) === obj ? obj : inst);
};
}
function builderFn(tableName) {
var builder = knex(tableName);
var instance = this;
return builder.on('query', function(data) {
instance.trigger('query', data);
});
}
// Attach `where`, `query`, and `fetchAll` as static methods.

@@ -154,10 +148,15 @@ ['where', 'query'].forEach(function(method) {

});
Model.fetchAll = function(options) { return this.forge().fetchAll(options); };
return bookshelf;
}
Model.extend = Collection.extend = require('simple-extend');
return bookshelf;
// Constructor for a new `Bookshelf` object, it accepts
// an active `knex` instance and initializes the appropriate
// `Model` and `Collection` constructors for use in the current instance.
Bookshelf.initialize = function(knex) {
helpers.warn("Bookshelf.initialize is deprecated, pass knex directly: require('bookshelf')(knex)")
return new Bookshelf(knex)
};
// Finally, export `Bookshelf` to the world.
module.exports = Bookshelf;
module.exports = Bookshelf;
## How to contribute to Bookshelf.js
* Before sending a pull request for a feature or bug fix, be sure to have
[tests](https://github.com/tgriesser/bookshelf/tree/master/test).
[tests](https://github.com/tgriesser/bookshelf/tree/master/test).

@@ -12,1 +12,11 @@ * Use the same coding style as the rest of the

* All pull requests should be made to the `master` branch.
### Running the Tests
The test suite requires you to create a [MySQL](https://www.mysql.com/) and [Postgres](http://www.postgresql.org/) database named `bookshelf_test`.
Once you have done that, you can run the tests:
```sh
$ npm test
```

@@ -6,3 +6,3 @@ // Base Collection

var _ = require('lodash');
var Backbone = require('backbone');
var inherits = require('inherits');

@@ -15,8 +15,9 @@ // All components that need to be referenced in this scope.

var array = [];
var push = array.push;
var slice = array.slice;
var splice = array.splice;
var CollectionBase = function(models, options) {
function CollectionBase(models, options) {
if (options) _.extend(this, _.pick(options, collectionProps));
this._reset();
this.initialize.apply(this, arguments);
if (!_.isFunction(this.model)) {

@@ -26,143 +27,352 @@ throw new Error('A valid `model` constructor must be defined for all collections.');

if (models) this.reset(models, _.extend({silent: true}, options));
};
}
inherits(CollectionBase, Events);
// List of attributes attached directly from the constructor's options object.
var collectionProps = ['model', 'comparator'];
var collectionProps = ['model', 'Model', 'comparator'];
// A list of properties that are omitted from the `Backbone.Model.prototype`, to create
// a generic collection base.
var collectionOmitted = ['model', 'fetch', 'url', 'sync', 'create'];
// Copied over from Backbone.
var setOptions = {add: true, remove: true, merge: true};
var addOptions = {add: true, remove: false};
_.extend(CollectionBase.prototype, _.omit(Backbone.Collection.prototype, collectionOmitted), Events, {
// The `tableName` on the associated Model, used in relation building.
CollectionBase.prototype.tableName = function() {
return _.result(this.model.prototype, 'tableName');
};
// The `tableName` on the associated Model, used in relation building.
tableName: function() {
return _.result(this.model.prototype, 'tableName');
},
// The `idAttribute` on the associated Model, used in relation building.
CollectionBase.prototype.idAttribute = function() {
return this.model.prototype.idAttribute;
};
// The `idAttribute` on the associated Model, used in relation building.
idAttribute: function() {
return this.model.prototype.idAttribute;
},
CollectionBase.prototype.toString = function() {
return '[Object Collection]';
};
// A simplified version of Backbone's `Collection#set` method,
// removing the comparator, and getting rid of the temporary model creation,
// since there's *no way* we'll be getting the data in an inconsistent
// form from the database.
set: function(models, options) {
options = _.defaults({}, options, setOptions);
if (options.parse) models = this.parse(models, options);
if (!_.isArray(models)) models = models ? [models] : [];
var i, l, id, model, attrs, existing;
var at = options.at;
var targetModel = this.model;
var toAdd = [], toRemove = [], modelMap = {};
var add = options.add, merge = options.merge, remove = options.remove;
var order = add && remove ? [] : false;
// The JSON representation of a Collection is an array of the
// models' attributes.
CollectionBase.prototype.toJSON = function(options) {
return this.map(function(model){ return model.toJSON(options); });
};
// Turn bare objects into model references, and prevent invalid models
// from being added.
for (i = 0, l = models.length; i < l; i++) {
attrs = models[i];
if (attrs instanceof ModelBase) {
id = model = attrs;
} else {
id = attrs[targetModel.prototype.idAttribute];
// A simplified version of Backbone's `Collection#set` method,
// removing the comparator, and getting rid of the temporary model creation,
// since there's *no way* we'll be getting the data in an inconsistent
// form from the database.
CollectionBase.prototype.set = function(models, options) {
options = _.defaults({}, options, setOptions);
if (options.parse) models = this.parse(models, options);
if (!_.isArray(models)) models = models ? [models] : [];
var i, l, id, model, attrs, existing;
var at = options.at;
var targetModel = this.model;
var toAdd = [], toRemove = [], modelMap = {};
var add = options.add, merge = options.merge, remove = options.remove;
var order = add && remove ? [] : false;
// Turn bare objects into model references, and prevent invalid models
// from being added.
for (i = 0, l = models.length; i < l; i++) {
attrs = models[i];
if (attrs instanceof ModelBase) {
id = model = attrs;
} else {
id = attrs[targetModel.prototype.idAttribute];
}
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(id)) {
if (remove) {
modelMap[existing.cid] = true;
}
if (merge) {
attrs = attrs === model ? model.attributes : attrs;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
}
// If a duplicate is found, prevent it from being added and
// optionally merge it into the existing model.
if (existing = this.get(id)) {
if (remove) {
modelMap[existing.cid] = true;
}
if (merge) {
attrs = attrs === model ? model.attributes : attrs;
if (options.parse) attrs = existing.parse(attrs, options);
existing.set(attrs, options);
}
// This is a new model, push it to the `toAdd` list.
} else if (add) {
if (!(model = this._prepareModel(attrs, options))) continue;
toAdd.push(model);
// This is a new model, push it to the `toAdd` list.
} else if (add) {
if (!(model = this._prepareModel(attrs, options))) continue;
toAdd.push(model);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
if (order) order.push(existing || model);
// Listen to added models' events, and index models for lookup by
// `id` and by `cid`.
model.on('all', this._onModelEvent, this);
this._byId[model.cid] = model;
if (model.id != null) this._byId[model.id] = model;
}
if (order) order.push(existing || model);
}
// Remove nonexistent models if appropriate.
if (remove) {
for (i = 0, l = this.length; i < l; ++i) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
}
if (toRemove.length) this.remove(toRemove, options);
// Remove nonexistent models if appropriate.
if (remove) {
for (i = 0, l = this.length; i < l; ++i) {
if (!modelMap[(model = this.models[i]).cid]) toRemove.push(model);
}
if (toRemove.length) this.remove(toRemove, options);
}
// See if sorting is needed, update `length` and splice in new models.
if (toAdd.length || (order && order.length)) {
this.length += toAdd.length;
if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));
// See if sorting is needed, update `length` and splice in new models.
if (toAdd.length || (order && order.length)) {
this.length += toAdd.length;
if (at != null) {
splice.apply(this.models, [at, 0].concat(toAdd));
} else {
if (order) {
this.models.length = 0;
} else {
if (order) {
this.models.length = 0;
} else {
order = toAdd;
}
for (i = 0, l = order.length; i < l; ++i) {
this.models.push(order[i]);
}
order = toAdd;
}
for (i = 0, l = order.length; i < l; ++i) {
this.models.push(order[i]);
}
}
}
if (options.silent) return this;
if (options.silent) return this;
// Trigger `add` events.
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
// Trigger `add` events.
for (i = 0, l = toAdd.length; i < l; i++) {
(model = toAdd[i]).trigger('add', model, this, options);
}
return this;
};
// Prepare a model or hash of attributes to be added to this collection.
CollectionBase.prototype._prepareModel = function(attrs, options) {
if (attrs instanceof ModelBase) return attrs;
return new this.model(attrs, options);
};
// Run "Promise.map" over the models
CollectionBase.prototype.mapThen = function(iterator, context) {
return Promise.bind(context).thenReturn(this.models).map(iterator);
};
// Convenience method for invoke, returning a `Promise.all` promise.
CollectionBase.prototype.invokeThen = function() {
return Promise.all(this.invoke.apply(this, arguments));
};
// Run "reduce" over the models in the collection.
CollectionBase.prototype.reduceThen = function(iterator, initialValue, context) {
return Promise.bind(context).thenReturn(this.models).reduce(iterator, initialValue).bind();
};
CollectionBase.prototype.fetch = function() {
return Promise.rejected('The fetch method has not been implemented');
};
// Add a model, or list of models to the set.
CollectionBase.prototype.add = function(models, options) {
return this.set(models, _.extend({merge: false}, options, addOptions));
};
// Remove a model, or a list of models from the set.
CollectionBase.prototype.remove = function(models, options) {
var singular = !_.isArray(models);
models = singular ? [models] : _.clone(models);
options || (options = {});
var i, l, index, model;
for (i = 0, l = models.length; i < l; i++) {
model = models[i] = this.get(models[i]);
if (!model) continue;
delete this._byId[model.id];
delete this._byId[model.cid];
index = this.indexOf(model);
this.models.splice(index, 1);
this.length--;
if (!options.silent) {
options.index = index;
model.trigger('remove', model, this, options);
}
return this;
},
this._removeReference(model);
}
return singular ? models[0] : models;
};
// Prepare a model or hash of attributes to be added to this collection.
_prepareModel: function(attrs, options) {
if (attrs instanceof ModelBase) return attrs;
return new this.model(attrs, options);
},
// When you have more items than you want to add or remove individually,
// you can reset the entire set with a new list of models, without firing
// any granular `add` or `remove` events. Fires `reset` when finished.
// Useful for bulk operations and optimizations.
CollectionBase.prototype.reset = function(models, options) {
options = options || {};
for (var i = 0, l = this.models.length; i < l; i++) {
this._removeReference(this.models[i]);
}
options.previousModels = this.models;
this._reset();
models = this.add(models, _.extend({silent: true}, options));
if (!options.silent) this.trigger('reset', this, options);
return models;
};
// Run "Promise.map" over the models
mapThen: function(iterator, context) {
return Promise.bind(context).thenReturn(this.models).map(iterator);
},
// Add a model to the end of the collection.
CollectionBase.prototype.push = function(model, options) {
return this.add(model, _.extend({at: this.length}, options));
};
// Convenience method for invoke, returning a `Promise.all` promise.
invokeThen: function() {
return Promise.all(this.invoke.apply(this, arguments));
},
// Remove a model from the end of the collection.
CollectionBase.prototype.pop = function(options) {
var model = this.at(this.length - 1);
this.remove(model, options);
return model;
};
// Run "reduce" over the models in the collection.
reduceThen: function(iterator, initialValue, context) {
return Promise.bind(context).thenReturn(this.models).reduce(iterator, initialValue).bind();
},
// Add a model to the beginning of the collection.
CollectionBase.prototype.unshift = function(model, options) {
return this.add(model, _.extend({at: 0}, options));
};
fetch: function() {
return Promise.rejected('The fetch method has not been implemented');
// Remove a model from the beginning of the collection.
CollectionBase.prototype.shift = function(options) {
var model = this.at(0);
this.remove(model, options);
return model;
};
// Slice out a sub-array of models from the collection.
CollectionBase.prototype.slice = function() {
return slice.apply(this.models, arguments);
};
// Get a model from the set by id.
CollectionBase.prototype.get = function(obj) {
if (obj == null) return void 0;
return this._byId[obj.id] || this._byId[obj.cid] || this._byId[obj];
};
// Get the model at the given index.
CollectionBase.prototype.at = function(index) {
return this.models[index];
};
// Return models with matching attributes. Useful for simple cases of
// `filter`.
CollectionBase.prototype.where = function(attrs, first) {
if (_.isEmpty(attrs)) return first ? void 0 : [];
return this[first ? 'find' : 'filter'](function(model) {
for (var key in attrs) {
if (attrs[key] !== model.get(key)) return false;
}
return true;
});
};
// Return the first model with matching attributes. Useful for simple cases
// of `find`.
CollectionBase.prototype.findWhere = function(attrs) {
return this.where(attrs, true);
};
// Force the collection to re-sort itself, based on a comporator defined on the model.
CollectionBase.prototype.sort = function(options) {
if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
options || (options = {});
// Run sort based on type of `comparator`.
if (_.isString(this.comparator) || this.comparator.length === 1) {
this.models = this.sortBy(this.comparator, this);
} else {
this.models.sort(_.bind(this.comparator, this));
}
if (!options.silent) this.trigger('sort', this, options);
return this;
};
// Pluck an attribute from each model in the collection.
CollectionBase.prototype.pluck = function(attr) {
return this.invoke('get', attr);
};
// Create a new instance of a model in this collection. Add the model to the
// collection immediately, unless `wait: true` is passed, in which case we
// wait for the server to agree.
CollectionBase.prototype.create = function(model, options) {
options = options ? _.clone(options) : {};
if (!(model = this._prepareModel(model, options))) return false;
if (!options.wait) this.add(model, options);
var collection = this;
var success = options.success;
options.success = function(model, resp, options) {
if (options.wait) collection.add(model, options);
if (success) success(model, resp, options);
};
model.save(null, options);
return model;
};
// **parse** converts a response into a list of models to be added to the
// collection. The default implementation is just to pass it through.
CollectionBase.prototype.parse = function(resp, options) {
return resp;
};
// Create a new collection with an identical list of models as this one.
CollectionBase.prototype.clone = function() {
return new this.constructor(this.models);
};
// Private method to reset all internal state. Called when the collection
// is first initialized or reset.
CollectionBase.prototype._reset = function() {
this.length = 0;
this.models = [];
this._byId = Object.create(null);
};
// Internal method to sever a model's ties to a collection.
CollectionBase.prototype._removeReference = function(model) {
// TODO: Sever from the internal model cache.
};
// Internal method called every time a model in the set fires an event.
// Sets need to update their indexes when models change ids. All other
// events simply proxy through. "add" and "remove" events that originate
// in other collections are ignored.
CollectionBase.prototype._onModelEvent = function(event, model, collection, options) {
// TOOD: See if we need anything here.
};
// Underscore methods that we want to implement on the Collection.
// 90% of the core usefulness of Backbone Collections is actually implemented
// right here:
var methods = ['forEach', 'each', 'map', 'collect', 'reduce', 'foldl',
'inject', 'reduceRight', 'foldr', 'find', 'detect', 'filter', 'select',
'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke',
'max', 'min', 'toArray', 'size', 'first', 'head', 'take', 'initial', 'rest',
'tail', 'drop', 'last', 'without', 'difference', 'indexOf', 'shuffle',
'lastIndexOf', 'isEmpty', 'chain'];
// Mix in each Underscore method as a proxy to `Collection#models`.
_.each(methods, function(method) {
CollectionBase.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.models);
return _[method].apply(_, args);
};
});
// Underscore methods that take a property name as an argument.
var attributeMethods = ['groupBy', 'countBy', 'sortBy'];
// Use attributes instead of properties.
_.each(attributeMethods, function(method) {
CollectionBase.prototype[method] = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
return _[method](this.models, iterator, context);
};
});
// List of attributes attached directly from the `options` passed to the constructor.
var modelProps = ['tableName', 'hasTimestamps'];
CollectionBase.extend = require('simple-extend');
CollectionBase.extend = require('../extend');
module.exports = CollectionBase;

@@ -10,3 +10,2 @@ // Eager Base

var _ = require('lodash');
var Backbone = require('backbone');
var Promise = require('./promise');

@@ -76,3 +75,3 @@

// returning the original response when these syncs & pairings are complete.
return Promise.all(pendingDeferred).yield(this.parentResponse);
return Promise.all(pendingDeferred).return(this.parentResponse);
}),

@@ -79,0 +78,0 @@

// Events
// ---------------
var Promise = require('./promise');
var Backbone = require('backbone');
var triggerThen = require('trigger-then');
var _ = require('lodash');
var Promise = require('./promise');
var inherits = require('inherits');
var EventEmitter = require('events').EventEmitter;
var _ = require('lodash');
Backbone.Events.once = function(name, callback, context) {
//if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
var self = this;
var once = _.once(function() {
self.off(name, once);
return callback.apply(this, arguments);
});
once._callback = callback;
return this.on(name, once, context);
function Events() {
EventEmitter.apply(this, arguments);
}
inherits(Events, EventEmitter);
// Regular expression used to split event strings.
var eventSplitter = /\s+/;
Events.prototype.on = function(name, handler) {
// Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
this.on(names[i], handler);
}
return this;
}
return EventEmitter.prototype.on.apply(this, arguments);
};
// Mixin the `triggerThen` function into all relevant Backbone objects,
// so we can have event driven async validations, functions, etc.
triggerThen(Backbone, Promise);
// Add "off", "trigger", and "" method, for parity with Backbone.Events
Events.prototype.off = function(event, listener) {
if (arguments.length === 0) {
return this.removeAllListeners();
}
if (arguments.length === 1) {
return this.removeAllListeners(event);
}
return this.removeListener(event, listener);
};
Events.prototype.trigger = function(name) {
// Handle space separated event names.
if (eventSplitter.test(name)) {
var len = arguments.length;
var rest = new Array(len - 1);
for (i = 1; i < len; i++) rest[i - 1] = arguments[i];
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
EventEmitter.prototype.emit.apply(this, [names[i]].concat(rest));
}
return this;
}
EventEmitter.prototype.emit.apply(this, arguments);
return this;
};
module.exports = Backbone.Events;
Events.prototype.triggerThen = function(name) {
var i, l, rest, listeners = [];
// Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (i = 0, l = names.length; i < l; i++) {
listeners = listeners.concat(this.listeners(names[i]));
}
} else {
listeners = this.listeners(name);
}
var len = arguments.length;
switch (len) {
case 1: rest = []; break;
case 2: rest = [arguments[1]]; break;
case 3: rest = [arguments[1], arguments[2]]; break;
default: rest = new Array(len - 1); for (i = 1; i < len; i++) rest[i - 1] = arguments[i];
}
var events = this
return Promise.try(function() {
var pending = [];
for (i = 0, l = listeners.length; i < l; i++) {
pending[i] = listeners[i].apply(events, rest);
}
return Promise.all(pending);
})
};
Events.prototype.emitThen = Events.prototype.triggerThen;
Events.prototype.once = function(name, callback, context) {
var self = this;
var once = _.once(function() {
self.off(name, once);
return callback.apply(this, arguments);
});
once._callback = callback;
return this.on(name, once, context);
};
module.exports = Events;
// Base Model
// ---------------
var _ = require('lodash');
var Backbone = require('backbone');
var inherits = require('inherits');
var Events = require('./events');
var Promise = require('./promise');
var Errors = require('../errors');
var slice = Array.prototype.slice
// A list of properties that are omitted from the `Backbone.Model.prototype`, to create
// a generic model base.
var modelOmitted = [
'changedAttributes', 'isValid', 'validationError',
'save', 'sync', 'fetch', 'destroy', 'url',
'urlRoot', '_validate'
];
// List of attributes attached directly from the `options` passed to the constructor.

@@ -21,7 +15,6 @@ var modelProps = ['tableName', 'hasTimestamps'];

// The "ModelBase" is similar to the 'Active Model' in Rails,
// it defines a standard interface from which other objects may
// inherit.
var ModelBase = function(attributes, options) {
// it defines a standard interface from which other objects may inherit.
function ModelBase(attributes, options) {
var attrs = attributes || {};
options || (options = {});
options = options || {};
this.attributes = Object.create(null);

@@ -37,154 +30,198 @@ this._reset();

this.initialize.apply(this, arguments);
}
inherits(ModelBase, Events);
ModelBase.prototype.initialize = function() {};
// The default value for the "id" attribute.
ModelBase.prototype.idAttribute = 'id';
// Get the value of an attribute.
ModelBase.prototype.get = function(attr) {
return this.attributes[attr];
};
_.extend(ModelBase.prototype, _.omit(Backbone.Model.prototype, modelOmitted), Events, {
// Set a property.
ModelBase.prototype.set = function(key, val, options) {
if (key == null) return this;
var attrs;
// Similar to the standard `Backbone` set method, but without individual
// change events, and adding different meaning to `changed` and `previousAttributes`
// defined as the last "sync"'ed state of the model.
set: function(key, val, options) {
if (key == null) return this;
var attrs;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options = _.clone(options) || {};
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
// Extract attributes and options.
var hasChanged = false;
var unset = options.unset;
var current = this.attributes;
var prev = this._previousAttributes;
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
// For each `set` attribute, update or delete the current value.
for (var attr in attrs) {
val = attrs[attr];
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
if (!_.isEqual(current[attr], val)) hasChanged = true;
} else {
(attrs = {})[key] = val;
delete this.changed[attr];
}
options || (options = {});
unset ? delete current[attr] : current[attr] = val;
}
return this;
};
// Extract attributes and options.
var hasChanged = false;
var unset = options.unset;
var current = this.attributes;
var prev = this._previousAttributes;
// A model is new if it has never been persisted, which we assume if it lacks an id.
ModelBase.prototype.isNew = function() {
return this.id == null;
};
// Check for changes of `id`.
if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
// For each `set` attribute, update or delete the current value.
for (var attr in attrs) {
val = attrs[attr];
if (!_.isEqual(prev[attr], val)) {
this.changed[attr] = val;
if (!_.isEqual(current[attr], val)) hasChanged = true;
} else {
delete this.changed[attr];
}
unset ? delete current[attr] : current[attr] = val;
// Returns an object containing a shallow copy of the model attributes,
// along with the `toJSON` value of any relations,
// unless `{shallow: true}` is passed in the `options`.
ModelBase.prototype.toJSON = function(options) {
var attrs = _.clone(this.attributes);
if (options && options.shallow) return attrs;
var relations = this.relations;
for (var key in relations) {
var relation = relations[key];
attrs[key] = relation.toJSON ? relation.toJSON(options) : relation;
}
if (this.pivot) {
var pivot = this.pivot.attributes;
for (key in pivot) {
attrs['_pivot_' + key] = pivot[key];
}
}
return attrs;
};
if (hasChanged && !options.silent) this.trigger('change', this, options);
return this;
},
// Returns the string representation of the object.
ModelBase.prototype.toString = function() {
return '[Object Model]';
};
// Returns an object containing a shallow copy of the model attributes,
// along with the `toJSON` value of any relations,
// unless `{shallow: true}` is passed in the `options`.
// Also includes _pivot_ keys for relations unless `{omitPivot: true}`
// is passed in `options`.
toJSON: function(options) {
var attrs = _.extend({}, this.attributes);
if (options && options.shallow) return attrs;
var relations = this.relations;
for (var key in relations) {
var relation = relations[key];
attrs[key] = relation.toJSON ? relation.toJSON(options) : relation;
}
if (options && options.omitPivot) return attrs;
if (this.pivot) {
var pivot = this.pivot.attributes;
for (key in pivot) {
attrs['_pivot_' + key] = pivot[key];
}
}
return attrs;
},
// Get the HTML-escaped value of an attribute.
ModelBase.prototype.escape = function(key) {
return _.escape(this.get(key));
};
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
parse: function(resp, options) {
return resp;
},
// Returns `true` if the attribute contains a value that is not null
// or undefined.
ModelBase.prototype.has = function(attr) {
return this.get(attr) != null;
};
// **format** converts a model into the values that should be saved into
// the database table. The default implementation is just to pass the data along.
format: function(attrs, options) {
return attrs;
},
// **parse** converts a response into the hash of attributes to be `set` on
// the model. The default implementation is just to pass the response along.
ModelBase.prototype.parse = function(resp, options) {
return resp;
};
// Returns the related item, or creates a new
// related item by creating a new model or collection.
related: function(name) {
return this.relations[name] || (this[name] ? this.relations[name] = this[name]() : void 0);
},
// Remove an attribute from the model, firing `"change"`. `unset` is a noop
// if the attribute doesn't exist.
ModelBase.prototype.unset = function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
};
// Create a new model with identical attributes to this one,
// including any relations on the current model.
clone: function() {
var model = new this.constructor(this.attributes);
var relations = this.relations;
for (var key in relations) {
model.relations[key] = relations[key].clone();
}
model._previousAttributes = _.clone(this._previousAttributes);
model.changed = _.clone(this.changed);
return model;
},
// Clear all attributes on the model, firing `"change"`.
ModelBase.prototype.clear = function(options) {
var attrs = {};
for (var key in this.attributes) attrs[key] = void 0;
return this.set(attrs, _.extend({}, options, {unset: true}));
};
// Sets the timestamps before saving the model.
timestamp: function(options) {
var d = new Date();
var keys = (_.isArray(this.hasTimestamps) ? this.hasTimestamps : ['created_at', 'updated_at']);
var vals = {};
if (keys[0]) {
if (this.isNew(options)) {
if (!options || options.method !== 'update') {
vals[keys[0]] = d;
}
}
else if (options && options.method === 'insert') {
vals[keys[0]] = d;
}
}
if (keys[1]) vals[keys[1]] = d;
return vals;
},
// **format** converts a model into the values that should be saved into
// the database table. The default implementation is just to pass the data along.
ModelBase.prototype.format = function(attrs, options) {
return attrs;
};
// Called after a `sync` action (save, fetch, delete) -
// resets the `_previousAttributes` and `changed` hash for the model.
_reset: function() {
this._previousAttributes = _.extend(Object.create(null), this.attributes);
this.changed = Object.create(null);
return this;
},
// Returns the related item, or creates a new
// related item by creating a new model or collection.
ModelBase.prototype.related = function(name) {
return this.relations[name] || (this[name] ? this.relations[name] = this[name]() : void 0);
};
fetch: function() {},
// Create a new model with identical attributes to this one,
// including any relations on the current model.
ModelBase.prototype.clone = function(options) {
var model = new this.constructor(this.attributes);
var relations = this.relations;
for (var key in relations) {
model.relations[key] = relations[key].clone();
}
model._previousAttributes = _.clone(this._previousAttributes);
model.changed = setProps(Object.create(null), this.changed);
return model;
};
save: function() {},
// Sets the timestamps before saving the model.
ModelBase.prototype.timestamp = function(options) {
var d = new Date();
var keys = (_.isArray(this.hasTimestamps) ? this.hasTimestamps : ['created_at', 'updated_at']);
var vals = {};
if (keys[1]) vals[keys[1]] = d;
if (this.isNew(options) && keys[0] && (!options || options.method !== 'update')) vals[keys[0]] = d;
return vals;
};
// Destroy a model, calling a "delete" based on its `idAttribute`.
// A "destroying" and "destroyed" are triggered on the model before
// and after the model is destroyed, respectively. If an error is thrown
// during the "destroying" event, the model will not be destroyed.
destroy: Promise.method(function(options) {
options = options ? _.clone(options) : {};
var sync = this.sync(options);
options.query = sync.query;
return Promise.bind(this).then(function() {
return this.triggerThen('destroying', this, options);
}).then(function() {
return sync.del();
}).then(function(resp) {
this.clear();
return this.triggerThen('destroyed', this, resp, options);
}).then(this._reset);
})
// Determine if the model has changed since the last `"change"` event.
// If you specify an attribute name, determine if that attribute has changed.
ModelBase.prototype.hasChanged = function(attr) {
if (attr == null) return !_.isEmpty(this.changed);
return _.has(this.changed, attr);
};
// Get the previous value of an attribute, recorded at the time the last
// `"change"` event was fired.
ModelBase.prototype.previous = function(attr) {
if (attr == null || !this._previousAttributes) return null;
return this._previousAttributes[attr];
};
// Get all of the attributes of the model at the time of the previous
// `"change"` event.
ModelBase.prototype.previousAttributes = function() {
return _.clone(this._previousAttributes);
};
// Resets the `_previousAttributes` and `changed` hash for the model.
// Typically called after a `sync` action (save, fetch, delete) -
ModelBase.prototype._reset = function() {
this._previousAttributes = _.clone(this.attributes);
this.changed = Object.create(null);
return this;
};
// Set the changed properties on the object.
function setProps(obj, hash) {
var i = -1, keys = Object.keys(hash);
while (++i < hash.length) {
var key = hash[i]
obj[key] = hash[key];
}
}
// "_" methods that we want to implement on the Model.
var modelMethods = ['keys', 'values', 'pairs', 'invert', 'pick', 'omit'];
// Mix in each "_" method as a proxy to `Model#attributes`.
_.each(modelMethods, function(method) {
ModelBase.prototype[method] = function() {
var args = slice.call(arguments);
args.unshift(this.attributes);
return _[method].apply(_, args);
};
});
ModelBase.extend = require('simple-extend');
ModelBase.extend = require('../extend');
module.exports = ModelBase;
var Promise = require('bluebird/js/main/promise')();
var helpers = require('../helpers')
Promise.prototype.yield = Promise.prototype.return;
Promise.prototype.ensure = Promise.prototype.lastly;
Promise.prototype.otherwise = Promise.prototype.caught;
Promise.prototype.exec = Promise.prototype.nodeify;
Promise.prototype.yield = function() {
helpers.deprecate('.yield', '.return')
return this.return.apply(this, arguments);
}
Promise.prototype.ensure = function() {
helpers.deprecate('.ensure', '.finally')
return this.finally.apply(this, arguments);
}
Promise.prototype.otherwise = function() {
helpers.deprecate('.otherwise', '.catch')
return this.catch.apply(this, arguments);
}
Promise.prototype.exec = function() {
helpers.deprecate('bookshelf.exec', 'bookshelf.asCallback')
return this.nodeify.apply(this, arguments);
};
Promise.resolve = Promise.fulfilled;
Promise.reject = Promise.rejected;
module.exports = Promise;

@@ -5,3 +5,2 @@ // Base Relation

var _ = require('lodash');
var Backbone = require('backbone');
var CollectionBase = require('./collection');

@@ -42,4 +41,4 @@

RelationBase.extend = Backbone.Model.extend;
RelationBase.extend = require('../extend');
module.exports = RelationBase;
// Collection
// ---------------
var _ = require('lodash');
var inherits = require('inherits');

@@ -13,12 +12,6 @@ var Sync = require('./sync');

var Promise = require('./base/promise');
var createError = require('create-error');
function BookshelfCollection() {
CollectionBase.apply(this, arguments);
}
inherits(BookshelfCollection, CollectionBase);
var BookshelfCollection = CollectionBase.extend({
BookshelfCollection.EmptyError = Errors.EmptyError;
_.extend(BookshelfCollection.prototype, {
// Used to define passthrough relationships - `hasOne`, `hasMany`,

@@ -39,3 +32,3 @@ // `belongsTo` or `belongsToMany`, "through" a `Interim` model or collection.

if (!response || response.length === 0) {
if (options.require) throw new Errors.EmptyError('EmptyResponse');
if (options.require) throw new this.constructor.EmptyError('EmptyResponse');
return Promise.reject(null);

@@ -145,4 +138,12 @@ }

}, {
extended: function(child) {
child.EmptyError = createError(this.EmptyError)
}
});
BookshelfCollection.EmptyError = Errors.EmptyError
module.exports = BookshelfCollection;

@@ -10,4 +10,10 @@ var createError = require('create-error');

// collection.fetch
EmptyError: createError('EmptyError')
EmptyError: createError('EmptyError'),
};
// Thrown when an update affects no rows and {require: true} is passed in model.save.
NoRowsUpdatedError: createError('NoRowsUpdatedError'),
// Thrown when a delete affects no rows and {require: true} is passed in model.destroy.
NoRowsDeletedError: createError('NoRowsDeletedError')
};
// Helpers
// ---------------
var _ = require('lodash');
var _ = require('lodash');
var chalk = require('chalk')
module.exports = {
var helpers = {

@@ -49,4 +50,19 @@ // Sets the constraints necessary during a `model.save` call.

return obj;
},
error: function(msg) {
console.log(chalk.red(msg))
},
warn: function(msg) {
console.log(chalk.yellow(msg))
},
deprecate: function(a, b) {
helpers.warn(a + ' has been deprecated, please use ' + b + ' instead')
}
};
module.exports = helpers
// Model
// ---------------
var _ = require('lodash');
var inherits = require('inherits');
var createError = require('create-error')

@@ -14,11 +14,4 @@ var Sync = require('./sync');

function BookshelfModel() {
ModelBase.apply(this, arguments);
}
inherits(BookshelfModel, ModelBase);
var BookshelfModel = ModelBase.extend({
BookshelfModel.NotFoundError = Errors.NotFoundError;
_.extend(BookshelfModel.prototype, {
// The `hasOne` relation specifies that this table has exactly one of another type of object,

@@ -101,3 +94,3 @@ // specified by a foreign key in the other table. The foreign key is assumed to be the singular of this

if (!response || response.length === 0) {
if (options.require) throw new Errors.NotFoundError('EmptyResponse');
if (options.require) throw new this.constructor.NotFoundError('EmptyResponse');
return Promise.reject(null);

@@ -225,3 +218,5 @@ }

} else if (method === 'update' && resp === 0) {
throw new Error('No rows were affected in the update, did you mean to pass the {method: "insert"} option?');
if (options.require !== false) {
throw new this.constructor.NoRowsUpdatedError('No Rows Updated');
}
}

@@ -237,3 +232,2 @@

});
})

@@ -243,2 +237,23 @@ .return(this);

// Destroy a model, calling a "delete" based on its `idAttribute`.
// A "destroying" and "destroyed" are triggered on the model before
// and after the model is destroyed, respectively. If an error is thrown
// during the "destroying" event, the model will not be destroyed.
destroy: Promise.method(function(options) {
options = options ? _.clone(options) : {};
var sync = this.sync(options);
options.query = sync.query;
return Promise.bind(this).then(function() {
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();
return this.triggerThen('destroyed', this, resp, options);
}).then(this._reset);
}),
// Reset the query builder, called internally

@@ -295,4 +310,16 @@ // each time a query is run.

}, {
extended: function(child) {
child.NotFoundError = createError(this.NotFoundError)
child.NoRowsUpdatedError = createError(this.NoRowsUpdatedError)
child.NoRowsDeletedError = createError(this.NoRowsDeletedError)
}
});
BookshelfModel.NotFoundError = Errors.NotFoundError,
BookshelfModel.NoRowsUpdatedError = Errors.NoRowsUpdatedError,
BookshelfModel.NoRowsDeletedError = Errors.NoRowsDeletedError
module.exports = BookshelfModel;

@@ -14,9 +14,4 @@ // Relation

function BookshelfRelation() {
RelationBase.apply(this, arguments);
}
inherits(BookshelfRelation, RelationBase);
var BookshelfRelation = RelationBase.extend({
_.extend(BookshelfRelation.prototype, {
// Assembles the new model or collection we're creating an instance of,

@@ -137,3 +132,3 @@ // gathering any relevant primitives from the parent object,

if (this.isJoined()) this.joinColumns(knex);
// If this is a single relation and we're not eager loading,

@@ -383,2 +378,4 @@ // limit the query to a single item.

return this.triggerThen('attached', this, resp, options);
}).then(function() {
return this;
});

@@ -429,21 +426,17 @@ },

}
return Promise.all(pending).yield(this);
return Promise.all(pending).return(this);
}),
// Handles setting the appropriate constraints and shelling out
// to either the `insert` or `delete` call for the current model,
// returning a promise.
// Handles preparing the appropriate constraints and then delegates
// the database interaction to _processPlainPivot for non-.through()
// pivot definitions, or _processModelPivot for .through() models.
// Returns a promise.
_processPivot: Promise.method(function(method, item, options) {
var data = {};
var relatedData = this.relatedData;
var relatedData = this.relatedData
, args = Array.prototype.slice.call(arguments)
, fks = {}
, data = {};
// Grab the `knex` query builder for the current model, and
// check if we have any additional constraints for the query.
var builder = this._builder(relatedData.joinTable());
if (options && options.query) {
Helpers.query.call(null, {_knex: builder}, [options.query]);
}
fks[relatedData.key('foreignKey')] = relatedData.parentFk;
data[relatedData.key('foreignKey')] = relatedData.parentFk;
// If the item is an object, it's either a model

@@ -454,3 +447,3 @@ // that we're looking to attach to this model, or

if (item instanceof ModelBase) {
data[relatedData.key('otherKey')] = item.id;
fks[relatedData.key('otherKey')] = item.id;
} else if (method !== 'update') {

@@ -460,5 +453,27 @@ _.extend(data, item);

} else if (item) {
data[relatedData.key('otherKey')] = item;
fks[relatedData.key('otherKey')] = item;
}
args.push(_.extend(data, fks), fks);
if (this.relatedData.throughTarget) {
return this._processModelPivot.apply(this, args);
}
return this._processPlainPivot.apply(this, args);
}),
// Applies constraints to the knex builder and handles shelling out
// to either the `insert` or `delete` call for the current model,
// returning a promise.
_processPlainPivot: Promise.method(function(method, item, options, data, fks) {
var relatedData = this.relatedData;
// Grab the `knex` query builder for the current model, and
// check if we have any additional constraints for the query.
var builder = this._builder(relatedData.joinTable());
if (options && options.query) {
Helpers.query.call(null, {_knex: builder}, [options.query]);
}
if (options) {

@@ -468,2 +483,3 @@ if (options.transacting) builder.transacting(options.transacting);

}
var collection = this;

@@ -491,2 +507,28 @@ if (method === 'delete') {

});
}),
// Loads or prepares a pivot model based on the constraints and deals with
// pivot model changes by calling the appropriate Bookshelf Model API
// methods. Returns a promise.
_processModelPivot: Promise.method(function(method, item, options, data, fks) {
var relatedData = this.relatedData
, JoinModel = relatedData.throughTarget
, instance = new JoinModel;
fks = instance.parse(fks);
data = instance.parse(data);
if (method === 'insert') {
return instance.set(data).save(null, options);
}
return instance.set(fks).fetch({
require: true
}).then(function (instance) {
if (method === 'delete') {
return instance.destroy(options);
}
return instance.save(item, options);
});
})

@@ -493,0 +535,0 @@

@@ -47,15 +47,22 @@ // Sync

select: Promise.method(function() {
var columns, sync = this,
options = this.options, relatedData = this.syncing.relatedData;
var knex = this.query;
var knex = this.query
, options = this.options
, relatedData = this.syncing.relatedData
, columnsInQuery = _.some(knex._statements, {grouping:'columns'})
, columns;
// Inject all appropriate select costraints dealing with the relation
// into the `knex` query builder for the current instance.
if (relatedData) {
relatedData.selectConstraints(knex, options);
} else {
if (!relatedData) {
columns = options.columns;
// Call the function, if one exists, to constrain the eager loaded query.
if (options._beforeFn) options._beforeFn.call(knex, knex);
if (!_.isArray(columns)) columns = columns ? [columns] : [_.result(this.syncing, 'tableName') + '.*'];
if (!_.isArray(columns)) {
columns = columns ? [columns] :
// if columns have been selected in a query closure, use them.
// any user who does this is responsible for prefixing each
// selected column with the correct table name. this will also
// break withRelated queries if the dependent fkey fields are not
// manually included. this is a temporary hack which will be
// replaced by an upcoming rewrite.
columnsInQuery ? [] : [_.result(this.syncing, 'tableName') + '.*'];
}
}

@@ -67,4 +74,22 @@

// Trigger a `fetching` event on the model, and then select the appropriate columns.
return Promise.bind(this).then(function() {
return Promise.bind(this).then(function () {
var fks = {}
, through;
// Inject all appropriate select costraints dealing with the relation
// into the `knex` query builder for the current instance.
if (relatedData) {
if (relatedData.throughTarget) {
fks[relatedData.key('foreignKey')] = relatedData.parentFk;
through = new relatedData.throughTarget(fks);
return through.triggerThen('fetching', through, relatedData.pivotColumns, options)
.then(function () {
relatedData.pivotColumns = through.parse(relatedData.pivotColumns);
relatedData.selectConstraints(knex, options);
});
} else {
relatedData.selectConstraints(knex, options);
}
}
}).then(function () {
return this.syncing.triggerThen('fetching', this.syncing, columns, options);

@@ -104,2 +129,2 @@ }).then(function() {

module.exports = Sync;
module.exports = Sync;
{
"name": "bookshelf",
"version": "0.7.9",
"version": "0.8.0",
"description": "A lightweight ORM for PostgreSQL, MySQL, and SQLite3",
"main": "bookshelf.js",
"scripts": {
"build": "./scripts/build.sh",
"jshint": "jshint bookshelf.js lib/*",
"test": "mocha -b -t 5000 --check-leaks -R spec test/index.js",
"release:patch": "npm run jshint && npm test && gulp build && gulp bump --type patch && gulp release",
"release:minor": "npm run jshint && npm test && gulp build && gulp bump --type minor && gulp release"
"test": "mocha -b -t 5000 --check-leaks -R spec test/index.js"
},

@@ -26,22 +25,14 @@ "homepage": "http://bookshelfjs.org",

"dependencies": {
"backbone": "1.1.0",
"bluebird": "^2.0.0",
"bluebird": "^2.9.4",
"chalk": "^1.0.0",
"create-error": "~0.3.1",
"inflection": "^1.5.1",
"inherits": "~2.0.1",
"lodash": "^2.0.0",
"semver": "^4.1.0",
"simple-extend": "0.1.0",
"trigger-then": "0.3.x"
"lodash": "^3.7.0",
"semver": "^4.3.3"
},
"devDependencies": {
"browserify": "^6.2.0",
"chai": "~1.9.1",
"chai-as-promised": "~4.1.0",
"gulp": "^3.6.2",
"gulp-bump": "^0.1.8",
"gulp-git": "^0.5.3",
"gulp-shell": "^0.2.5",
"jshint": "~2.5.1",
"knex": "0.7.x",
"jshint": "~2.7.0",
"knex": "^0.8.0",
"minimist": "^1.1.0",

@@ -51,8 +42,6 @@ "mocha": "^2.0.1",

"node-uuid": "~1.4.1",
"pg": "^3.6.2",
"pg": "^4.3.0",
"sinon": "^1.11.1",
"sinon-chai": "^2.6.0",
"sqlite3": "^3.0.2",
"through": "^2.3.4",
"underscore.string": "~2.3.1"
"sqlite3": "^3.0.5"
},

@@ -59,0 +48,0 @@ "author": {

@@ -33,3 +33,3 @@ // Registry Plugin -

if (_.isPlainObject(CollectionCtor)) {
CollectionCtor = this.Model.extend(CollectionCtor, staticProps);
CollectionCtor = this.Collection.extend(CollectionCtor, staticProps);
}

@@ -36,0 +36,0 @@ this._collections[name] = CollectionCtor;

@@ -104,4 +104,6 @@ // Virtuals Plugin

var virtual = this.virtuals && this.virtuals[key];
if (virtual && virtual.set) {
virtual.set.call(this, value);
if (virtual) {
if (virtual.set) {
virtual.set.call(this, value);
}
return true;

@@ -108,0 +110,0 @@ }

@@ -1,6 +0,6 @@

# [bookshelf.js](http://bookshelfjs.org) [![Build Status](https://travis-ci.org/tgriesser/bookshelf.png?branch=master)](https://travis-ci.org/tgriesser/bookshelf)
# [bookshelf.js](http://bookshelfjs.org) [![Build Status](https://travis-ci.org/tgriesser/bookshelf.svg?branch=master)](https://travis-ci.org/tgriesser/bookshelf)
Bookshelf is a Node.js ORM with support for PostgreSQL, MySQL / MariaDB, and SQLite3.
It is built atop the <a href="http://knexjs.org">Knex Query Builder</a>,
It is built atop the <a href="http://knexjs.org">Knex query builder</a>,
and is strongly influenced by the Model and Collection foundations of Backbone.js.

@@ -10,5 +10,5 @@

For Docs, License, Tests, FAQ, and other information, see: http://bookshelfjs.org.
For documentation, FAQs, and other information, see: http://bookshelfjs.org.
To suggest a feature, report a bug, or general discussion: http://github.com/tgriesser/bookshelf/issues/
To suggest a feature, report a bug, or for general discussion: http://github.com/tgriesser/bookshelf/issues/

@@ -24,3 +24,3 @@ ## Examples

var User = bookshelf.Model.extend({
tableName: 'users'
tableName: 'users',
messages: function() {

@@ -51,2 +51,6 @@ return this.hasMany(Posts);

});
```
```
## Contributing
To contribute to Bookshelf, read the [contribution documentation](CONTRIBUTING.md) for more information.

@@ -28,3 +28,3 @@ var Promise = require('../lib/base/promise');

chai.use(require('chai-as-promised'));
// chai.use(require('chai-as-promised'));
chai.use(require('sinon-chai'));

@@ -31,0 +31,0 @@ chai.should();

@@ -1,3 +0,7 @@

var Promise = global.testPromise;
var Promise = global.testPromise;
var assert = require('assert')
var equal = assert.equal
var deepEqual = assert.deepEqual
module.exports = function(bookshelf) {

@@ -7,4 +11,2 @@

var Backbone = require('backbone');
var output = require('./output/Collection');

@@ -36,2 +38,15 @@ var dialect = bookshelf.knex.client.dialect;

describe('extend', function() {
it ('should have own EmptyError', function() {
var Sites = bookshelf.Collection.extend({model: Site});
var OtherSites = bookshelf.Collection.extend({model: Site});
var err = new Sites.EmptyError();
expect(Sites.EmptyError).to.not.be.eql(bookshelf.Collection.EmptyError);
expect(Sites.EmptyError).to.not.be.eql(OtherSites.EmptyError);
expect(Sites.EmptyError).to.not.be.eql(OtherSites.EmptyError);
expect(err).to.be.an.instanceof(bookshelf.Collection.EmptyError);
});
});
describe('fetch', function() {

@@ -66,3 +81,3 @@

.then(function(model) {
expect(model).to.be.null;
equal(model, null);
});

@@ -74,6 +89,10 @@

return expect(new Site({id:1})
return new Site({id:1})
.authors()
.query({where: {id: 40}})
.fetchOne({require: true})).to.be.rejected;
.fetchOne({require: true})
.throw(new Error())
.catch(function(err) {
assert(err instanceof Author.NotFoundError, 'Error is a Site.NotFoundError')
});

@@ -88,3 +107,3 @@ });

var model = new bookshelf.Model();
expect(model.sync(model)).to.be.an.instanceOf(require('../../lib/sync'));
assert(model.sync(model) instanceof require('../../lib/sync'));
});

@@ -91,0 +110,0 @@

@@ -10,3 +10,4 @@ var _ = require('lodash');

'Customer', 'Settings', 'hostnames', 'instances', 'uuid_test',
'parsed_users', 'tokens', 'thumbnails'
'parsed_users', 'tokens', 'thumbnails',
'lefts', 'rights', 'lefts_rights'
];

@@ -148,2 +149,15 @@

table.string('token');
})
//
.createTable('lefts', function(table) {
table.increments();
})
.createTable('rights', function(table) {
table.increments();
})
.createTable('lefts_rights', function(table) {
table.increments();
table.string('parsed_name');
table.integer('left_id').notNullable();
table.integer('right_id').notNullable();
});

@@ -150,0 +164,0 @@

@@ -6,3 +6,2 @@ // All Models & Collections Used in the Tests

var _ = require('lodash');
_.str = require('underscore.string');

@@ -268,3 +267,3 @@ function _parsed (attributes) {

return _.transform(attrs, function (result, val, key) {
result[_.str.underscored(key)] = val;
result[_.snakeCase(key)] = val;
});

@@ -274,3 +273,3 @@ },

return _.transform(attrs, function (result, val, key) {
result[_.str.camelize(key)] = val;
result[_.camelCase(key)] = val;
});

@@ -293,2 +292,45 @@ }

/**
* Issue #578 - lifecycle events on pivot model for belongsToMany().through()
*
* Here we bootstrap some models involved in a .belongsToMany().through()
* relationship. The models are overridden with actual relationship methods
* e.g. `lefts: function () { return this.belongsToMany(LeftModel).through(JoinModel) }`
* within the tests to ensure the appropriate lifecycle events are being
* triggered.
*/
var LeftModel = Bookshelf.Model.extend({
tableName: 'lefts'
});
var RightModel = Bookshelf.Model.extend({
tableName: 'rights'
});
var JoinModel = Bookshelf.Model.extend({
tableName: 'lefts_rights',
defaults: { parsedName: '' },
format: function (attrs) {
return _.reduce(attrs, function(memo, val, key) {
memo[_.snakeCase(key)] = val;
return memo;
}, {});
},
parse: function (attrs) {
return _.reduce(attrs, function(memo, val, key) {
memo[_.camelCase(key)] = val;
return memo;
}, {});
},
lefts: function() {
return this.belongsTo(LeftModel);
},
rights: function() {
return this.belongsTo(RightModel);
}
});
return {

@@ -319,3 +361,6 @@ Models: {

Hostname: Hostname,
Uuid: Uuid
Uuid: Uuid,
LeftModel: LeftModel,
RightModel: RightModel,
JoinModel: JoinModel
}

@@ -322,0 +367,0 @@ };

var _ = require('lodash');
var uuid = require('node-uuid');
_.str = require('underscore.string');
var Promise = global.testPromise;
var Promise = global.testPromise;
var equal = require('assert').equal;
var assert = require('assert')
var equal = require('assert').equal;
var deepEqual = require('assert').deepEqual;

@@ -14,3 +14,2 @@

var Backbone = require('backbone');
var Models = require('./helpers/objects')(bookshelf).Models;

@@ -41,4 +40,11 @@

var OtherUser = bookshelf.Model.extend({
idAttribute: 'user_id',
getData: function() { return 'test'; }
}, {
classMethod: function() { return 'test'; }
});
it('can be extended', function() {
var user = new User();
var user = new User({name: "hoge"});
var subUser = new SubUser();

@@ -64,6 +70,43 @@ expect(user.idAttribute).to.equal('user_id');

it('doesnt have ommitted Backbone properties', function() {
expect(User.prototype.changedAttributes).to.be.undefined;
expect((new User()).changedAttributes).to.be.undefined;
equal(User.prototype.changedAttributes, undefined);
equal((new User()).changedAttributes, undefined);
});
context('should have own errors: name of', function(){
it('NotFoundError', function(){
var err = new User.NotFoundError();
var suberr = new SubUser.NotFoundError();
expect(User.NotFoundError).to.not.be.eql(bookshelf.Model.NotFoundError);
expect(err).to.be.an.instanceof(bookshelf.Model.NotFoundError);
expect(User.NotFoundError).to.not.be.eql(SubUser.NotFoundError);
expect(err).to.not.be.an.instanceof(SubUser.NotFoundError);
expect(suberr).to.be.an.instanceof(User.NotFoundError);
expect(User.NotFoundError).to.not.be.eql(OtherUser.NotFoundError);
expect(err).to.not.be.an.instanceof(OtherUser.NotFoundError);
});
it('NoRowsUpdatedError', function(){
var err = new User.NoRowsUpdatedError();
var suberr = new SubUser.NoRowsUpdatedError();
expect(User.NoRowsUpdatedError).to.not.be.eql(bookshelf.Model.NoRowsUpdatedError);
expect(err).to.be.an.instanceof(bookshelf.Model.NoRowsUpdatedError);
expect(User.NoRowsUpdatedError).to.not.be.eql(SubUser.NoRowsUpdatedError);
expect(err).to.not.be.an.instanceof(SubUser.NoRowsUpdatedError);
expect(suberr).to.be.an.instanceof(User.NoRowsUpdatedError);
expect(User.NoRowsUpdatedError).to.not.be.eql(OtherUser.NoRowsUpdatedError);
expect(err).to.not.be.an.instanceof(OtherUser.NoRowsUpdatedError);
});
it('NoRowsDeletedError', function(){
var err = new User.NoRowsDeletedError();
var suberr = new SubUser.NoRowsDeletedError();
expect(User.NoRowsDeletedError).to.not.be.eql(bookshelf.Model.NoRowsDeletedError);
expect(err).to.be.an.instanceof(bookshelf.Model.NoRowsDeletedError);
expect(User.NoRowsDeletedError).to.not.be.eql(SubUser.NoRowsDeletedError);
expect(err).to.not.be.an.instanceof(SubUser.NoRowsDeletedError);
expect(suberr).to.be.an.instanceof(User.NoRowsDeletedError);
expect(User.NoRowsDeletedError).to.not.be.eql(OtherUser.NoRowsDeletedError);
expect(err).to.not.be.an.instanceof(OtherUser.NoRowsDeletedError);
});
});
});

@@ -100,13 +143,2 @@

describe('get', function() {
it('should use the same get method as the Backbone library', function() {
var attached = ['get'];
_.each(attached, function(item) {
deepEqual(bookshelf.Model.prototype[item], Backbone.Model.prototype[item]);
});
});
});
describe('query', function() {

@@ -233,3 +265,3 @@

return _.reduce(attrs, function(memo, val, key) {
memo[_.str.underscored(key)] = val;
memo[_.snakeCase(key)] = val;
return memo;

@@ -290,3 +322,5 @@ }, {});

});
return expect(model.fetch()).to.be.rejected;
return model.fetch().throw(new Error('Err')).catch(function(err) {
assert(err.message === 'This failed')
})
});

@@ -297,3 +331,3 @@

model.on('fetching', function(model, columns, options) {
expect(options.query.whereIn).to.be.a.function;
assert(typeof options.query.whereIn === 'function')
});

@@ -308,4 +342,3 @@ return model.fetch();

});
return expect(model.fetch()).to.be.fulfilled;
return model.fetch()
});

@@ -361,3 +394,3 @@

}, function(err) {
expect(err.message).to.equal('No rows were affected in the update, did you mean to pass the {method: "insert"} option?');
expect(err.message).to.equal('No Rows Updated');
});

@@ -367,2 +400,6 @@

it('does not error if if the row was not updated but require is false', function() {
return new Site({id: 200, name: 'This doesnt exist'}).save({}, {require: false});
});
it('should not error if updated row was not affected', function() {

@@ -532,3 +569,3 @@ return new Site({id: 5, name: 'Fifth site, explicity created'}).save();

m.on('destroying', function(model, options) {
expect(options.query.whereIn).to.be.a.function;
assert(typeof options.query.whereIn === "function");
});

@@ -538,2 +575,8 @@ return m.destroy();

it('will throw an error when trying to destroy a non-existent object with {require: true}', function() {
return new Site({id: 1337}).destroy({require: true}).catch(function(err) {
assert(err instanceof bookshelf.NoRowsDeletedError)
})
});
});

@@ -714,5 +757,2 @@

var model = new Models.Site({id: 1});
model.on('change', function() {
count++;
});
equal(model.previous('id'), void 0);

@@ -727,3 +767,3 @@

model.set('id', 1);
equal(count, 1);
deepEqual(model.changed, {});
});

@@ -740,3 +780,3 @@

return new Models.Site({id: 1}).fetch().then(function(site) {
expect(site.hasChanged()).to.be.false;
equal(site.hasChanged(), false);
site.set('name', 'Changed site');

@@ -743,0 +783,0 @@ equal(site.hasChanged('name'), true);

var _ = require('lodash');
var equal = require('assert').equal;
var deepEqual = require('assert').deepEqual;
var expect = require('chai').expect;

@@ -48,2 +49,18 @@ module.exports = function (bookshelf) {

it('defaults virtual properties with no setter to a noop', function () {
var m = new (bookshelf.Model.extend({
virtuals: {
fullName: function () {
return this.get('firstName') + ' ' + this.get('lastName');
}
}
}))({fullName: 'Jane Doe'});
expect(m.attributes.fullName).to.be.undefined;
m.set('fullName', 'John Doe');
expect(m.attributes.fullName).to.be.undefined;
});
it('allows virtual properties to be set and get like normal properties', function () {

@@ -50,0 +67,0 @@ var m = new (bookshelf.Model.extend({

@@ -45,2 +45,6 @@ var _ = require('lodash');

var LeftModel = Models.LeftModel;
var RightModel = Models.RightModel;
var JoinModel = Models.JoinModel;
describe('Bookshelf Relations', function() {

@@ -220,4 +224,5 @@

});
expect(site1.related('admins').attach(admin1)).to.be.rejected;
return site1.related('admins').attach(admin1);
}).throw(new Error()).catch(function(err) {
equal(err.message, 'This failed')
});

@@ -237,3 +242,5 @@ });

}).then(function() {
expect(site1.related('admins').detach(admin1)).to.be.rejected;
return site1.related('admins').detach(admin1)
}).throw(new Error()).catch(function(err) {
equal(err.message, 'This failed')
});

@@ -270,4 +277,6 @@ });

]);
})
.then(function(resp) {
}).spread(function(site1Admins, site2Admins) {
expect(site1Admins).to.equal(site1.related('admins'));
expect(site2Admins).to.equal(site2.related('admins'));
expect(site1.related('admins')).to.have.length(2);

@@ -452,4 +461,3 @@ expect(site2.related('admins')).to.have.length(1);

}).catch(function (err) {
expect(err).to.be.ok;
expect(err).to.be.an.instanceof(Error);
assert(err instanceof Error);
});

@@ -868,4 +876,83 @@ });

describe('Issue #578 - lifecycle events on pivot model for belongsToMany().through()', function () {
// Overrides the `initialize` method on the JoinModel to throw an Error
// when the current lifecycleEvent is triggered. Additionally overrides
// the Left/Right models `.belongsToMany().through()` configuration with
// the overridden JoinModel.
function initializeModelsForLifecycleEvent(lifecycleEvent) {
JoinModel = JoinModel.extend({
initialize: (function (v) {
return function () {
this.on(lifecycleEvent, function () {
throw new Error('`' + lifecycleEvent + '` triggered on JoinModel()');
});
};
}(lifecycleEvent))
});
LeftModel = LeftModel.extend({
rights: function () {
return this.belongsToMany(RightModel).through(JoinModel);
}
});
RightModel = RightModel.extend({
lefts: function () {
return this.belongsToMany(LeftModel).through(JoinModel).withPivot(['parsedName']);
}
});
};
// First, initialize the models for the current `lifecycleEvent`, then
// step through the entire lifecycle of a JoinModel, returning a promise.
function joinModelLifecycleRoutine(lifecycleEvent) {
initializeModelsForLifecycleEvent(lifecycleEvent);
return (new LeftModel).save().then(function (left) {
// creating, saving, created, saved
return [left, left.rights().create()];
}).spread(function (left, right) {
// fetching, fetched
return [left, right, right.lefts().fetch()];
}).spread(function (left, right, lefts) {
// updating, updated
return [left, right, left.rights().updatePivot({})];
}).spread(function (left, right, relationship) {
return (new LeftModel).save().then(function (left) {
return [left, right, right.lefts().attach(left)];
});
}).spread(function (left, right, relationship) {
// destroying, destroyed
return left.rights().detach(right);
});
}
// For each lifecycle event that should be triggered on the JoinModel,
// build a test that verifies the expected Error is being thrown by the
// JoinModel's lifecycle event handler.
[
'creating',
'created',
'saving',
'saved',
'fetching',
'fetched',
'updating',
'updated',
'destroying',
'destroyed'
].forEach(function (v) {
it('should trigger pivot model lifecycle event: ' + v, function () {
return joinModelLifecycleRoutine(v).catch(function (err) {
assert(err instanceof Error)
equal(err.message, '`' + v + '` triggered on JoinModel()');
});
});
});
});
});
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc