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.1.2 to 0.1.3

262

bookshelf.js

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

// Bookshelf.js 0.1.2
// Bookshelf.js 0.1.3

@@ -19,3 +19,3 @@ // (c) 2013 Tim Griesser

var _ = require('underscore');
var When = require('when');
var when = require('when');
var Knex = require('knex');

@@ -26,6 +26,6 @@ var Inflection = require('inflection');

// so we can have event driven async validations, functions, etc.
require('trigger-then')(Backbone, When);
require('trigger-then')(Backbone, when);
// Keep in sync with `package.json`.
Bookshelf.VERSION = '0.1.2';
Bookshelf.VERSION = '0.1.3';

@@ -99,8 +99,3 @@ // We're using `Backbone.Events` rather than `EventEmitter`,

// Returns the related item
related: function(item) {
return this.relations[item];
},
// Helper for attaching query constraints on related
// Helper for attaching query constraints on related
// `models` or `collections` as necessary.

@@ -111,8 +106,8 @@ _addConstraints: function(resp) {

if (!relation.fkValue && !resp) {
return When.reject(new Error("The " + relation.otherKey + " must be specified."));
return when.reject(new Error("The " + relation.otherKey + " must be specified."));
}
if (relation.type !== 'belongsToMany') {
constraints.call(this, resp);
constraints(this, resp);
} else {
belongsToMany.call(this, resp);
belongsToMany(this, resp);
}

@@ -137,3 +132,4 @@ }

options || (options = {});
this.attributes = {};
this.attributes = Object.create(null);
this._reset();
this.relations = {};

@@ -146,3 +142,2 @@ this.cid = _.uniqueId('c');

this.set(attrs, options);
this.changed = {};
this.initialize.apply(this, arguments);

@@ -152,5 +147,5 @@ };

// A list of properties that are omitted from the `Backbone.Model.prototype`, since we're not
// handling validations, or tracking changes in the same fashion as `Backbone`, we can drop all of
// these related keys.
var modelOmitted = ['isValid', 'hasChanged', 'changedAttributes', 'previous', 'previousAttributes'];
// handling validations, or tracking changes in the same fashion as `Backbone`, we can drop these
// specific methods.
var modelOmitted = ['isValid', 'validationError', 'changedAttributes'];

@@ -203,3 +198,3 @@ // List of attributes attached directly from the `options` passed to the constructor.

joinTableName: joinTableName || [
_.result(this, 'tableName'),
_.result(this, 'tableName'),
_.result(Target.prototype, 'tableName')

@@ -210,2 +205,43 @@ ].sort().join('_')

// 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 attr, attrs, changing;
// Handle both `"key", value` and `{key: value}` -style arguments.
if (typeof key === 'object') {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
options || (options = {});
// 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 (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;
}
if (hasChanged && !options.silent) this.trigger('change', this, options);
return this;
},
// Fetch a model based on the currently set attributes,

@@ -215,3 +251,6 @@ // returning a model to the callback, along with any options.

fetch: function(options) {
return this.sync(this, options).first();
return this.sync(this, options).first().then(function(model) {
model._reset();
return model;
});
},

@@ -224,3 +263,3 @@

