Comparing version 0.0.0 to 0.0.1
242
bookshelf.js
@@ -12,3 +12,2 @@ // Bookshelf.js 0.1.0 | ||
// ------------- | ||
var Bookshelf = {}; | ||
@@ -26,6 +25,2 @@ | ||
// Attach `Knex` & `Knex.Transaction` for convenience. | ||
Bookshelf.Knex = Knex; | ||
Bookshelf.Transaction = Knex.Transaction; | ||
// Keep in sync with package.json. | ||
@@ -38,4 +33,4 @@ Bookshelf.VERSION = '0.0.0'; | ||
var Events = Bookshelf.Events = Backbone.Events; | ||
Events.emit = function() { this.trigger.apply(this, arguments); }; | ||
Events.removeAllListeners = function(event) { this.off(event, null, null); }; | ||
Events.emit = function() { this.trigger.apply(this, arguments); }; | ||
@@ -49,2 +44,7 @@ // `Bookshelf` may be used as a top-level pub-sub bus. | ||
// Returns an instance of the query builder. | ||
builder: function(table) { | ||
return Bookshelf.Knex(table); | ||
}, | ||
// If there are no arguments, return the current object's | ||
@@ -55,3 +55,3 @@ // query builder (or create a new one). If there are arguments, | ||
query: function() { | ||
this._builder || (this._builder = Bookshelf.Knex(_.result(this, 'tableName'))); | ||
this._builder || (this._builder = this.builder(_.result(this, 'tableName'))); | ||
var args = _.toArray(arguments); | ||
@@ -109,3 +109,3 @@ if (args.length === 0) return this._builder; | ||
// A Bookshelf Model represents an individual row in the database table -- | ||
// It is a similar implementation to the `Backbone.Model` | ||
// It has a similar implementation to the `Backbone.Model` | ||
// constructor, except that defaults are not set until the | ||
@@ -123,3 +123,3 @@ // object is persisted, and the collection property is not used. | ||
this._configure(options); | ||
if (options && options.parse) attrs = this.parse(attrs, options) || {}; | ||
if (options.parse) attrs = this.parse(attrs, options) || {}; | ||
options.protect || (options.protect = false); | ||
@@ -132,3 +132,4 @@ this.set(attrs, options); | ||
// A list of properties that are omitted from the `Backbone.Model.prototype`, since we're not | ||
// handling validations, or tracking changes, we can drop all of these related features. | ||
// handling validations, or tracking changes in the same way as Backbone, we can drop all of | ||
// these related features. | ||
var modelOmitted = ['isValid', 'hasChanged', 'changedAttributes', 'previous', 'previousAttributes']; | ||
@@ -215,3 +216,3 @@ | ||
if (key == null || typeof key === "object") { | ||
attrs = key; | ||
attrs = key || {}; | ||
options = val || {}; | ||
@@ -223,26 +224,19 @@ } else { | ||
// Don't allow setting an id attribute on save, unless the | ||
// `{existing: true}` flag is set on the options hash. | ||
if (attrs) id = attrs[this.idAttribute]; | ||
if (id && id !== this.id && !options.existing) { | ||
Q.reject(new Error('The model cannot be saved with an idAttribute')); | ||
// If the model has timestamp columns, | ||
// set them as attributes on the model, even | ||
// if the method is set to "patch". | ||
if (this.hasTimestamps) { | ||
_.extend(attrs, this.timestamp(options)); | ||
} | ||
// Handle the defaults at the `save` level rather than the | ||
// object creation level. | ||
// Merge any defaults here rather than during object creation. | ||
var defaults = _.result(this, 'defaults'); | ||
var vals = attrs; | ||
if (defaults) { | ||
attrs = _.extend({}, defaults, this.attributes); | ||
vals = _.extend({}, defaults, this.attributes, vals); | ||
} | ||
// Set the model, and maintain a reference to use below. | ||
var model = this.set(attrs); | ||
// If the model has timestamp columns, | ||
// set them as attributes on the model | ||
if (model.hasTimestamps) { | ||
model.timestamp(options); | ||
} | ||
var sync = model.sync(model, options); | ||
// Set the attributes on the model, and maintain a reference to use below. | ||
var model = this.set(vals); | ||
var sync = model.sync(model, options); | ||
var method = options.method || (model.isNew(options) ? 'insert' : 'update'); | ||
@@ -254,3 +248,3 @@ | ||
return sync[method]().then(function(resp) { | ||
return sync[method](attrs, options).then(function(resp) { | ||
@@ -301,6 +295,6 @@ // After a successful database save, the id is updated | ||
var d = new Date(); | ||
this.set('updated_at', d); | ||
if (this.isNew(options)) { | ||
this.set('created_at', d); | ||
} | ||
var vals = {}; | ||
vals.updated_at = d; | ||
if (this.isNew(options)) vals.created_at = d; | ||
return vals; | ||
}, | ||
@@ -405,9 +399,2 @@ | ||
// Efficiently persists any models in the current collection, only saving models that have | ||
// been changed, batch inserting or updating where appropriate, and retuning | ||
// a promise resolving with the `collection`. | ||
save: function(options) { | ||
// TODO | ||
}, | ||
// Shortcut for creating a new model, saving, and adding to the collection. | ||
@@ -422,3 +409,2 @@ // Returns a promise which will resolve with the model added to the collection. | ||
if (options.success) options.success(model, resp, options); | ||
this.add(model, options); | ||
return model; | ||
@@ -449,9 +435,2 @@ }); | ||
// Temporary helper object for handling the response of an `EagerRelation` load. | ||
var RelatedModels = function(models) { | ||
this.models = models; | ||
this.length = this.models.length; | ||
}; | ||
_.extend(RelatedModels.prototype, _.pick(Collection.prototype, 'find', 'where', 'filter', 'findWhere')); | ||
// An `EagerRelation` object temporarily stores the models from an eager load, | ||
@@ -476,3 +455,3 @@ // and handles matching eager loaded objects with their parent(s). | ||
Bookshelf.addEagerConstraints(opts.type, this, opts.parentResponse); | ||
Bookshelf.addConstraints(opts.type, this, opts.parentResponse); | ||
@@ -608,13 +587,11 @@ return this.query().select(opts.columns).then(function(resp) { | ||
// Adds the basic relation constraints onto the query. | ||
Bookshelf.addConstraints = function(type, target) { | ||
if (type !== 'belongsToMany') { | ||
return constraints(target); | ||
} else { | ||
return belongsToMany(target); | ||
} | ||
// Temporary helper object for handling the response of an `EagerRelation` load. | ||
var RelatedModels = function(models) { | ||
this.models = models; | ||
this.length = this.models.length; | ||
}; | ||
_.extend(RelatedModels.prototype, _.pick(Collection.prototype, 'find', 'where', 'filter', 'findWhere')); | ||
// Adds eager loading relationship constraints onto the query. | ||
Bookshelf.addEagerConstraints = function(type, target, resp) { | ||
// Adds the relation constraints onto the query. | ||
Bookshelf.addConstraints = function(type, target, resp) { | ||
if (type !== 'belongsToMany') { | ||
@@ -659,15 +636,13 @@ return constraints(target, resp); | ||
var belongsToMany = function(target, resp) { | ||
var relation, columns, builder, idAttribute, tableName, | ||
otherKey, foreignKey, pivotColumns, joinTableName; | ||
relation = target._relation; | ||
columns = relation.columns || (relation.columns = []); | ||
builder = target.query(); | ||
var | ||
relation = target._relation, | ||
columns = relation.columns || (relation.columns = []), | ||
builder = target.query(), | ||
tableName = _.result(target, 'tableName'); | ||
idAttribute = _.result(target, 'idAttribute'); | ||
tableName = _.result(target, 'tableName'), | ||
idAttribute = _.result(target, 'idAttribute'), | ||
otherKey = relation.otherKey; | ||
foreignKey = relation.foreignKey; | ||
pivotColumns = relation.pivotColumns; | ||
otherKey = relation.otherKey, | ||
foreignKey = relation.foreignKey, | ||
pivotColumns = relation.pivotColumns, | ||
joinTableName = relation.joinTableName; | ||
@@ -699,2 +674,13 @@ | ||
// The `forge` function properly instantiates a new Model or Collection | ||
// without needing the "new" keyword... to make object creation cleaner | ||
// and more chainable. | ||
Model.forge = Collection.forge = function() { | ||
var Ctor = function() {}; | ||
Ctor.prototype = this.prototype; | ||
var inst = new Ctor(); | ||
var obj = this.apply(inst, arguments); | ||
return (Object(obj) === obj ? obj : inst); | ||
}; | ||
// Bookshelf.Sync | ||
@@ -707,3 +693,3 @@ // ------------------- | ||
// If the `transacting` option is set, the query is assumed to be | ||
// part of a transaction, and this information is passed along to Knex. | ||
// part of a transaction, and this information is passed along to `Knex`. | ||
var Sync = Bookshelf.Sync = function(model, options) { | ||
@@ -769,12 +755,14 @@ options || (options = {}); | ||
} | ||
// TODO: any handling for empty responses? | ||
// If `{require: true}` is set as an option, the fetch is considered | ||
// a failure if the model comes up blank. | ||
if (options.require) return Q.reject('EmptyResponse'); | ||
if (model instanceof Model) { | ||
model.clear(); | ||
return {}; | ||
} else { | ||
model.reset([], {silent: true}); | ||
return []; | ||
} | ||
model.reset([], {silent: true}); | ||
return []; | ||
@@ -796,3 +784,4 @@ }).then(function(resp) { | ||
// Issues an `update` command on the query. | ||
update: function() { | ||
update: function(attrs, options) { | ||
attrs = (attrs && options.patch ? attrs : this.model.attributes); | ||
return this.query.where(this.model.idAttribute, this.model.id) | ||
@@ -828,3 +817,4 @@ .update(this.model.format(_.extend({}, this.model.attributes))); | ||
var pivot = this._relation; | ||
return Q.allResolved(_.map(ids, function(item) { | ||
var context = this; | ||
return Q.all(_.map(ids, function(item) { | ||
var data = {}; | ||
@@ -845,3 +835,3 @@ data[pivot.otherKey] = pivot.fkValue; | ||
} | ||
var builder = Bookshelf.Knex(pivot.joinTableName); | ||
var builder = context.builder(pivot.joinTableName); | ||
if (options && options.transacting) { | ||
@@ -894,44 +884,2 @@ builder.transacting(options.transacting); | ||
// Inherit standard Backbone.js Collections & Models from the client, | ||
// transforming a client `Backbone.Model` or `Backbone.Collection` to a | ||
// `Bookshelf` compatible object, to reuse validations, defaults, user methods, etc. | ||
Model.convert = Collection.convert = function(Target, protoProps, staticProps) { | ||
var parent = this; | ||
// Don't allow convert to work with an object instance. | ||
if (!Target.prototype) { | ||
throw new Error('Bookshelf.convert can only work with a constructor object'); | ||
} | ||
// Traverse the prototype chain, breaking once we hit the prototype of the | ||
// Model or Collection we're converting. This way we can put the prototype chain | ||
// back together starting from the base "extend" so inheritance works properly. | ||
var current = Target; | ||
var depth = []; | ||
var passed = false; | ||
while (passed !== true) { | ||
if (_.isEqual(current.prototype, Backbone.Model.prototype) || | ||
_.isEqual(current.prototype, Backbone.Collection.prototype)) { | ||
passed = true; | ||
} else if (!current.__super__) { | ||
throw new Error("Only Backbone objects may be converted."); | ||
} else { | ||
depth.push(_.pick(current.prototype, _.keys(current.prototype))); | ||
current = current.__super__.constructor; | ||
} | ||
} | ||
// Setup the correct prototype chain. | ||
var currentObj = this; | ||
for (var i = depth.length; i > 0; i--) { | ||
// Omit parse and toJSON, as these have fundamentally different meanings | ||
// on the client and server. | ||
currentObj = currentObj.extend(_.omit(depth[i - 1], 'parse', 'toJSON')); | ||
} | ||
return currentObj.extend(protoProps, staticProps); | ||
}; | ||
// Filters an array of objects, cleaning out any nested properties. | ||
@@ -944,2 +892,7 @@ var skim = function(data) { | ||
// References to the default `Knex` and `Knex.Transaction`, overwritten | ||
// when a new database connection is created in `Initialize` below. | ||
Bookshelf.Knex = Knex; | ||
Bookshelf.Transaction = Knex.Transaction; | ||
// Bookshelf.Initialize | ||
@@ -949,9 +902,48 @@ // ------------------- | ||
// Configure the `Bookshelf` settings (database adapter, etc.) once, | ||
// so it is ready on first model initialization. | ||
Bookshelf.Initialize = function(options) { | ||
return Knex.Initialize(options); | ||
// so it is ready on first model initialization. Optionally, provide | ||
// a `name` so a named instance of | ||
Bookshelf.Initialize = function(name, options) { | ||
var Target; | ||
if (_.isObject(name)) { | ||
options = name; | ||
name = 'default'; | ||
} | ||
if (Bookshelf.Instances[name]) { | ||
throw new Error('A ' + name + ' instance of Bookshelf already exists'); | ||
} | ||
// If an object with this name already exists in `Knex.Instances`, we will | ||
// use that copy of `Knex` without trying to re-initialize. | ||
var Builder = (Knex[name] || Knex.Initialize(name, options)); | ||
if (name === 'default') { | ||
Target = Bookshelf.Instances['default'] = Bookshelf; | ||
} else { | ||
Target = Bookshelf.Instances[name] = {}; | ||
// Create a new `Bookshelf` instance for this database. | ||
_.extend(Target, _.omit(Bookshelf, 'Instances', 'Initialize', 'Knex', 'Transaction'), { | ||
Knex: Builder, | ||
Transaction: Builder.Transaction | ||
}); | ||
// Attach a new builder function that references the correct connection. | ||
_.each(['Model', 'Collection', 'EagerRelation'], function(item) { | ||
Target[item].prototype.builder = function(table) { | ||
return Target(table); | ||
}; | ||
}); | ||
} | ||
// Return the initialized instance. | ||
return Target; | ||
}; | ||
// Named instances of Bookshelf, presumably with different `Knex` | ||
// options, to initialize different databases. | ||
// The main instance being named "default"... | ||
Bookshelf.Instances = {}; | ||
module.exports = Bookshelf; | ||
}).call(this); |
@@ -7,3 +7,3 @@ { | ||
}, | ||
"version": "0.0.0", | ||
"version": "0.0.1", | ||
"description": "A promise based ORM in the style of Backbone.js", | ||
@@ -27,3 +27,3 @@ "main": "bookshelf.js", | ||
"backbone": "1.0.x", | ||
"knex": ">=0.0.0", | ||
"knex": ">=0.0.1", | ||
"underscore": "1.4.4", | ||
@@ -30,0 +30,0 @@ "q": "0.9.x", |
@@ -47,33 +47,15 @@ var Bookshelf = require('../bookshelf'); | ||
describe('convert', function() { | ||
describe('forge', function() { | ||
it('properly handles the inheritance chain', function() { | ||
var One = Backbone.Collection.extend({ | ||
customMethod: function(val) { return val || 1; } | ||
it('should create a new collection instance', function() { | ||
var User = Bookshelf.Model.extend({ | ||
tableName: 'users' | ||
}); | ||
var Two = One.extend({ | ||
secondMethod: function(val) { return One.prototype.customMethod.call(this, val || 2); } | ||
var Users = Bookshelf.Collection.extend({ | ||
model: User | ||
}); | ||
var Three = Two.extend({ | ||
thirdCustomMethod: function() { return this.secondMethod(3); } | ||
}); | ||
var Converted = Bookshelf.Collection.convert(Three, { | ||
test: function() { | ||
equal(this.thirdCustomMethod(Converted), 3); | ||
equal(this.secondMethod(), 2); | ||
equal(this.customMethod(), 1); | ||
} | ||
}); | ||
new Converted().test(); | ||
var users = Users.forge(); | ||
equal(users.tableName(), 'users'); | ||
}); | ||
it('breaks on invalid object conversions', function() { | ||
var Invalid = function() {}; | ||
Invalid.prototype = {testMethod: function() {}}; | ||
try { | ||
Bookshelf.Collection.convert(Invalid, {newMethod: function() {}}); | ||
} catch (e) { | ||
equal(e.toString(), 'Error: Only Backbone objects may be converted.'); | ||
} | ||
}); | ||
}); | ||
@@ -80,0 +62,0 @@ |
@@ -20,12 +20,2 @@ var Q = require('q'); | ||
// var base; | ||
// var Base = Bookshelf.Model.extend({ | ||
// tableName: 'authors' | ||
// }); | ||
// beforeEach(function() { | ||
// if (base) base.off(); | ||
// base = new Base(); | ||
// }); | ||
describe('extend/constructor/initialize', function() { | ||
@@ -69,33 +59,12 @@ | ||
describe('convert', function() { | ||
describe('forge', function() { | ||
it('properly handles the inheritance chain', function() { | ||
var One = Backbone.Model.extend({ | ||
customMethod: function(val) { return val || 1; } | ||
it('should create a new model instance', function() { | ||
var User = Bookshelf.Model.extend({ | ||
tableName: 'users' | ||
}); | ||
var Two = One.extend({ | ||
secondMethod: function(val) { return One.prototype.customMethod.call(this, val || 2); } | ||
}); | ||
var Three = Two.extend({ | ||
thirdCustomMethod: function() { return this.secondMethod(3); } | ||
}); | ||
var Converted = Bookshelf.Model.convert(Three, { | ||
test: function() { | ||
equal(this.thirdCustomMethod(Converted), 3); | ||
equal(this.secondMethod(), 2); | ||
equal(this.customMethod(), 1); | ||
} | ||
}); | ||
new Converted().test(); | ||
var user = User.forge(); | ||
equal(user.tableName, 'users'); | ||
}); | ||
it('breaks on invalid object conversions', function() { | ||
var Invalid = function() {}; | ||
Invalid.prototype = {testMethod: function() {}}; | ||
try { | ||
Bookshelf.Model.convert(Invalid, {newMethod: function() {}}); | ||
} catch (e) { | ||
equal(e.toString(), 'Error: Only Backbone objects may be converted.'); | ||
} | ||
}); | ||
}); | ||
@@ -373,8 +342,8 @@ | ||
var m1 = new Bookshelf.Model({id: 1}); | ||
m.timestamp(); | ||
m1.timestamp(); | ||
equal(_.isDate(m.get('created_at')), true); | ||
equal(_.isDate(m.get('updated_at')), true); | ||
equal(_.isEmpty(m1.get('created_at')), true); | ||
equal(_.isDate(m1.get('updated_at')), true); | ||
var ts = m.timestamp(); | ||
var ts2 = m1.timestamp(); | ||
equal(_.isDate(ts.created_at), true); | ||
equal(_.isDate(ts.updated_at), true); | ||
equal(_.isEmpty(ts2.created_at), true); | ||
equal(_.isDate(ts2.updated_at), true); | ||
}); | ||
@@ -381,0 +350,0 @@ }); |
@@ -20,6 +20,6 @@ var Q = require('q'); | ||
// Collections | ||
var Sites = Collections.Sites; | ||
var Admins = Collections.Admins; | ||
var Blogs = Collections.Blogs; | ||
var Posts = Collections.Posts; | ||
var Sites = Collections.Sites; | ||
var Admins = Collections.Admins; | ||
var Blogs = Collections.Blogs; | ||
var Posts = Collections.Posts; | ||
var Comments = Collections.Comment; | ||
@@ -26,0 +26,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 1 instance in 1 package
0
66356
1751
Updatedknex@>=0.0.1