waterline
Advanced tools
Comparing version 0.11.1 to 0.12.0
# Waterline Changelog | ||
### 0.12.0 | ||
* [Enhancement] Allows attribute definitions to contain a `meta` property that will be passed down to the adapter. This allows arbitrary information about an attribute to be passed down to interactions on the physical storage engine. Going forward any adapter specific migration information should be sent via the `meta` property. See [#1306](https://github.com/balderdashy/waterline/pull/1306) for more information. | ||
* [Enhancement] Allows for the use of `.select()` to build out projections in both top level queries and association queries. See [#1310](https://github.com/balderdashy/waterline/pull/1310) for more details and examples. | ||
* [Enhancement] Allow for the ability to pass in extra data to an adapter function using the `.meta()` option. This could be used for a variety of things inside custom adapters such as passing connections around for transactions or passing config values for muti-tenant functionality. For more details see [#1325](https://github.com/balderdashy/waterline/pull/1325). | ||
### 0.11.1 | ||
@@ -4,0 +12,0 @@ |
@@ -87,3 +87,3 @@ var _ = require('lodash'); | ||
for (var collection in options.collections) { | ||
this.loadCollection(_.cloneDeep(options.collections[collection])); | ||
this.loadCollection(options.collections[collection]); | ||
} | ||
@@ -164,2 +164,3 @@ | ||
schemas[collection.identity].definition = schema; | ||
schemas[collection.identity].attributes = collection._attributes; | ||
schemas[collection.identity].meta = meta; | ||
@@ -196,3 +197,4 @@ }); | ||
usedSchemas[identity] = results.buildCollectionSchemas[coll]; | ||
var schema = results.buildCollectionSchemas[coll]; | ||
usedSchemas[identity] = schema; | ||
}); | ||
@@ -199,0 +201,0 @@ |
@@ -13,3 +13,3 @@ /** | ||
// If an optimized createEach exists, use it, otherwise use an asynchronous loop with create() | ||
createEach: function(valuesList, cb) { | ||
createEach: function(valuesList, cb, metaContainer) { | ||
var self = this; | ||
@@ -31,3 +31,3 @@ var connName, | ||
if (hasOwnProperty(adapter, 'createEach')) { | ||
return adapter.createEach(connName, this.collection, valuesList, cb); | ||
return adapter.createEach(connName, this.collection, valuesList, cb, metaContainer); | ||
} | ||
@@ -53,3 +53,3 @@ } | ||
cb(); | ||
}); | ||
}, metaContainer); | ||
}, function(err) { | ||
@@ -62,3 +62,3 @@ if (err) return cb(err); | ||
// If an optimized findOrCreateEach exists, use it, otherwise use an asynchronous loop with create() | ||
findOrCreateEach: function(attributesToCheck, valuesList, cb) { | ||
findOrCreateEach: function(attributesToCheck, valuesList, cb, metaContainer) { | ||
var self = this; | ||
@@ -91,3 +91,3 @@ var connName; | ||
if (hasOwnProperty(adapter, 'findOrCreateEach')) { | ||
return adapter.findOrCreateEach(connName, this.collection, valuesList, cb); | ||
return adapter.findOrCreateEach(connName, this.collection, valuesList, cb, metaContainer); | ||
} | ||
@@ -130,3 +130,3 @@ } | ||
cb(null, model); | ||
}); | ||
}, metaContainer); | ||
}, function(err) { | ||
@@ -133,0 +133,0 @@ if (err) return cb(err); |
@@ -11,3 +11,3 @@ /** | ||
findOrCreate: function(criteria, values, cb) { | ||
findOrCreate: function(criteria, values, cb, metaContainer) { | ||
var self = this; | ||
@@ -33,3 +33,3 @@ var connName, | ||
if (hasOwnProperty(adapter, 'findOrCreate')) { | ||
return adapter.findOrCreate(connName, this.collection, values, cb); | ||
return adapter.findOrCreate(connName, this.collection, values, cb, metaContainer); | ||
} | ||
@@ -44,6 +44,6 @@ } | ||
self.create(values, cb); | ||
}); | ||
self.create(values, cb, metaContainer); | ||
}, metaContainer); | ||
} | ||
}; |
@@ -31,3 +31,3 @@ /** | ||
*/ | ||
join: function(criteria, cb) { | ||
join: function(criteria, cb, metaContainer) { | ||
@@ -53,3 +53,3 @@ // Normalize Arguments | ||
adapter.join(connName, this.collection, criteria, cb); | ||
adapter.join(connName, this.collection, criteria, cb, metaContainer); | ||
}, | ||
@@ -67,3 +67,3 @@ | ||
*/ | ||
create: function(values, cb) { | ||
create: function(values, cb, metaContainer) { | ||
@@ -75,3 +75,5 @@ var globalId = this.query.globalId; | ||
if (Array.isArray(values)) return this.createEach.call(this, values, cb); | ||
if (Array.isArray(values)) { | ||
return this.createEach.call(this, values, cb, metaContainer); | ||
} | ||
@@ -94,3 +96,3 @@ // Build Default Error Message | ||
else return cb(null, createdRecord); | ||
})); | ||
}), metaContainer); | ||
}, | ||
@@ -108,4 +110,3 @@ | ||
*/ | ||
find: function(criteria, cb) { | ||
find: function(criteria, cb, metaContainer) { | ||
// Normalize Arguments | ||
@@ -125,3 +126,3 @@ criteria = normalize.criteria(criteria); | ||
if (!adapter.find) return cb(new Error(err)); | ||
adapter.find(connName, this.collection, criteria, cb); | ||
adapter.find(connName, this.collection, criteria, cb, metaContainer); | ||
}, | ||
@@ -139,3 +140,3 @@ | ||
*/ | ||
findOne: function(criteria, cb) { | ||
findOne: function(criteria, cb, metaContainer) { | ||
@@ -162,3 +163,3 @@ // make shallow copy of criteria so original does not get modified | ||
criteria = normalize.criteria(criteria); | ||
return adapter.findOne(connName, this.collection, criteria, cb); | ||
return adapter.findOne(connName, this.collection, criteria, cb, metaContainer); | ||
} | ||
@@ -176,3 +177,3 @@ } | ||
cb(null, models); | ||
}); | ||
}, metaContainer); | ||
}, | ||
@@ -186,3 +187,3 @@ | ||
*/ | ||
count: function(criteria, cb) { | ||
count: function(criteria, cb, metaContainer) { | ||
var connName; | ||
@@ -210,3 +211,3 @@ | ||
if (hasOwnProperty(adapter, 'count')) return adapter.count(connName, this.collection, criteria, cb); | ||
if (hasOwnProperty(adapter, 'count')) return adapter.count(connName, this.collection, criteria, cb, metaContainer); | ||
@@ -217,3 +218,3 @@ this.find(criteria, function(err, models) { | ||
cb(err, count); | ||
}); | ||
}, metaContainer); | ||
}, | ||
@@ -229,3 +230,3 @@ | ||
*/ | ||
update: function(criteria, values, cb) { | ||
update: function(criteria, values, cb, metaContainer) { | ||
var globalId = this.query.globalId; | ||
@@ -259,3 +260,3 @@ | ||
return cb(null, updatedRecords); | ||
})); | ||
}), metaContainer); | ||
}, | ||
@@ -270,3 +271,3 @@ | ||
*/ | ||
destroy: function(criteria, cb) { | ||
destroy: function(criteria, cb, metaContainer) { | ||
@@ -286,5 +287,5 @@ // Normalize Arguments | ||
adapter.destroy(connName, this.collection, criteria, cb); | ||
adapter.destroy(connName, this.collection, criteria, cb, metaContainer); | ||
} | ||
}; |
@@ -16,3 +16,3 @@ /** | ||
// Must call stream.end() to complete stream | ||
stream: function(criteria, stream) { | ||
stream: function(criteria, stream, metaContainer) { | ||
@@ -32,5 +32,5 @@ // Normalize Arguments | ||
if (!hasOwnProperty(adapter, 'stream')) return stream.end(new Error(err)); | ||
adapter.stream(connName, this.collection, criteria, stream); | ||
adapter.stream(connName, this.collection, criteria, stream, metaContainer); | ||
} | ||
}; |
@@ -43,3 +43,3 @@ /** | ||
// Create a mapping of column names -> attribute names | ||
var columnNamesMap = _.reduce(self.query.waterline.schema[collectionName].attributes, function(memo, val, key) { | ||
var columnNamesMap = _.reduce(self.query.waterline.schema[collectionName].definition, function(memo, val, key) { | ||
// If the attribute has a custom column name, use it as the key for the mapping | ||
@@ -46,0 +46,0 @@ if (val.columnName) { |
@@ -28,3 +28,3 @@ /** | ||
this.adapter = this.adapter || {}; | ||
this._attributes = _.clone(this.attributes); | ||
this._attributes = this.attributes; | ||
this.connections = this.connections || {}; | ||
@@ -31,0 +31,0 @@ |
@@ -59,5 +59,28 @@ /** | ||
this.reservedProperties = ['defaultsTo', 'primaryKey', 'autoIncrement', 'unique', 'index', 'collection', 'dominant', 'through', | ||
'columnName', 'foreignKey', 'references', 'on', 'groupKey', 'model', 'via', 'size', | ||
'example', 'validationMessage', 'validations', 'populateSettings', 'onKey', 'protected']; | ||
// These properties are reserved and may not be used as validations | ||
this.reservedProperties = [ | ||
'defaultsTo', | ||
'primaryKey', | ||
'autoIncrement', | ||
'unique', | ||
'index', | ||
'collection', | ||
'dominant', | ||
'through', | ||
'columnName', | ||
'foreignKey', | ||
'references', | ||
'on', | ||
'groupKey', | ||
'model', | ||
'via', | ||
'size', | ||
'example', | ||
'validationMessage', | ||
'validations', | ||
'populateSettings', | ||
'onKey', | ||
'protected', | ||
'meta' | ||
]; | ||
@@ -64,0 +87,0 @@ |
@@ -24,10 +24,9 @@ /** | ||
createEach: function(valuesList, cb) { | ||
createEach: function(valuesList, cb, metaContainer) { | ||
var self = this; | ||
// Handle Deferred where it passes criteria first | ||
if (arguments.length === 3) { | ||
var args = Array.prototype.slice.call(arguments); | ||
cb = args.pop(); | ||
valuesList = args.pop(); | ||
if(_.isPlainObject(arguments[0]) && _.isArray(arguments[1])) { | ||
valuesList = arguments[1]; | ||
cb = arguments[2]; | ||
} | ||
@@ -56,3 +55,5 @@ | ||
// Create will take care of cloning values so original isn't mutated | ||
async.map(filteredValues, self.create.bind(self), cb); | ||
async.map(filteredValues, function(data, next) { | ||
self.create(data, next, metaContainer); | ||
}, cb); | ||
}, | ||
@@ -70,3 +71,3 @@ | ||
findOrCreateEach: function(criteria, valuesList, cb) { | ||
findOrCreateEach: function(criteria, valuesList, cb, metaContainer) { | ||
var self = this; | ||
@@ -166,3 +167,3 @@ | ||
}); | ||
}); | ||
}, metaContainer); | ||
}); | ||
@@ -169,0 +170,0 @@ } |
@@ -24,3 +24,3 @@ /** | ||
findOrCreate: function(criteria, values, cb) { | ||
findOrCreate: function(criteria, values, cb, metaContainer) { | ||
var self = this; | ||
@@ -57,3 +57,9 @@ | ||
// Try a find first. | ||
this.find(criteria).exec(function(err, results) { | ||
var q = this.find(criteria); | ||
if(metaContainer) { | ||
q.meta(metaContainer); | ||
} | ||
q.exec(function(err, results) { | ||
if (err) return cb(err); | ||
@@ -72,3 +78,9 @@ | ||
// Create a new record if nothing is found. | ||
self.create(values).exec(function(err, result) { | ||
var q2 = self.create(values); | ||
if(metaContainer) { | ||
q2.meta(metaContainer); | ||
} | ||
q2.exec(function(err, result) { | ||
if (err) return cb(err); | ||
@@ -75,0 +87,0 @@ return cb(null, result); |
@@ -108,3 +108,3 @@ /** | ||
} | ||
// Normalize sub-criteria | ||
@@ -153,28 +153,2 @@ try { | ||
////////////////////////////////////////////////////////////////////// | ||
// (there has been significant progress made towards both of these /// | ||
// goals-- contact @mikermcneil if you want to help) ///////////////// | ||
////////////////////////////////////////////////////////////////////// | ||
// TODO: | ||
// Create synonym for `.populate()` syntax using criteria object | ||
// syntax. i.e. instead of using `joins` key in criteria object | ||
// at the app level. | ||
////////////////////////////////////////////////////////////////////// | ||
// TODO: | ||
// Support Mongoose-style `foo.bar.baz` syntax for nested `populate`s. | ||
// (or something comparable.) | ||
// One solution would be: | ||
// .populate({ | ||
// friends: { | ||
// where: { name: 'mike' }, | ||
// populate: { | ||
// dentist: { | ||
// where: { name: 'rob' } | ||
// } | ||
// } | ||
// } | ||
// }, optionalCriteria ) | ||
//////////////////////////////////////////////////////////////////// | ||
// Grab the key being populated to check if it is a has many to belongs to | ||
@@ -192,3 +166,2 @@ // If it's a belongs_to the adapter needs to know that it should replace the foreign key | ||
childKey: attr.on, | ||
select: Object.keys(this._context.waterline.schema[attr.references].attributes), | ||
alias: keyName, | ||
@@ -202,5 +175,15 @@ removeParentKey: !!parentKey.model, | ||
var select = []; | ||
Object.keys(this._context.waterline.schema[attr.references].attributes).forEach(function(key) { | ||
var obj = self._context.waterline.schema[attr.references].attributes[key]; | ||
if (!hasOwnProperty(obj, 'columnName')) { | ||
var customSelect = criteria.select && _.isArray(criteria.select); | ||
_.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { | ||
// Ignore virtual attributes | ||
if(_.has(val, 'collection')) { | ||
return; | ||
} | ||
// Check if the user has defined a custom select and if so normalize it | ||
if(customSelect && !_.includes(criteria.select, key)) { | ||
return; | ||
} | ||
if (!_.has(val, 'columnName')) { | ||
select.push(key); | ||
@@ -210,5 +193,21 @@ return; | ||
select.push(obj.columnName); | ||
select.push(val.columnName); | ||
}); | ||
// Ensure the PK and FK on the child are always selected - otherwise things | ||
// like the integrator won't work correctly | ||
var childPk; | ||
_.each(this._context.waterline.schema[attr.references].attributes, function(val, key) { | ||
if(_.has(val, 'primaryKey') && val.primaryKey) { | ||
childPk = val.columnName || key; | ||
} | ||
}); | ||
select.push(childPk); | ||
// Add the foreign key for collections | ||
if(join.collection) { | ||
select.push(attr.on); | ||
} | ||
join.select = select; | ||
@@ -234,9 +233,32 @@ | ||
if (reference && hasOwnProperty(attr, 'on')) { | ||
// Build out the second join object that will link a junction table with the | ||
// values being populated | ||
var selects = _.map(_.keys(this._context.waterline.schema[reference.references].attributes), function(attr) { | ||
var expandedAttr = self._context.waterline.schema[reference.references].attributes[attr]; | ||
return expandedAttr.columnName || attr; | ||
var selects = []; | ||
_.each(this._context.waterline.schema[reference.references].attributes, function(val, key) { | ||
// Ignore virtual attributes | ||
if(_.has(val, 'collection')) { | ||
return; | ||
} | ||
// Check if the user has defined a custom select and if so normalize it | ||
if(customSelect && !_.includes(criteria.select, key)) { | ||
return; | ||
} | ||
if (!_.has(val, 'columnName')) { | ||
selects.push(key); | ||
return; | ||
} | ||
selects.push(val.columnName); | ||
}); | ||
// Ensure the PK and FK are always selected - otherwise things like the | ||
// integrator won't work correctly | ||
_.each(this._context.waterline.schema[reference.references].attributes, function(val, key) { | ||
if(_.has(val, 'primaryKey') && val.primaryKey) { | ||
childPk = val.columnName || key; | ||
} | ||
}); | ||
selects.push(childPk); | ||
join = { | ||
@@ -247,3 +269,3 @@ parent: attr.references, | ||
childKey: reference.on, | ||
select: selects, | ||
select: _.uniq(selects), | ||
alias: keyName, | ||
@@ -262,4 +284,6 @@ junctionTable: true, | ||
joins[1].criteria = criteria; | ||
joins[1].criteria.select = join.select; | ||
} else if (criteria) { | ||
joins[0].criteria = criteria; | ||
joins[0].criteria.select = join.select; | ||
} | ||
@@ -282,2 +306,21 @@ | ||
/** | ||
* Add projections to the parent | ||
* | ||
* @param {Array} attributes to select | ||
* @return this | ||
*/ | ||
Deferred.prototype.select = function(attributes) { | ||
if(!_.isArray(attributes)) { | ||
attributes = [attributes]; | ||
} | ||
var select = this._criteria.select || []; | ||
select = select.concat(attributes); | ||
this._criteria.select = _.uniq(select); | ||
return this; | ||
}; | ||
/** | ||
* Add a Where clause to the criteria object | ||
@@ -472,2 +515,11 @@ * | ||
/** | ||
* Pass metadata down to the adapter that won't be processed or touched by Waterline | ||
*/ | ||
Deferred.prototype.meta = function(data) { | ||
this._meta = data; | ||
return this; | ||
}; | ||
/** | ||
* Execute a Query using the method passed into the | ||
@@ -481,5 +533,4 @@ * constuctor. | ||
Deferred.prototype.exec = function(cb) { | ||
if (!cb) { | ||
console.log(new Error('Error: No Callback supplied, you must define a callback.').message); | ||
console.log('Error: No Callback supplied, you must define a callback.'); | ||
return; | ||
@@ -495,2 +546,7 @@ } | ||
// If there is a meta value, throw it on the very end | ||
if(this._meta) { | ||
args.push(this._meta); | ||
} | ||
// Pass control to the adapter with the appropriate arguments. | ||
@@ -497,0 +553,0 @@ this._method.apply(this._context, args); |
@@ -20,3 +20,3 @@ /** | ||
module.exports = function(criteria, options, cb) { | ||
module.exports = function(criteria, options, cb, metaContainer) { | ||
var usage = utils.capitalize(this.identity) + '.count([criteria],[options],callback)'; | ||
@@ -60,3 +60,3 @@ | ||
this.adapter.count(criteria, cb); | ||
this.adapter.count(criteria, cb, metaContainer); | ||
}; |
@@ -22,3 +22,3 @@ /** | ||
module.exports = function(values, cb) { | ||
module.exports = function(values, cb, metaContainer) { | ||
@@ -28,8 +28,8 @@ var self = this; | ||
// Handle Deferred where it passes criteria first | ||
if (arguments.length === 3) { | ||
var args = Array.prototype.slice.call(arguments); | ||
cb = args.pop(); | ||
values = args.pop(); | ||
if(_.isPlainObject(arguments[0]) && (_.isPlainObject(arguments[1]) || _.isArray(arguments[1]))) { | ||
values = arguments[1]; | ||
cb = arguments[2]; | ||
} | ||
// Loop through values and pull out any buffers before cloning | ||
@@ -64,3 +64,3 @@ var bufferValues = {}; | ||
if (Array.isArray(values)) { | ||
return this.createEach(values, cb); | ||
return this.createEach(values, cb, metaContainer); | ||
} | ||
@@ -77,4 +77,4 @@ | ||
if (err) return cb(err); | ||
createValues.call(self, valuesObject, cb); | ||
}); | ||
createValues.call(self, valuesObject, cb, metaContainer); | ||
}, metaContainer); | ||
}); | ||
@@ -120,3 +120,3 @@ }; | ||
function createBelongsTo(valuesObject, cb) { | ||
function createBelongsTo(valuesObject, cb, metaContainer) { | ||
var self = this; | ||
@@ -153,2 +153,6 @@ | ||
if(metaContainer) { | ||
query.meta(metaContainer); | ||
} | ||
query.exec(function(err, val) { | ||
@@ -200,3 +204,3 @@ if (err) return next(err); | ||
function createValues(valuesObject, cb) { | ||
function createValues(valuesObject, cb, metaContainer) { | ||
var self = this; | ||
@@ -259,3 +263,3 @@ var date; | ||
}); | ||
}, metaContainer); | ||
} |
@@ -23,3 +23,3 @@ /** | ||
module.exports = function(criteria, cb) { | ||
module.exports = function(criteria, cb, metaContainer) { | ||
var self = this; | ||
@@ -114,3 +114,9 @@ var pk; | ||
criteria[refKey] = mappedValues; | ||
collection.destroy(criteria).exec(next); | ||
var q = collection.destroy(criteria); | ||
if(metaContainer) { | ||
q.meta(metaContainer); | ||
} | ||
q.exec(next); | ||
} else { | ||
@@ -134,4 +140,4 @@ return next(); | ||
}); | ||
}, metaContainer); | ||
}); | ||
}; |
@@ -8,4 +8,4 @@ /** | ||
module.exports = function(collection, fk, pk, cb) { | ||
this._adapter.join(collection, fk, pk, cb); | ||
module.exports = function(collection, fk, pk, cb, metaContainer) { | ||
this._adapter.join(collection, fk, pk, cb, metaContainer); | ||
}; |
@@ -25,3 +25,3 @@ /** | ||
module.exports = function(criteria, values, cb) { | ||
module.exports = function(criteria, values, cb, metaContainer) { | ||
@@ -59,5 +59,5 @@ var self = this; | ||
if (err) return cb(err); | ||
updateRecords.call(self, valuesObject, cb); | ||
updateRecords.call(self, valuesObject, cb, metaContainer); | ||
}); | ||
}); | ||
}, metaContainer); | ||
}; | ||
@@ -107,3 +107,3 @@ | ||
function createBelongsTo(valuesObject, cb) { | ||
function createBelongsTo(valuesObject, cb, metaContainer) { | ||
var self = this; | ||
@@ -143,2 +143,6 @@ | ||
if(metaContainer) { | ||
query.meta(metaContainer); | ||
} | ||
query.exec(function(err, val) { | ||
@@ -192,3 +196,3 @@ if (err) return next(err); | ||
function updateRecords(valuesObject, cb) { | ||
function updateRecords(valuesObject, cb, metaContainer) { | ||
var self = this; | ||
@@ -243,3 +247,3 @@ | ||
}); | ||
}, metaContainer); | ||
} | ||
@@ -246,0 +250,0 @@ |
@@ -28,3 +28,3 @@ /** | ||
findOne: function(criteria, cb) { | ||
findOne: function(criteria, cb, metaContainer) { | ||
var self = this; | ||
@@ -57,2 +57,13 @@ | ||
// If a projection is being used, ensure that the Primary Key is included | ||
if(criteria.select) { | ||
_.each(this._schema.schema, function(val, key) { | ||
if (_.has(val, 'primaryKey') && val.primaryKey) { | ||
criteria.select.push(key); | ||
} | ||
}); | ||
criteria.select = _.uniq(criteria.select); | ||
} | ||
// serialize populated object | ||
@@ -77,3 +88,3 @@ if (criteria.joins) { | ||
// Build up an operations set | ||
var operations = new Operations(self, criteria, 'findOne'); | ||
var operations = new Operations(self, criteria, 'findOne', metaContainer); | ||
@@ -197,5 +208,4 @@ // Run the operations | ||
find: function(criteria, options, cb) { | ||
find: function(criteria, options, cb, metaContainer) { | ||
var self = this; | ||
var usage = utils.capitalize(this.identity) + '.find([criteria],[options]).exec(callback|switchback)'; | ||
@@ -206,8 +216,22 @@ | ||
criteria = null; | ||
options = null; | ||
if(arguments.length === 1) { | ||
options = null; | ||
} | ||
} | ||
// If options is a function, we want to check for any more values before nulling | ||
// them out or overriding them. | ||
if (typeof options === 'function') { | ||
cb = options; | ||
options = null; | ||
// If cb also exists it means there is a metaContainer value | ||
if (cb) { | ||
metaContainer = cb; | ||
cb = options; | ||
options = null; | ||
} else { | ||
cb = options; | ||
options = null; | ||
} | ||
} | ||
@@ -248,2 +272,13 @@ | ||
// If a projection is being used, ensure that the Primary Key is included | ||
if(criteria.select) { | ||
_.each(this._schema.schema, function(val, key) { | ||
if (_.has(val, 'primaryKey') && val.primaryKey) { | ||
criteria.select.push(key); | ||
} | ||
}); | ||
criteria.select = _.uniq(criteria.select); | ||
} | ||
// Transform Search Criteria | ||
@@ -267,3 +302,3 @@ if (!self._transformer) { | ||
// Build up an operations set | ||
var operations = new Operations(self, criteria, 'find'); | ||
var operations = new Operations(self, criteria, 'find', metaContainer); | ||
@@ -330,3 +365,2 @@ // Run the operations | ||
// Serialize values coming from an in-memory join before modelizing | ||
var _results = []; | ||
results.forEach(function(res) { | ||
@@ -333,0 +367,0 @@ |
@@ -19,3 +19,3 @@ | ||
var Operations = module.exports = function(context, criteria, parent) { | ||
var Operations = module.exports = function(context, criteria, parent, metaContainer) { | ||
@@ -34,2 +34,4 @@ // Build up a cache | ||
this.metaContainer = metaContainer; | ||
// Hold a default value for pre-combined results (native joins) | ||
@@ -124,4 +126,2 @@ this.preCombined = false; | ||
Operations.prototype._buildOperations = function _buildOperations() { | ||
var self = this; | ||
var operations = []; | ||
@@ -222,3 +222,3 @@ | ||
localOpts.forEach(function(localOpt) { | ||
if (localOpt.join.child != join.parent) return; | ||
if (localOpt.join.child !== join.parent) return; | ||
localOpt.child = operation; | ||
@@ -288,3 +288,2 @@ child = true; | ||
delete tmpCriteria.joins; | ||
connectionName = this.context.adapterDictionary[this.parent]; | ||
@@ -416,3 +415,3 @@ | ||
// Run the operation | ||
collection.adapter[method](criteria, cb); | ||
collection.adapter[method](criteria, cb, this.metaContainer); | ||
@@ -514,4 +513,2 @@ }; | ||
} | ||
userCriteria = userCriteria.where; | ||
} | ||
@@ -521,3 +518,3 @@ } | ||
criteria = _.merge(userCriteria, criteria); | ||
criteria = _.merge(userCriteria, { where: criteria }); | ||
} | ||
@@ -681,4 +678,2 @@ | ||
delete userCriteria.where; | ||
} else { | ||
userCriteria = userCriteria.where; | ||
} | ||
@@ -688,6 +683,9 @@ } | ||
delete userCriteria.sort; | ||
criteria = _.extend(criteria, userCriteria); | ||
delete userCriteria.skip; | ||
delete userCriteria.limit; | ||
criteria = _.merge({}, userCriteria, { where: criteria }); | ||
} | ||
criteria = normalize.criteria({ where: criteria }); | ||
criteria = normalize.criteria(criteria); | ||
@@ -694,0 +692,0 @@ // Empty the cache for the join table so we can only add values used |
@@ -19,3 +19,3 @@ /** | ||
stream: function(criteria, transformation) { | ||
stream: function(criteria, transformation, metaContainer) { | ||
var self = this; | ||
@@ -43,3 +43,3 @@ | ||
// Trigger Adapter Method | ||
self.adapter.stream(criteria, stream); | ||
self.adapter.stream(criteria, stream, metaContainer); | ||
}); | ||
@@ -46,0 +46,0 @@ |
@@ -8,3 +8,3 @@ var _ = require('lodash'); | ||
var normalize = module.exports = { | ||
module.exports = { | ||
@@ -182,4 +182,18 @@ // Expand Primary Key criteria into objects | ||
if (hop(criteria, 'where') && criteria.where !== null && hop(criteria.where, 'select')) { | ||
criteria.select = _.clone(criteria.where.select); | ||
if (hop(criteria, 'where') && criteria.where !== null && hop(criteria.where, 'select') || hop(criteria, 'select')) { | ||
if(criteria.where.select) { | ||
criteria.select = _.clone(criteria.where.select); | ||
} | ||
// If the select contains a '*' then remove the whole projection, a '*' | ||
// will always return all records. | ||
if(!_.isArray(criteria.select)) { | ||
criteria.select = [criteria.select]; | ||
} | ||
if(_.includes(criteria.select, '*')) { | ||
delete criteria.select; | ||
} | ||
delete criteria.where.select; | ||
@@ -186,0 +200,0 @@ } |
{ | ||
"name": "waterline", | ||
"description": "An ORM for Node.js and the Sails framework", | ||
"version": "0.11.1", | ||
"version": "0.12.0", | ||
"homepage": "http://github.com/balderdashy/waterline", | ||
@@ -6,0 +6,0 @@ "contributors": [ |
520
README.md
@@ -11,3 +11,3 @@ # [<img title="waterline-logo" src="http://i.imgur.com/3Xqh6Mz.png" width="810px" alt="Waterline logo"/>](https://github.com/balderdashy/waterline) | ||
It provides a uniform API for accessing stuff from different kinds of databases, protocols, and 3rd party APIs. That means you write the same code to get and store things like users, whether they live in Redis, mySQL, LDAP, MongoDB, or Postgres. | ||
It provides a uniform API for accessing stuff from different kinds of databases, protocols, and 3rd party APIs. That means you write the same code to get and store things like users, whether they live in Redis, MySQL, MongoDB, or Postgres. | ||
@@ -26,37 +26,4 @@ Waterline strives to inherit the best parts of ORMs like ActiveRecord, Hibernate, and Mongoose, but with a fresh perspective and emphasis on modularity, testability, and consistency across adapters. | ||
## Example | ||
#### Using with Sails.js | ||
Waterline was extracted from the [Sails](https://github.com/balderdashy/sails) framework and is the default ORM used in Sails. For more information on using Waterline in your Sails App view the [Sails Docs](http://sailsjs.org). | ||
For examples of how to use with frameworks such as [Express](http://expressjs.com/) look in the [Example](https://github.com/balderdashy/waterline/tree/master/example) folder. | ||
#### Usage (standalone) | ||
```javascript | ||
var Waterline = require('waterline'); | ||
// Define your collection (aka model) | ||
var User = Waterline.Collection.extend({ | ||
attributes: { | ||
firstName: { | ||
type: 'string', | ||
required: true | ||
}, | ||
lastName: { | ||
type: 'string', | ||
required: true | ||
} | ||
} | ||
}); | ||
``` | ||
## Overview | ||
### Adapters Concept | ||
Waterline uses the concept of an Adapter to translate a predefined set of methods into a query that can be understood by your data store. Adapters allow you to use various datastores such as MySQL, PostgreSQL, MongoDB, Redis, etc. and have a clear API for working with your model data. | ||
@@ -66,4 +33,2 @@ | ||
**NOTE:** When using custom adapter methods the features of Waterline are not used. You no longer get the Lifecycle Callbacks and Validations as you would when using a defined Waterline method. | ||
You may also supply an array of adapters and Waterline will map out the methods so they are both mixed in. It works similar to Underscore's [Extend](http://underscorejs.org/#extend) method where the last item in the array will override any methods in adapters before it. This allows you to mixin both the traditional CRUD adapters such as MySQL with specialized adapters such as Twilio and have both types of methods available. | ||
@@ -92,477 +57,23 @@ | ||
## Collection | ||
A [Collection](https://github.com/balderdashy/waterline/blob/master/lib/waterline/collection/index.js) is the main object used in Waterline. It defines the layout/schema of your data along with any validations and instance methods you create. | ||
## Support | ||
Need help or have a question? | ||
- [StackOverflow](http://stackoverflow.com/questions/tagged/waterline) | ||
- [Gitter Chat Room](https://gitter.im/balderdashy/sails) | ||
To create a new collection you extend `Waterline.Collection` and add in any properties you need. | ||
#### options | ||
## Issue Submission | ||
Please read the [issue submission guidelines](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md#opening-issues) before opening a new issue. | ||
Available options are: | ||
Waterline and Sails are composed of a [number of different sub-projects](https://github.com/balderdashy/sails/blob/master/MODULES.md), many of which have their own dedicated repository. If you suspect an issue in one of these sub-modules, you can find its repo on the [organization](https://github.com/balderdashy) page, or in [MODULES.md](https://github.com/balderdashy/sails/blob/master/MODULES.md). Click [here](https://github.com/balderdashy/waterline/search?q=&type=Issues) to search/post issues in this repository. | ||
- `tableName` Define a custom table name to store the models | ||
- `adapter` the name of the adapter you would like to use for this collection | ||
- `schema` Set schema true/false to only allow fields defined in `attributes` to be saved. Only for schemaless adapters. | ||
- `attributes` A hash of attributes to be defined for a model | ||
- `autoCreatedAt` and `autoUpdatedAt` Set false to prevent creating `createdAt` and `updatedAt` properties in your model | ||
- `autoPK` Set false to prevent creating `id`. By default `id` will be created as index with auto increment | ||
- [lifecycle callbacks](#lifecycle-callbacks) | ||
- any other class method you define! | ||
#### Attributes | ||
## Feature Requests | ||
If you have an idea for a new feature, please feel free to submit it as a pull request to the backlog section of the [ROADMAP.md](https://github.com/balderdashy/waterline/blob/master/ROADMAP.md) file in this repository. | ||
The following attribute types are currently available: | ||
- string | ||
- text | ||
- integer | ||
- float | ||
- date | ||
- time | ||
- datetime | ||
- boolean | ||
- binary | ||
- array | ||
- json | ||
## Contribute | ||
Please carefully read our [contribution guide](https://github.com/balderdashy/sails/blob/master/CONTRIBUTING.md) before submitting a pull request with code changes. | ||
#### Example Collection | ||
```javascript | ||
var User = Waterline.Collection.extend({ | ||
// Define a custom table name | ||
tableName: 'user', | ||
// Set schema true/false for adapters that support schemaless | ||
schema: true, | ||
// Define an adapter to use | ||
adapter: 'postgresql', | ||
// Define attributes for this collection | ||
attributes: { | ||
firstName: { | ||
type: 'string', | ||
// also accepts any validations | ||
required: true | ||
}, | ||
lastName: { | ||
type: 'string', | ||
required: true, | ||
maxLength: 20 | ||
}, | ||
email: { | ||
// Special types are allowed, they are used in validations and | ||
// set as a string when passed to an adapter | ||
type: 'email', | ||
required: true | ||
}, | ||
age: { | ||
type: 'integer', | ||
min: 18 | ||
}, | ||
// You can also define instance methods here | ||
fullName: function() { | ||
return this.firstName + ' ' + this.lastName | ||
} | ||
}, | ||
/** | ||
* Lifecycle Callbacks | ||
* | ||
* Run before and after various stages: | ||
* | ||
* beforeValidate | ||
* afterValidate | ||
* beforeUpdate | ||
* afterUpdate | ||
* beforeCreate | ||
* afterCreate | ||
* beforeDestroy | ||
* afterDestroy | ||
*/ | ||
beforeCreate: function(values, cb) { | ||
// An example encrypt function defined somewhere | ||
encrypt(values.password, function(err, password) { | ||
if(err) return cb(err); | ||
values.password = password; | ||
cb(); | ||
}); | ||
}, | ||
// Class Method | ||
doSomething: function() { | ||
// Do something here | ||
} | ||
}); | ||
``` | ||
Now that a collection is defined we can instantiate it and begin executing queries against it. All Collections take `options` and `callback` arguments. | ||
Options will be made up of: | ||
- `tableName`, used if not defined in a Collection definition | ||
- `adapters` object that specifies each adapter, either custom definitions or from NPM | ||
```javascript | ||
var postgres = require('sails-postgresql'); | ||
new User({ tableName: 'foobar', adapters: { postgresql: postgres }}, function(err, Model) { | ||
// We now have an instantiated collection to execute queries against | ||
Model.find() | ||
.where({ age: 21 }) | ||
.limit(10) | ||
.exec(function(err, users) { | ||
// Now we have an array of users | ||
}); | ||
}); | ||
``` | ||
## Model | ||
Each result that gets returned from a Waterline query will be an instance of [Model](https://github.com/balderdashy/waterline/blob/master/lib/waterline/model/index.js). This will add in any instance methods defined in your collection along with some CRUD helper methods. View the [Core Instance Methods](https://github.com/balderdashy/waterline/blob/master/lib/waterline/model/index.js) to see how they are implemented. | ||
Default CRUD instance methods: | ||
- save | ||
- destroy | ||
- toObject | ||
- toJSON | ||
If you would like to filter records and remove certain attributes you can override the `toJSON` method like so: | ||
```javascript | ||
var user = Waterline.Collection.extend({ | ||
attributes: { | ||
name: 'string', | ||
password: 'string', | ||
// Override toJSON instance method | ||
toJSON: function() { | ||
var obj = this.toObject(); | ||
delete obj.password; | ||
return obj; | ||
} | ||
} | ||
}); | ||
// Then on an instantiated user: | ||
user.find({ id: 1 }).exec(function(err, model) { | ||
return model.toJSON(); // Will return only the name | ||
}); | ||
``` | ||
## Query Methods | ||
Queries can be run with either a callback interface or with a deferred object. For building complicated queries, the deferred object method is the best choice. For convenience, promises are supported by default. | ||
**Callback Method** | ||
```javascript | ||
User.findOne({ id: 1 }, function(err, user) { | ||
// Do stuff here | ||
}); | ||
``` | ||
**Deferred Object Method** | ||
```javascript | ||
User.find() | ||
.where({ id: { '>': 100 }}) | ||
.where({ age: 21 }) | ||
.limit(100) | ||
.sort('name') | ||
.exec(function(err, users) { | ||
// Do stuff here | ||
}); | ||
``` | ||
**Promises** | ||
```javascript | ||
User.findOne() | ||
.where({ id: 2 }) | ||
.then(function(user){ | ||
var comments = Comment.find({userId: user.id}).then(function(comments){ | ||
return comments; | ||
}); | ||
return [user.id, user.friendsList, comments]; | ||
}).spread(function(userId, friendsList, comments){ | ||
// Promises are awesome! | ||
}).catch(function(err){ | ||
// An error occurred | ||
}) | ||
``` | ||
Promises use the [Bluebird library](https://github.com/petkaantonov/bluebird), so anything you do after the first `then` call (or `spread`, or `catch`), will be a complete Bluebird promise object. Remember, you must end the query somehow (by calling `then` or one of the other functions) in order to complete the database request. | ||
Each of the following basic methods are available by default on a Collection instance: | ||
- findOne | ||
- find | ||
- create | ||
- update | ||
- destroy | ||
- count | ||
In addition you also have the following helper methods: | ||
- createEach | ||
- findOrCreateEach (*DEPRECATED*) | ||
- findOrCreate | ||
- findOneLike | ||
- findLike | ||
- startsWith | ||
- endsWith | ||
- contains | ||
Based on your Collection attributes you also have dynamic finders. So given a `name` attribute the following queries will be available: | ||
- findOneByName | ||
- findOneByNameIn | ||
- findOneByNameLike | ||
- findByName | ||
- findByNameIn | ||
- findByNameLike | ||
- countByName | ||
- countByNameIn | ||
- countByNameLike | ||
- nameStartsWith | ||
- nameEndsWith | ||
- nameContains | ||
## Pagination | ||
In addition to the other find methods, there are a few helper methods to take care of pagination: | ||
- skip | ||
- limit | ||
- paginate | ||
Skip takes an integer and can be used to skip records: | ||
``` javascript | ||
User.find().skip(20); | ||
``` | ||
Limit takes an integer and limits the amount of records returned: | ||
``` javascript | ||
User.find().limit(10); | ||
``` | ||
And put together they create the ability to paginate through records as you would pages. For example, if I wanted 'page 2' of a given record set, and I only want to see 10 records at a time, I know that I need to ```skip(10)``` and ```limit(10)``` like so: | ||
``` javascript | ||
User.find().skip(10).limit(10); | ||
``` | ||
But, while we are thinking in terms of pagination, or pages, it might be easier to use the final helper - paginate: | ||
``` javascript | ||
User.find().paginate({page: 2, limit: 10}); | ||
``` | ||
Paginate has several options: | ||
- ```paginate()``` defaults options to ```{page: 0, limit: 10}``` | ||
- ```paginate({page: 2})``` uses ```{page: 2, limit: 10}``` as the options | ||
- ```paginate({limit: 20})``` uses ```{page: 0, limit: 20}``` as the options | ||
- ```paginate({page: 1, limit: 20})``` uses ```{page: 1, limit: 20}``` as the options | ||
It returns a deferred object so that you can continue to chain your helpers. | ||
## Sorting | ||
Sorting can be performed in the deferred object query method `sort` or by adding the sort key into the criteria object. | ||
Simply specify an attribute name for natural (ascending) sort, or specify an `asc` or `desc` flag for ascending or descending orders respectively. | ||
```javascript | ||
User.find() | ||
.sort('roleId asc') | ||
.sort({ createdAt: 'desc' }) | ||
.exec(function(err, users) { | ||
// Do stuff here | ||
}); | ||
``` | ||
## Validations | ||
Validations are handled by [Anchor](https://github.com/sailsjs/anchor) which is based off of [Node Validate](https://github.com/chriso/node-validator) and supports most of the properties in node-validate. | ||
For a full list of validations see: [Anchor Validations](https://github.com/balderdashy/anchor/blob/master/lib/match/rules.js). | ||
Validations are defined directly in you Collection attributes. In addition you may set the attribute `type` to any supported Anchor type and Waterline will build a validation and set the schema type as a string for that attribute. | ||
Validation rules may be defined as simple values or functions (both sync and async) that return the value to test against. | ||
```javascript | ||
var User = Waterline.Collection.extend({ | ||
attributes: { | ||
firstName: { | ||
type: 'string', | ||
required: true, | ||
minLength: 5, | ||
maxLength: 15 | ||
}, | ||
lastName: { | ||
type: 'string', | ||
required: true, | ||
minLength: 5, | ||
maxLength: 100 | ||
}, | ||
age: { | ||
type: 'integer', | ||
after: '12/12/2001' | ||
}, | ||
website: { | ||
type: 'string', | ||
// Validation rule may be defined as a function. Here, an async function is mimicked. | ||
contains: function(cb) { | ||
setTimeout(function() { | ||
cb('http://'); | ||
}, 1); | ||
} | ||
} | ||
} | ||
}); | ||
var Event = Waterline.Collection.extend({ | ||
attributes: { | ||
startDate: { | ||
type: 'date', | ||
// Validation rule functions allow you to validate values against other attributes | ||
before: function() { | ||
return this.endDate; | ||
} | ||
}, | ||
endDate: { | ||
type: 'date', | ||
after: function() { | ||
return this.startDate; | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
## Custom Types | ||
You can define your own types and their validation with the `types` hash. It's possible to access and compare values to other attributes. | ||
```javascript | ||
var User = Waterline.Collection.extend({ | ||
types: { | ||
point: function(latlng){ | ||
return latlng.x && latlng.y | ||
}, | ||
password: function(password) { | ||
return password === this.passwordConfirmation; | ||
}); | ||
}, | ||
attributes: { | ||
firstName: { | ||
type: 'string', | ||
required: true, | ||
minLength: 5, | ||
maxLength: 15 | ||
}, | ||
location: { | ||
// Note, that the base type (json) still has to be defined | ||
type: 'json', | ||
point: true | ||
}, | ||
password: { | ||
type: 'string', | ||
password: true | ||
}, | ||
passwordConfirmation: { | ||
type: 'string' | ||
} | ||
} | ||
}); | ||
``` | ||
## Indexing | ||
You can add an index property to any attribute to create an index if your adapter supports it. This comes in handy when performing repeated queries | ||
against a key. | ||
```javascript | ||
var User = Waterline.Collection.extend({ | ||
attributes: { | ||
serviceID: { | ||
type: 'integer', | ||
index: true | ||
} | ||
} | ||
}); | ||
``` | ||
Currently Waterline doesn't support multi-column indexes in the attributes definition. If you would like to build any sort of special index you will still | ||
need to build that manually. Also note that when adding a `unique` property to an attribute, an index will automatically be created for that attribute. | ||
There is currently an issue with adding indexes to string fields. Because Waterline performs its queries in a case insensitive manner, we are unable to use the index on a string attribute. | ||
There are some workarounds being discussed but nothing is implemented so far. This will be updated in the near future to fully support indexes on strings. | ||
## Lifecycle Callbacks | ||
Lifecycle callbacks are functions you can define to run at certain times in a query. They are useful for mutating data before creating or generating properties before they are validated. | ||
**Callbacks run on Create** | ||
- beforeValidate / *fn(values, cb)* | ||
- afterValidate / *fn(values, cb)* | ||
- beforeCreate / *fn(values, cb)* | ||
- afterCreate / *fn(newlyInsertedRecord, cb)* | ||
**Callbacks run on Update** | ||
- beforeValidate / *fn(valuesToUpdate, cb)* | ||
- afterValidate / *fn(valuesToUpdate, cb)* | ||
- beforeUpdate / *fn(valuesToUpdate, cb)* | ||
- afterUpdate / *fn(updatedRecord, cb)* | ||
**Callbacks run on Destroy** | ||
- beforeDestroy / *fn(criteria, cb)* | ||
- afterDestroy / *fn(cb)* | ||
## Associations | ||
With Waterline you can associate models with other models across all data stores. This means that | ||
your users can live in PostgreSQL and their photos can live in MongoDB and you can interact with | ||
the data as if they lived together on the same database. You can also have associations that | ||
live on seperate connections or in different databases within the same adapter. Read more about | ||
associations [here](https://github.com/balderdashy/waterline-docs/blob/master/models/associations/associations.md). | ||
## Tests | ||
@@ -584,1 +95,8 @@ | ||
And have a look at `coverage/lcov-report/index.html`. | ||
## License | ||
[MIT License](http://sails.mit-license.org/) Copyright © 2012-2016 Balderdash Design Co. | ||
 |
@@ -9,3 +9,3 @@ # Waterline Roadmap | ||
## v0.12 | ||
## v0.13 | ||
@@ -16,4 +16,2 @@ This section includes the main features, enhancements, and other improvements tentatively planned or already implemented for the v0.11 release of Waterline. Note that this is by no means a comprehensive changelog or release plan and may exclude important additions, bug fixes, and documentation tasks; it is just a reference point. Please also realize that the following notes may be slightly out of date-- until the release is finalized, API changes, deprecation announcements, additions, etc. are all tentative. | ||
+ Pass criteria into the hooks. | ||
+ Deep populate | ||
+ Recursively populate child associations. | ||
+ Updated Docs | ||
@@ -78,3 +76,2 @@ + Document adapter spec in detail, including the `join` method and how it's used. | ||
Transactions | Add the ability to run transactions on adapters that support them. See this [issue](https://github.com/balderdashy/waterline/issues/755) for more details. | ||
Projections | Add the ability to use `.select()` on any query, including associations. See this [issue](https://github.com/balderdashy/waterline/issues/919) for more details. | ||
Change the `this` context inside lifecycle callbacks | Currently the `this` context is the generic collection instead of the instance (no instance has been made yet). Change this to get access to instance methods. See [issue](https://github.com/balderdashy/waterline/issues/1210) for more details. | ||
@@ -81,0 +78,0 @@ Run lifecycle callbacks on defined join tables | Lifecycle callbacks don't run for join table records. See [issue](https://github.com/balderdashy/waterline/issues/1215) for more details. |
3028027
214
9621
98