save: function(key, val, options) {
var id, attrs;
var attrs;

@@ -243,15 +282,21 @@ // Handle both `"key", value` and `{key: value}` -style arguments.

// Merge any defaults here rather than during object creation.
var defaults = _.result(this, 'defaults');
// Determine whether the model is new, typically based on whether the model has
// an `idAttribute` or not.
var method = options.method || (this.isNew(options) ? 'insert' : 'update');
var vals = attrs;
if (defaults) {
vals = _.extend({}, defaults, this.attributes, vals);
// If the object is being created, we merge any defaults here
// rather than during object creation.
if (method === 'insert' || options.defaults) {
var defaults = _.result(this, 'defaults');
if (defaults) {
vals = _.extend({}, defaults, this.attributes, vals);
}
}
// Set the attributes on the model, and maintain a reference to use below.
var model = this.set(vals);
var model = this.set(vals, {silent: true});
var sync = model.sync(model, options);
var method = options.method || (model.isNew(options) ? 'insert' : 'update');
return When.all([
return when.all([
model.triggerThen((method === 'insert' ? 'creating' : 'updating'), model, attrs, options),

@@ -264,5 +309,9 @@ model.triggerThen('saving', model, attrs, options)

// After a successful database save, the id is updated if the model was created
if (method === 'insert' && resp) model.set(model.idAttribute, resp[0]);
if (method === 'insert' && resp) {
model[model.idAttribute] = resp[0];
}
model.trigger((method === 'insert' ? 'created' : 'updated'), model, resp, options);
model.trigger('saved', model, resp, options);
model._reset();
return model;

@@ -283,3 +332,5 @@ })

.then(function(resp) {
model.clear();
model.trigger('destroyed', model, resp, options);
model._reset();
return resp;

@@ -323,5 +374,6 @@ }).ensure(function() {

var model = new this.constructor(this.attributes);
model.relations = _.map(this.relations, function(relation) {
return relation.clone();
});
var relations = this.relations;
for (var key in relations) {
model.relations[key] = relations[key].clone();
}
return model;

@@ -350,3 +402,3 @@ },

// If we're handling an eager loaded related model,
// keep a reference to the original constructor to assemble
// keep a reference to the original constructor to assemble
// the correct object once the eager matching is finished.

@@ -383,5 +435,18 @@ // Otherwise, just grab the `foreignKey` value for building the query.

// 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.relations[name] = this[name]());
},
// Called after a `sync` action (save, fetch, delete) -
// resets the `_previousAttributes` and `changed` hash for the model.
_reset: function() {
this._previousAttributes = extendNull(this.attributes);
this.changed = extendNull();
},
// Validation can be complicated, and is better handled
// on its own and not mixed in with database logic.
_validate: function() {
_validate: function() {
return true;

@@ -395,3 +460,3 @@ }

// A Bookshelf Collection contains a number of database rows, represented by
// A Bookshelf Collection contains a number of database rows, represented by
// models, so they can be easily sorted, serialized, and manipulated.

@@ -429,3 +494,3 @@ var Collection = Bookshelf.Collection = function(models, options) {

var collection = this;
return model.save(null, options).then(function(resp) {
return model.save(null, options).then(function() {
collection.add(model, options);

@@ -514,3 +579,3 @@ return model;

pendingNames.push(name);
pendingDeferred.push(eagerFetch.call(handled[name], {
pendingDeferred.push(eagerFetch(handled[name], {
transacting: options.transacting,

@@ -523,3 +588,3 @@ withRelated: subRelated[name]

// returning the original response when these syncs are complete.
return When.all(pendingDeferred).spread(_.bind(this.matchResponses, this));
return when.all(pendingDeferred).spread(_.bind(this.matchResponses, this));
},

@@ -532,6 +597,6 @@

var handled = this.handled;
// Pair each of the query responses with the parent models.
for (var i = 0, l = args.length; i < l; i++) {
// Get the current relation this response matches up with, based

@@ -549,3 +614,3 @@ // on the pendingNames array.

var models = parent.models;
// Attach the appropriate related items onto the parent model.

@@ -597,8 +662,8 @@ for (var i2 = 0, l2 = models.length; i2 < l2; i2++) {

// Standard constraints for regular or eager loaded relations.
var constraints = function(resp) {
var relation = this._relation;
var constraints = function(target, resp) {
var relation = target._relation;
if (resp) {
this.query('whereIn', relation.foreignKey, _.pluck(resp, relation.parentIdAttr));
target.query('whereIn', relation.foreignKey, _.uniq(_.pluck(resp, relation.parentIdAttr)));
} else {
this.query('where', relation.foreignKey, '=', relation.fkValue);
target.query('where', relation.foreignKey, '=', relation.fkValue);
}

@@ -608,10 +673,10 @@ };

// Helper function for adding the constraints needed on a eager load.
var belongsToMany = function(resp) {
var
relation = this._relation,
var belongsToMany = function(target, resp) {
var
relation = target._relation,
columns = relation.columns || (relation.columns = []),
builder = this.query(),
builder = target.query(),
tableName = _.result(this, 'tableName'),
idAttribute = _.result(this, 'idAttribute'),
tableName = _.result(target, 'tableName'),
idAttribute = _.result(target, 'idAttribute'),

@@ -643,14 +708,13 @@ otherKey = relation.otherKey,

// Called from `EagerRelation.processRelated` with the context
// Called from `EagerRelation.processRelated` with the context
// of an eager-loading model or collection, this function
// fetches the nested related items, and returns a deferred object,
// fetches the nested related items, and returns a deferred object,
// with the cumulative handling of multiple (potentially nested) relations.
var eagerFetch = function(options) {
var eagerFetch = function(related, options) {
var current = this;
var models = this.models = [];
var relation = this._relation;
var models = related.models = [];
var relation = related._relation;
return When(this._addConstraints(relation.parentResponse)).then(function() {
return current.query().select(relation.columns);
return when(related._addConstraints(relation.parentResponse)).then(function() {
return related.query().select(relation.columns);
})

@@ -670,3 +734,3 @@ .then(function(resp) {

var model = new relation.modelCtor();
return new EagerRelation(current, model, resp).processRelated(options);
return new EagerRelation(related, model, resp).processRelated(options);
}

@@ -678,3 +742,3 @@ }

}).ensure(function() {
current.resetQuery();
related.resetQuery();
});

@@ -690,5 +754,3 @@ };

Model.forge = Collection.forge = function() {
var Ctor = function() {};
Ctor.prototype = this.prototype;
var inst = new Ctor();
var inst = Object.create(this.prototype);
var obj = this.apply(inst, arguments);

@@ -711,3 +773,2 @@ return (Object(obj) === obj ? obj : inst);

this.query = model.query();
if (options.transacting) this.query.transacting(options.transacting);

@@ -718,5 +779,5 @@ };

// Select the first item from the database.
// Select the first item from the database - only used by models.
first: function() {
this.query.where(_.extend({}, this.model.attributes)).limit(1);
this.query.where(extendNull(this.model.attributes)).limit(1);
return this.select();

@@ -729,3 +790,3 @@ },

// the promise is resolved. Any `success` handler passed in the
// options will be called.
// options will be called - used by both models & collections.
select: function() {

@@ -736,3 +797,3 @@ var sync = this;

return When(model._addConstraints()).then(function() {
return when(model._addConstraints()).then(function() {
var columns = options.columns;

@@ -746,6 +807,7 @@

return sync.query.select(columns);
return model.triggerThen('fetching', model, columns, options).then(function() {
return sync.query.select(columns);
});
})
.then(function(resp) {
var target;

@@ -757,3 +819,4 @@ if (resp && resp.length > 0) {

if (model instanceof Model) {
model.set(model.parse(resp[0], options), options);
model.set(model.parse(resp[0], options), _.extend({silent: true}, options));
model._previousAttributes = extendNull(model.attributes);
} else {

@@ -764,7 +827,7 @@ model.reset(resp, {silent: true, parse: true});

// If the `withRelated` property is specified on the options hash, we dive
// into the `EagerRelation`. If the current querying object is a collection,
// into the `EagerRelation`. If the current querying object is a collection,
// we find the associated `model` to determine necessary eager relations.
// Once the `EagerRelation` is complete, we return the original response from the query.
if (options.withRelated) {
target = (model instanceof Collection ? new model.model() : model);
var target = (model instanceof Collection ? new model.model() : model);
return new EagerRelation(model, target, resp)

@@ -780,6 +843,6 @@ .processRelated(options)

// a failure if the model comes up blank.
if (options.require) return When.reject(new Error('EmptyResponse'));
if (options.require) return when.reject(new Error('EmptyResponse'));
if (model instanceof Model) {
model.clear();
model.clear({silent: true});
return {};

@@ -801,15 +864,24 @@ }

// Issues an `insert` command on the query.
// Issues an `insert` command on the query - only used by models.
insert: function() {
var model = this.model;
return this.query
.idAttribute(_.result(this.model, 'idAttribute'))
.insert(this.model.format(_.extend({}, this.model.attributes)));
.idAttribute(model.idAttribute)
.insert(model.format(extendNull(model.attributes)))
.then(function(resp) {
model._previousAttributes = extendNull(model.attributes);
return resp;
});
},
// Issues an `update` command on the query.
// Issues an `update` command on the query - only used by models.
update: function(attrs, options) {
attrs = (attrs && options.patch ? attrs : this.model.attributes);
var model = this.model;
return this.query
.where(this.model.idAttribute, this.model.id)
.update(this.model.format(_.extend({}, attrs)));
.where(model.idAttribute, model.id)
.update(model.format(extendNull(model.attributes)))
.then(function(resp) {
model._previousAttributes = extendNull(model.attributes);
return resp;
});
},

@@ -819,3 +891,3 @@

del: function() {
var wheres;
var wheres, model = this.model;
if (this.model.id != null) {

@@ -826,3 +898,3 @@ wheres = {};

if (!wheres && this.query.wheres.length === 0) {
return When.reject(new Error('A model cannot be destroyed without a "where" clause or an idAttribute.'));
return when.reject(new Error('A model cannot be destroyed without a "where" clause or an idAttribute.'));
}

@@ -852,3 +924,3 @@ return this.query.where(wheres).del();

// it attempts to remove the item based on a where clause with
// these parameters. If no parameters are specified, we assume we will
// these parameters. If no parameters are specified, we assume we will
// detach all related associations.

@@ -865,3 +937,3 @@ detach: function(ids, options) {

if (!_.isArray(columns)) columns = columns ? [columns] : [];
var joinString, relation = this._relation;
var relation = this._relation;
relation.pivotColumns || (relation.pivotColumns = []);

@@ -884,3 +956,3 @@ for (var i = 0, l = columns.length; i < l; i++) {

_handler: function(method, ids, options) {
if (ids == void 0 && method === 'insert') return When.resolve();
if (ids == void 0 && method === 'insert') return when.resolve();
if (!_.isArray(ids)) ids = ids ? [ids] : [];

@@ -891,3 +963,3 @@ var pending = [];

}
return When.all(pending);
return when.all(pending);
},

@@ -925,5 +997,11 @@

// Creates a new object, extending an object that
// does not inherit the `Object.prototype`.
var extendNull = function(target) {
return _.extend(Object.create(null), target);
};
// Simple memoization of the singularize call.
var singularMemo = (function(value) {
var cache = {};
var singularMemo = (function() {
var cache = Object.create(null);
return function(arg) {

@@ -991,3 +1069,3 @@ if (arg in cache) {

// Named instances of Bookshelf, presumably with different `Knex`
// options, to initialize different databases.
// options, to initialize different databases.
// The main instance being named "main"...

@@ -994,0 +1072,0 @@ Bookshelf.Instances = {};

{
"name": "bookshelf",
"author": "Tim Griesser",
"version": "0.1.2",
"description": "An ORM with some Backbone",
"version": "0.1.3",
"description": "A lightweight ORM for Postgres, MySQL, and SQLite3 with some Backbone",
"main": "bookshelf.js",

@@ -18,5 +17,13 @@ "scripts": {

"sqlite",
"sqlite3",
"knex",
"database",
"object relational mapper",
"datamapper",
"active record"
],
"repository":{
"type": "git",
"url": "https://github.com/tgriesser/knex.git"
},
"dependencies": {

@@ -38,4 +45,8 @@ "backbone": "1.0.x",

},
"author": {
"name": "Tim Griesser",
"web": "https://github.com/tgriesser"
},
"license": "MIT",
"readmeFilename": "README.md"
}

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

/*global describe, it */
var _ = require('underscore');

@@ -30,3 +31,3 @@ _.str = require('underscore.string');

});
var SubUser = User.extend({

@@ -125,5 +126,5 @@ otherMethod: function() { return this.getData(); }

describe('tableName', function() {
var table = new Bookshelf.Model({}, {tableName: 'customers'});
it('can be passed in the initialize options', function() {

@@ -219,7 +220,13 @@ equal(table.tableName, 'customers');

it('issues a first (get one) to Knex, returning a promise', function(ok) {
it('issues a first (get one) to Knex, triggering a fetched event, returning a promise', function(ok) {
var count = 0;
var model = new Site({id: 1});
model.on('fetched', function() {
count++;
});
new Site({id: 1}).fetch().then(function(model) {
model.fetch().then(function(model) {
equal(model.get('id'), 1);
equal(model.get('name'), 'knexjs.org');
equal(count, 1);
ok();

@@ -230,2 +237,14 @@ });

it('has a fetching event, which will fail if an error is thrown or if a rejected promise is provided', function(ok) {
var model = new Site({id: 1});
model.on('fetching', function() {
throw new Error("This failed");
});
model.fetch().then(null, function() {
ok();
});
});
});

@@ -383,3 +402,3 @@

});
});
});

@@ -400,14 +419,39 @@ describe('resetQuery', function() {

var Item = Bookshelf.Model.extend({defaults: {item: 'test'}});
var item = new Item({id: 1});
deepEqual(item.toJSON(), {id: 1});
var item = new Item({newItem: 'test2'});
deepEqual(item.toJSON(), {newItem: 'test2'});
item.sync = function() {
deepEqual(this.toJSON(), {id: 1, item: 'test'});
deepEqual(this.toJSON(), {id: 1, item: 'test', newItem: 'test2'});
ok();
return stubSync;
};
item.save({id: 1});
});
it('only assigns defaults when creating a model, unless {defaults: true} is passed in the save options', function(ok) {
var Item = Bookshelf.Model.extend({defaults: {item: 'test'}});
var item = new Item({id: 1, newItem: 'test2'});
deepEqual(item.toJSON(), {id: 1, newItem: 'test2'});
item.sync = function() {
deepEqual(this.toJSON(), {id: 1, newItem: 'test2'});
return stubSync;
};
item.save();
item.sync = function() {
deepEqual(this.toJSON(), {id: 2, item: 'test', newItem: 'test2'});
ok();
return stubSync;
};
item.save({id: 2}, {defaults: true});
});
});
describe('sync', function() {
it('creates a new instance of Bookshelf.Sync', function(){
var model = new Bookshelf.Model();
equal((model.sync(model) instanceof Bookshelf.Sync), true);
});
});
describe('isNew', function() {

@@ -424,10 +468,37 @@

describe('sync', function() {
describe('previous, previousAttributes', function() {
it('creates a new instance of Bookshelf.Sync', function(){
var model = new Bookshelf.Model();
equal((model.sync(model) instanceof Bookshelf.Sync), true);
it('will return the previous value of an attribute the last time it was synced', function(ok) {
var count = 0;
var model = new Models.Site({id: 1});
model.on('change', function() {
count++;
});
equal(model.previous('id'), void 0);
model.fetch().then(function() {
deepEqual(model.previousAttributes(), {id: 1, name: 'knexjs.org'});
deepEqual(model.changed, {});
model.set('id', 2);
equal(model.previous('id'), 1);
deepEqual(model.changed, {id: 2});
model.set('id', 1);
equal(count, 1);
ok();
}).then(null, ok);
});
});
});
describe('hasChanged', function(ok) {
var site = new Models.Site({name: 'Third Site'});
equal(site.hasChanged('name'), true);
deepEqual(site.changed, {name: 'Third Site'});
site.fetch().then(function() {
deepEqual(site.hasChanged, {});
});
});
};
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