Merge branch 'release/1.3.0' into production
This commit is contained in:
commit
c653483568
|
@ -11,3 +11,4 @@ docs/html
|
|||
docs/man
|
||||
npm-debug.log
|
||||
.project
|
||||
test/memory.json
|
||||
|
|
|
@ -17,6 +17,42 @@ function Connector(name, settings) {
|
|||
*/
|
||||
Connector.prototype.relational = false;
|
||||
|
||||
/**
|
||||
* Get types associated with the connector
|
||||
* @returns {String[]} The types for the connector
|
||||
*/
|
||||
Connector.prototype.getTypes = function() {
|
||||
return ['db', 'nosql'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the default data type for ID
|
||||
* @returns {Function} The default type for ID
|
||||
*/
|
||||
Connector.prototype.getDefaultIdType = function() {
|
||||
return String;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the metadata for the connector
|
||||
* @returns {Object} The metadata object
|
||||
* @property {String} type The type for the backend
|
||||
* @property {Function} defaultIdType The default id type
|
||||
* @property {Boolean} [isRelational] If the connector represents a relational database
|
||||
* @property {Object} schemaForSettings The schema for settings object
|
||||
*/
|
||||
Connector.prototype.getMedadata = function () {
|
||||
if (!this._metadata) {
|
||||
this._metadata = {
|
||||
types: this.getTypes(),
|
||||
defaultIdType: this.getDefaultIdType(),
|
||||
isRelational: this.isRelational || (this.getTypes().indexOf('rdbms') !== -1),
|
||||
schemaForSettings: {}
|
||||
};
|
||||
}
|
||||
return this._metadata;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute a command with given parameters
|
||||
* @param {String} command The command such as SQL
|
||||
|
@ -98,6 +134,7 @@ Connector.prototype.defineProperty = function (model, propertyName, propertyDefi
|
|||
*/
|
||||
Connector.prototype.disconnect = function disconnect(cb) {
|
||||
// NO-OP
|
||||
cb && process.nextTick(cb);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,6 +2,8 @@ var util = require('util');
|
|||
var Connector = require('../connector');
|
||||
var geo = require('../geo');
|
||||
var utils = require('../utils');
|
||||
var fs = require('fs');
|
||||
var async = require('async');
|
||||
|
||||
/**
|
||||
* Initialize the Oracle connector against the given data source
|
||||
|
@ -10,42 +12,111 @@ var utils = require('../utils');
|
|||
* @param {Function} [callback] The callback function
|
||||
*/
|
||||
exports.initialize = function initializeDataSource(dataSource, callback) {
|
||||
dataSource.connector = new Memory();
|
||||
dataSource.connector = new Memory(null, dataSource.settings);
|
||||
dataSource.connector.connect(callback);
|
||||
};
|
||||
|
||||
exports.Memory = Memory;
|
||||
|
||||
function Memory(m) {
|
||||
if (m) {
|
||||
function Memory(m, settings) {
|
||||
if (m instanceof Memory) {
|
||||
this.isTransaction = true;
|
||||
this.cache = m.cache;
|
||||
this.ids = m.ids;
|
||||
this.constructor.super_.call(this, 'memory');
|
||||
this.constructor.super_.call(this, 'memory', settings);
|
||||
this._models = m._models;
|
||||
} else {
|
||||
this.isTransaction = false;
|
||||
this.cache = {};
|
||||
this.ids = {};
|
||||
this.constructor.super_.call(this, 'memory');
|
||||
this.constructor.super_.call(this, 'memory', settings);
|
||||
}
|
||||
}
|
||||
|
||||
util.inherits(Memory, Connector);
|
||||
|
||||
Memory.prototype.getDefaultIdType = function() {
|
||||
return Number;
|
||||
};
|
||||
|
||||
Memory.prototype.getTypes = function() {
|
||||
return ['db', 'nosql', 'memory'];
|
||||
};
|
||||
|
||||
Memory.prototype.connect = function (callback) {
|
||||
if (this.isTransaction) {
|
||||
this.onTransactionExec = callback;
|
||||
} else {
|
||||
this.loadFromFile(callback);
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.loadFromFile = function(callback) {
|
||||
var self = this;
|
||||
if (self.settings.file) {
|
||||
fs.readFile(self.settings.file, {encoding: 'utf8', flag: 'r'}, function (err, data) {
|
||||
if (err && err.code !== 'ENOENT') {
|
||||
callback && callback(err);
|
||||
} else {
|
||||
if (data) {
|
||||
data = JSON.parse(data.toString());
|
||||
self.ids = data.ids || {};
|
||||
self.cache = data.models || {};
|
||||
} else {
|
||||
if(!self.cache) {
|
||||
self.ids = {};
|
||||
self.cache = {};
|
||||
}
|
||||
}
|
||||
callback && callback();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
process.nextTick(callback);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Flush the cache into the json file if necessary
|
||||
* @param {Function} callback
|
||||
*/
|
||||
Memory.prototype.saveToFile = function (result, callback) {
|
||||
var self = this;
|
||||
if (this.settings.file) {
|
||||
if(!self.writeQueue) {
|
||||
// Create a queue for writes
|
||||
self.writeQueue = async.queue(function (task, cb) {
|
||||
// Flush out the models/ids
|
||||
var data = JSON.stringify({
|
||||
ids: self.ids,
|
||||
models: self.cache
|
||||
}, null, ' ');
|
||||
|
||||
fs.writeFile(self.settings.file, data, function (err) {
|
||||
cb(err);
|
||||
task.callback && task.callback(err, task.data);
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
// Enqueue the write
|
||||
self.writeQueue.push({
|
||||
data: result,
|
||||
callback: callback
|
||||
});
|
||||
} else {
|
||||
process.nextTick(function () {
|
||||
callback && callback(null, result);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.define = function defineModel(definition) {
|
||||
this.constructor.super_.prototype.define.apply(this, [].slice.call(arguments));
|
||||
var m = definition.model.modelName;
|
||||
this.cache[m] = {};
|
||||
this.ids[m] = 1;
|
||||
if(!this.cache[m]) {
|
||||
this.cache[m] = {};
|
||||
this.ids[m] = 1;
|
||||
}
|
||||
};
|
||||
|
||||
Memory.prototype.create = function create(model, data, callback) {
|
||||
|
@ -68,10 +139,11 @@ Memory.prototype.create = function create(model, data, callback) {
|
|||
var idName = this.idName(model);
|
||||
id = (props[idName] && props[idName].type && props[idName].type(id)) || id;
|
||||
this.setIdValue(model, data, id);
|
||||
if(!this.cache[model]) {
|
||||
this.cache[model] = {};
|
||||
}
|
||||
this.cache[model][id] = JSON.stringify(data);
|
||||
process.nextTick(function () {
|
||||
callback(null, id);
|
||||
});
|
||||
this.saveToFile(id, callback);
|
||||
};
|
||||
|
||||
Memory.prototype.updateOrCreate = function (model, data, callback) {
|
||||
|
@ -90,9 +162,7 @@ Memory.prototype.updateOrCreate = function (model, data, callback) {
|
|||
|
||||
Memory.prototype.save = function save(model, data, callback) {
|
||||
this.cache[model][this.getIdValue(model, data)] = JSON.stringify(data);
|
||||
process.nextTick(function () {
|
||||
callback(null, data);
|
||||
});
|
||||
this.saveToFile(data, callback);
|
||||
};
|
||||
|
||||
Memory.prototype.exists = function exists(model, id, callback) {
|
||||
|
@ -110,7 +180,7 @@ Memory.prototype.find = function find(model, id, callback) {
|
|||
|
||||
Memory.prototype.destroy = function destroy(model, id, callback) {
|
||||
delete this.cache[model][id];
|
||||
process.nextTick(callback);
|
||||
this.saveToFile(null, callback);
|
||||
};
|
||||
|
||||
Memory.prototype.fromDb = function (model, data) {
|
||||
|
@ -273,7 +343,7 @@ Memory.prototype.destroyAll = function destroyAll(model, where, callback) {
|
|||
if (!where) {
|
||||
this.cache[model] = {};
|
||||
}
|
||||
process.nextTick(callback);
|
||||
this.saveToFile(null, callback);
|
||||
};
|
||||
|
||||
Memory.prototype.count = function count(model, callback, where) {
|
||||
|
|
58
lib/dao.js
58
lib/dao.js
|
@ -126,6 +126,7 @@ DataAccessObject.create = function (data, callback) {
|
|||
function modelCreated() {
|
||||
if (--wait === 0) {
|
||||
callback(gotError ? errors : null, instances);
|
||||
if(!gotError) instances.forEach(Model.emit.bind('changed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +169,7 @@ DataAccessObject.create = function (data, callback) {
|
|||
saveDone.call(obj, function () {
|
||||
createDone.call(obj, function () {
|
||||
callback(err, obj);
|
||||
if(!err) Model.emit('changed', obj);
|
||||
});
|
||||
});
|
||||
}, obj);
|
||||
|
@ -221,7 +223,7 @@ DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data
|
|||
this.getDataSource().connector.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) {
|
||||
var obj;
|
||||
if (data) {
|
||||
inst._initProperties(data, false);
|
||||
inst._initProperties(data);
|
||||
obj = inst;
|
||||
} else {
|
||||
obj = null;
|
||||
|
@ -318,7 +320,7 @@ DataAccessObject.findById = function find(id, cb) {
|
|||
setIdValue(this, data, id);
|
||||
}
|
||||
obj = new this();
|
||||
obj._initProperties(data, false);
|
||||
obj._initProperties(data);
|
||||
}
|
||||
cb(err, obj);
|
||||
}.bind(this));
|
||||
|
@ -540,14 +542,34 @@ DataAccessObject.find = function find(params, cb) {
|
|||
data.forEach(function (d, i) {
|
||||
var obj = new constr();
|
||||
|
||||
obj._initProperties(d, false, params.fields);
|
||||
obj._initProperties(d, {fields: params.fields});
|
||||
|
||||
if (params && params.include && params.collect) {
|
||||
data[i] = obj.__cachedRelations[params.collect];
|
||||
} else {
|
||||
data[i] = obj;
|
||||
if (params && params.include) {
|
||||
if (params.collect) {
|
||||
// The collect property indicates that the query is to return the
|
||||
// standlone items for a related model, not as child of the parent object
|
||||
// For example, article.tags
|
||||
obj = obj.__cachedRelations[params.collect];
|
||||
} else {
|
||||
// This handles the case to return parent items including the related
|
||||
// models. For example, Article.find({include: 'tags'}, ...);
|
||||
// Try to normalize the include
|
||||
var includes = params.include || [];
|
||||
if (typeof includes === 'string') {
|
||||
includes = [includes];
|
||||
} else if (typeof includes === 'object') {
|
||||
includes = Object.keys(includes);
|
||||
}
|
||||
includes.forEach(function (inc) {
|
||||
// Promote the included model as a direct property
|
||||
obj.__data[inc] = obj.__cachedRelations[inc];
|
||||
});
|
||||
delete obj.__data.__cachedRelations;
|
||||
}
|
||||
}
|
||||
data[i] = obj;
|
||||
});
|
||||
|
||||
if (data && data.countBeforeLimit) {
|
||||
data.countBeforeLimit = data.countBeforeLimit;
|
||||
}
|
||||
|
@ -607,6 +629,7 @@ DataAccessObject.remove =
|
|||
DataAccessObject.deleteAll =
|
||||
DataAccessObject.destroyAll = function destroyAll(where, cb) {
|
||||
if (stillConnecting(this.getDataSource(), this, arguments)) return;
|
||||
var Model = this;
|
||||
|
||||
if (!cb && 'function' === typeof where) {
|
||||
cb = where;
|
||||
|
@ -615,6 +638,7 @@ DataAccessObject.remove =
|
|||
if (!where) {
|
||||
this.getDataSource().connector.destroyAll(this.modelName, function (err, data) {
|
||||
cb && cb(err, data);
|
||||
if(!err) Model.emit('deletedAll');
|
||||
}.bind(this));
|
||||
} else {
|
||||
// Support an optional where object
|
||||
|
@ -622,6 +646,7 @@ DataAccessObject.remove =
|
|||
where = this._coerce(where);
|
||||
this.getDataSource().connector.destroyAll(this.modelName, where, function (err, data) {
|
||||
cb && cb(err, data);
|
||||
if(!err) Model.emit('deletedAll', where);
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
@ -635,11 +660,13 @@ DataAccessObject.removeById =
|
|||
DataAccessObject.deleteById =
|
||||
DataAccessObject.destroyById = function deleteById(id, cb) {
|
||||
if (stillConnecting(this.getDataSource(), this, arguments)) return;
|
||||
var Model = this;
|
||||
|
||||
this.getDataSource().connector.destroy(this.modelName, id, function (err) {
|
||||
if ('function' === typeof cb) {
|
||||
cb(err);
|
||||
}
|
||||
if(!err) Model.emit('deleted', id);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
@ -684,6 +711,7 @@ setRemoting(DataAccessObject.count, {
|
|||
*/
|
||||
DataAccessObject.prototype.save = function (options, callback) {
|
||||
if (stillConnecting(this.getDataSource(), this, arguments)) return;
|
||||
var Model = this.constructor;
|
||||
|
||||
if (typeof options == 'function') {
|
||||
callback = options;
|
||||
|
@ -736,10 +764,13 @@ DataAccessObject.prototype.save = function (options, callback) {
|
|||
if (err) {
|
||||
return callback(err, inst);
|
||||
}
|
||||
inst._initProperties(data, false);
|
||||
inst._initProperties(data);
|
||||
updateDone.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
callback(err, inst);
|
||||
if(!err) {
|
||||
Model.emit('changed', inst);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -769,15 +800,18 @@ DataAccessObject.prototype.remove =
|
|||
DataAccessObject.prototype.delete =
|
||||
DataAccessObject.prototype.destroy = function (cb) {
|
||||
if (stillConnecting(this.getDataSource(), this, arguments)) return;
|
||||
var Model = this.constructor;
|
||||
var id = getIdValue(this.constructor, this);
|
||||
|
||||
this.trigger('destroy', function (destroyed) {
|
||||
this._adapter().destroy(this.constructor.modelName, getIdValue(this.constructor, this), function (err) {
|
||||
this._adapter().destroy(this.constructor.modelName, id, function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
destroyed(function () {
|
||||
if (cb) cb();
|
||||
Model.emit('deleted', id);
|
||||
});
|
||||
}.bind(this));
|
||||
});
|
||||
|
@ -811,7 +845,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb
|
|||
if (stillConnecting(this.getDataSource(), this, arguments)) return;
|
||||
|
||||
var inst = this;
|
||||
var model = this.constructor.modelName;
|
||||
var Model = this.constructor
|
||||
var model = Model.modelName;
|
||||
|
||||
if (typeof data === 'function') {
|
||||
cb = data;
|
||||
|
@ -850,7 +885,8 @@ DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb
|
|||
}
|
||||
done.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
cb(err, inst);
|
||||
if(cb) cb(err, inst);
|
||||
if(!err) Model.emit('changed', inst);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -424,17 +424,28 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
|
|||
|
||||
/*!
|
||||
* Set up the data access functions from the data source
|
||||
* @param modelClass
|
||||
* @param settings
|
||||
* @param {Model} modelClass The model class
|
||||
* @param {Object} settings The settings object
|
||||
*/
|
||||
DataSource.prototype.setupDataAccess = function (modelClass, settings) {
|
||||
if (this.connector && this.connector.define) {
|
||||
// pass control to connector
|
||||
this.connector.define({
|
||||
model: modelClass,
|
||||
properties: modelClass.definition.properties,
|
||||
settings: settings
|
||||
});
|
||||
if (this.connector) {
|
||||
// Check if the id property should be generated
|
||||
var idName = modelClass.definition.idName();
|
||||
var idProp = modelClass.definition.rawProperties[idName];
|
||||
if(idProp && idProp.generated && this.connector.getDefaultIdType) {
|
||||
// Set the default id type from connector's ability
|
||||
var idType = this.connector.getDefaultIdType() || String;
|
||||
idProp.type = idType;
|
||||
modelClass.definition.properties[idName].type = idType;
|
||||
}
|
||||
if (this.connector.define) {
|
||||
// pass control to connector
|
||||
this.connector.define({
|
||||
model: modelClass,
|
||||
properties: modelClass.definition.properties,
|
||||
settings: settings
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// add data access objects
|
||||
|
@ -559,14 +570,55 @@ DataSource.prototype.mixin = function (ModelCtor) {
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @see ModelBuilder.prototype.getModel
|
||||
*/
|
||||
DataSource.prototype.getModel = function (name, forceCreate) {
|
||||
return this.modelBuilder.getModel(name, forceCreate);
|
||||
};
|
||||
|
||||
/**
|
||||
* @see ModelBuilder.prototype.getModelDefinition
|
||||
*/
|
||||
DataSource.prototype.getModelDefinition = function (name) {
|
||||
return this.modelBuilder.getModelDefinition(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the data source types
|
||||
* @returns {String[]} The data source type, such as ['db', 'nosql', 'mongodb'],
|
||||
* ['rest'], or ['db', 'rdbms', 'mysql']
|
||||
*/
|
||||
DataSource.prototype.getTypes = function () {
|
||||
var types = this.connector && this.connector.getTypes() || [];
|
||||
if (typeof types === 'string') {
|
||||
types = types.split(/[\s,\/]+/);
|
||||
}
|
||||
return types;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the data source supports the given types
|
||||
* @param String|String[]) types A type name or an array of type names
|
||||
* @return {Boolean} true if all types are supported by the data source
|
||||
*/
|
||||
DataSource.prototype.supportTypes = function (types) {
|
||||
var supportedTypes = this.getTypes();
|
||||
if (Array.isArray(types)) {
|
||||
// Check each of the types
|
||||
for (var i = 0; i < types.length; i++) {
|
||||
if (supportedTypes.indexOf(types[i]) === -1) {
|
||||
// Not supported
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
// The types is a string
|
||||
return supportedTypes.indexOf(types) !== -1;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach an existing model to a data source.
|
||||
*
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
var utils = require('./utils');
|
||||
var isPlainObject = utils.isPlainObject;
|
||||
var defineCachedRelations = utils.defineCachedRelations;
|
||||
|
||||
/**
|
||||
* Include mixin for ./model.js
|
||||
*/
|
||||
|
@ -29,8 +33,8 @@ Inclusion.include = function (objects, include, cb) {
|
|||
var self = this;
|
||||
|
||||
if (
|
||||
(include.constructor.name == 'Array' && include.length == 0) ||
|
||||
(include.constructor.name == 'Object' && Object.keys(include).length == 0)
|
||||
!include || (Array.isArray(include) && include.length === 0) ||
|
||||
(isPlainObject(include) && Object.keys(include).length === 0)
|
||||
) {
|
||||
cb(null, objects);
|
||||
return;
|
||||
|
@ -48,7 +52,7 @@ Inclusion.include = function (objects, include, cb) {
|
|||
nbCallbacks++;
|
||||
callback(function () {
|
||||
nbCallbacks--;
|
||||
if (nbCallbacks == 0) {
|
||||
if (nbCallbacks === 0) {
|
||||
cb(null, objects);
|
||||
}
|
||||
});
|
||||
|
@ -61,7 +65,7 @@ Inclusion.include = function (objects, include, cb) {
|
|||
if (typeof ij === 'string') {
|
||||
ij = [ij];
|
||||
}
|
||||
if (ij.constructor.name === 'Object') {
|
||||
if (isPlainObject(ij)) {
|
||||
var newIj = [];
|
||||
for (var key in ij) {
|
||||
var obj = {};
|
||||
|
@ -76,12 +80,13 @@ Inclusion.include = function (objects, include, cb) {
|
|||
function processIncludeItem(objs, include, keyVals, objsByKeys) {
|
||||
var relations = self.relations;
|
||||
|
||||
if (include.constructor.name === 'Object') {
|
||||
var relationName = Object.keys(include)[0];
|
||||
var subInclude = include[relationName];
|
||||
var relationName, subInclude;
|
||||
if (isPlainObject(include)) {
|
||||
relationName = Object.keys(include)[0];
|
||||
subInclude = include[relationName];
|
||||
} else {
|
||||
var relationName = include;
|
||||
var subInclude = [];
|
||||
relationName = include;
|
||||
subInclude = [];
|
||||
}
|
||||
var relation = relations[relationName];
|
||||
|
||||
|
@ -89,7 +94,7 @@ Inclusion.include = function (objects, include, cb) {
|
|||
return function () {
|
||||
cb(new Error('Relation "' + relationName + '" is not defined for '
|
||||
+ self.modelName + ' model'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var req = {'where': {}};
|
||||
|
@ -117,18 +122,17 @@ Inclusion.include = function (objects, include, cb) {
|
|||
}
|
||||
}
|
||||
|
||||
req['where'][relation.keyTo] = {inq: inValues};
|
||||
req['include'] = subInclude;
|
||||
req.where[relation.keyTo] = {inq: inValues};
|
||||
req.include = subInclude;
|
||||
|
||||
return function (cb) {
|
||||
relation.modelTo.find(req, function (err, objsIncluded) {
|
||||
var objectsFrom, j;
|
||||
for (var i = 0; i < objsIncluded.length; i++) {
|
||||
delete keysToBeProcessed[objsIncluded[i][relation.keyTo]];
|
||||
var objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
|
||||
for (var j = 0; j < objectsFrom.length; j++) {
|
||||
if (!objectsFrom[j].__cachedRelations) {
|
||||
objectsFrom[j].__cachedRelations = {};
|
||||
}
|
||||
objectsFrom = objsByKeys[relation.keyFrom][objsIncluded[i][relation.keyTo]];
|
||||
for (j = 0; j < objectsFrom.length; j++) {
|
||||
defineCachedRelations(objectsFrom[j]);
|
||||
if (relation.multiple) {
|
||||
if (!objectsFrom[j].__cachedRelations[relationName]) {
|
||||
objectsFrom[j].__cachedRelations[relationName] = [];
|
||||
|
@ -142,11 +146,9 @@ Inclusion.include = function (objects, include, cb) {
|
|||
|
||||
// No relation have been found for these keys
|
||||
for (var key in keysToBeProcessed) {
|
||||
var objectsFrom = objsByKeys[relation.keyFrom][key];
|
||||
for (var j = 0; j < objectsFrom.length; j++) {
|
||||
if (!objectsFrom[j].__cachedRelations) {
|
||||
objectsFrom[j].__cachedRelations = {};
|
||||
}
|
||||
objectsFrom = objsByKeys[relation.keyFrom][key];
|
||||
for (j = 0; j < objectsFrom.length; j++) {
|
||||
defineCachedRelations(objectsFrom[j]);
|
||||
objectsFrom[j].__cachedRelations[relationName] =
|
||||
relation.multiple ? [] : null;
|
||||
}
|
||||
|
@ -158,5 +160,5 @@ Inclusion.include = function (objects, include, cb) {
|
|||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -71,6 +71,11 @@ ModelBuilder.prototype.getModel = function (name, forceCreate) {
|
|||
return model;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the model definition by name
|
||||
* @param {String} name The model name
|
||||
* @returns {ModelDefinition} The model definition
|
||||
*/
|
||||
ModelBuilder.prototype.getModelDefinition = function (name) {
|
||||
return this.definitions[name];
|
||||
};
|
||||
|
@ -151,17 +156,14 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
// TODO: [rfeng] We need to decide what names to use for built-in models such as User.
|
||||
if (!ModelClass || !ModelClass.settings.unresolved) {
|
||||
// every class can receive hash of data as optional param
|
||||
ModelClass = function ModelConstructor(data, dataSource) {
|
||||
ModelClass = function ModelConstructor(data, options) {
|
||||
if (!(this instanceof ModelConstructor)) {
|
||||
return new ModelConstructor(data, dataSource);
|
||||
return new ModelConstructor(data, options);
|
||||
}
|
||||
if (ModelClass.settings.unresolved) {
|
||||
throw new Error('Model ' + ModelClass.modelName + ' is not defined.');
|
||||
}
|
||||
ModelBaseClass.apply(this, arguments);
|
||||
if (dataSource) {
|
||||
hiddenProperty(this, '__dataSource', dataSource);
|
||||
}
|
||||
};
|
||||
// mix in EventEmitter (don't inherit from)
|
||||
var events = new EventEmitter();
|
||||
|
@ -338,16 +340,6 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
if (!DataType) {
|
||||
throw new Error('Invalid type for property ' + propertyName);
|
||||
}
|
||||
if (Array.isArray(DataType) || DataType === Array) {
|
||||
DataType = List;
|
||||
} else if (DataType.name === 'Date') {
|
||||
var OrigDate = Date;
|
||||
DataType = function Date(arg) {
|
||||
return new OrigDate(arg);
|
||||
};
|
||||
} else if (typeof DataType === 'string') {
|
||||
DataType = modelBuilder.resolveType(DataType);
|
||||
}
|
||||
|
||||
if (prop.required) {
|
||||
var requiredOptions = typeof prop.required === 'object' ? prop.required : undefined;
|
||||
|
@ -363,6 +355,17 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
|
|||
}
|
||||
},
|
||||
set: function (value) {
|
||||
var DataType = ModelClass.definition.properties[propertyName].type;
|
||||
if (Array.isArray(DataType) || DataType === Array) {
|
||||
DataType = List;
|
||||
} else if (DataType.name === 'Date') {
|
||||
var OrigDate = Date;
|
||||
DataType = function Date(arg) {
|
||||
return new OrigDate(arg);
|
||||
};
|
||||
} else if (typeof DataType === 'string') {
|
||||
DataType = modelBuilder.resolveType(DataType);
|
||||
}
|
||||
if (ModelClass.setter[propertyName]) {
|
||||
ModelClass.setter[propertyName].call(this, value); // Try setter first
|
||||
} else {
|
||||
|
|
114
lib/model.js
114
lib/model.js
|
@ -27,8 +27,13 @@ var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
|
|||
* @constructor
|
||||
* @param {Object} data - initial object data
|
||||
*/
|
||||
function ModelBaseClass(data) {
|
||||
this._initProperties(data, true);
|
||||
function ModelBaseClass(data, options) {
|
||||
options = options || {};
|
||||
if(!('applySetters' in options)) {
|
||||
// Default to true
|
||||
options.applySetters = true;
|
||||
}
|
||||
this._initProperties(data, options);
|
||||
}
|
||||
|
||||
// FIXME: [rfeng] We need to make sure the input data should not be mutated. Disabled cloning for now to get tests passing
|
||||
|
@ -42,19 +47,29 @@ function clone(data) {
|
|||
*/
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize properties
|
||||
* @param data
|
||||
* @param applySetters
|
||||
* Initialize the model instance with a list of properties
|
||||
* @param {Object} data The data object
|
||||
* @param {Object} options An object to control the instantiation
|
||||
* @property {Boolean} applySetters Controls if the setters will be applied
|
||||
* @property {Boolean} strict Set the instance level strict mode
|
||||
* @private
|
||||
*/
|
||||
ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
||||
ModelBaseClass.prototype._initProperties = function (data, options) {
|
||||
var self = this;
|
||||
var ctor = this.constructor;
|
||||
|
||||
var properties = ctor.definition.build();
|
||||
data = data || {};
|
||||
|
||||
options = options || {};
|
||||
var applySetters = options.applySetters;
|
||||
var strict = options.strict;
|
||||
|
||||
if(strict === undefined) {
|
||||
strict = ctor.definition.settings.strict;
|
||||
}
|
||||
Object.defineProperty(this, '__cachedRelations', {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
|
@ -76,15 +91,32 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
value: {}
|
||||
});
|
||||
|
||||
if (data['__cachedRelations']) {
|
||||
this.__cachedRelations = data['__cachedRelations'];
|
||||
/**
|
||||
* Instance level data source
|
||||
*/
|
||||
Object.defineProperty(this, '__dataSource', {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: options.dataSource
|
||||
});
|
||||
|
||||
/**
|
||||
* Instance level strict mode
|
||||
*/
|
||||
Object.defineProperty(this, '__strict', {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: strict
|
||||
});
|
||||
|
||||
if (data.__cachedRelations) {
|
||||
this.__cachedRelations = data.__cachedRelations;
|
||||
}
|
||||
|
||||
// Check if the strict option is set to false for the model
|
||||
var strict = ctor.definition.settings.strict;
|
||||
|
||||
for (var i in data) {
|
||||
if (i in properties) {
|
||||
if (i in properties && typeof data[i] !== 'function') {
|
||||
this.__data[i] = this.__dataWas[i] = clone(data[i]);
|
||||
} else if (i in ctor.relations) {
|
||||
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
|
||||
|
@ -100,7 +132,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
|
||||
if (applySetters === true) {
|
||||
for (var propertyName in data) {
|
||||
if ((propertyName in properties) || (propertyName in ctor.relations)) {
|
||||
if (typeof data[propertyName] !== 'function' && ((propertyName in properties) || (propertyName in ctor.relations))) {
|
||||
self[propertyName] = self.__data[propertyName] || data[propertyName];
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +141,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
// Set the unknown properties as properties to the object
|
||||
if (strict === false) {
|
||||
for (var propertyName in data) {
|
||||
if (!(propertyName in properties)) {
|
||||
if (typeof data[propertyName] !== 'function' && !(propertyName in properties)) {
|
||||
self[propertyName] = self.__data[propertyName] || data[propertyName];
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +149,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
|
||||
ctor.forEachProperty(function (propertyName) {
|
||||
|
||||
if ('undefined' === typeof self.__data[propertyName]) {
|
||||
if (undefined === self.__data[propertyName]) {
|
||||
self.__data[propertyName] = self.__dataWas[propertyName] = getDefault(propertyName);
|
||||
} else {
|
||||
self.__dataWas[propertyName] = self.__data[propertyName];
|
||||
|
@ -160,7 +192,7 @@ ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
|||
}
|
||||
|
||||
this.trigger('initialize');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} prop - property name
|
||||
|
@ -197,18 +229,23 @@ ModelBaseClass.toString = function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Convert instance to Object
|
||||
* Convert model instance to a plain JSON object
|
||||
*
|
||||
* @param {Boolean} onlySchema - restrict properties to dataSource only, default false
|
||||
* when onlySchema == true, only properties defined in dataSource returned,
|
||||
* otherwise all enumerable properties returned
|
||||
* @param {Boolean} onlySchema - restrict properties to dataSource only,
|
||||
* default to false. When onlySchema is true, only properties defined in
|
||||
* the schema are returned, otherwise all enumerable properties returned
|
||||
* @returns {Object} - canonical object representation (no getters and setters)
|
||||
*/
|
||||
ModelBaseClass.prototype.toObject = function (onlySchema) {
|
||||
if(onlySchema === undefined) {
|
||||
onlySchema = true;
|
||||
}
|
||||
var data = {};
|
||||
var self = this;
|
||||
|
||||
var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema;
|
||||
var strict = this.__strict;
|
||||
var schemaLess = (strict === false) || !onlySchema;
|
||||
|
||||
this.constructor.forEachProperty(function (propertyName) {
|
||||
if (self[propertyName] instanceof List) {
|
||||
data[propertyName] = self[propertyName].toObject(!schemaLess);
|
||||
|
@ -223,10 +260,25 @@ ModelBaseClass.prototype.toObject = function (onlySchema) {
|
|||
}
|
||||
});
|
||||
|
||||
var val = null;
|
||||
if (schemaLess) {
|
||||
for (var propertyName in self.__data) {
|
||||
// Find its own properties which can be set via myModel.myProperty = 'myValue'.
|
||||
// If the property is not declared in the model definition, no setter will be
|
||||
// triggered to add it to __data
|
||||
for (var propertyName in self) {
|
||||
if(self.hasOwnProperty(propertyName) && (!data.hasOwnProperty(propertyName))) {
|
||||
val = self[propertyName];
|
||||
if (val !== undefined && val !== null && val.toObject) {
|
||||
data[propertyName] = val.toObject(!schemaLess);
|
||||
} else {
|
||||
data[propertyName] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now continue to check __data
|
||||
for (propertyName in self.__data) {
|
||||
if (!data.hasOwnProperty(propertyName)) {
|
||||
var val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName];
|
||||
val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName];
|
||||
if (val !== undefined && val !== null && val.toObject) {
|
||||
data[propertyName] = val.toObject(!schemaLess);
|
||||
} else {
|
||||
|
@ -238,13 +290,8 @@ ModelBaseClass.prototype.toObject = function (onlySchema) {
|
|||
return data;
|
||||
};
|
||||
|
||||
// ModelBaseClass.prototype.hasOwnProperty = function (prop) {
|
||||
// return this.__data && this.__data.hasOwnProperty(prop) ||
|
||||
// Object.getOwnPropertyNames(this).indexOf(prop) !== -1;
|
||||
// };
|
||||
|
||||
ModelBaseClass.prototype.toJSON = function () {
|
||||
return this.toObject();
|
||||
return this.toObject(false);
|
||||
};
|
||||
|
||||
ModelBaseClass.prototype.fromObject = function (obj) {
|
||||
|
@ -291,10 +338,15 @@ ModelBaseClass.mixin = function (anotherClass, options) {
|
|||
|
||||
ModelBaseClass.prototype.getDataSource = function () {
|
||||
return this.__dataSource || this.constructor.dataSource;
|
||||
}
|
||||
};
|
||||
|
||||
ModelBaseClass.getDataSource = function () {
|
||||
return this.dataSource;
|
||||
}
|
||||
};
|
||||
|
||||
ModelBaseClass.prototype.setStrict = function (strict) {
|
||||
this.__strict = strict;
|
||||
};
|
||||
|
||||
jutil.mixin(ModelBaseClass, Hookable);
|
||||
jutil.mixin(ModelBaseClass, validations.Validatable);
|
||||
|
|
|
@ -18,6 +18,18 @@ Relation.relationNameFor = function relationNameFor(foreignKey) {
|
|||
}
|
||||
};
|
||||
|
||||
function lookupModel(models, modelName) {
|
||||
if(models[modelName]) {
|
||||
return models[modelName];
|
||||
}
|
||||
var lookupClassName = modelName.toLowerCase();
|
||||
for (var name in models) {
|
||||
if (name.toLowerCase() === lookupClassName) {
|
||||
return models[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare hasMany relation
|
||||
*
|
||||
|
@ -34,11 +46,7 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
|||
anotherClass = params.model;
|
||||
} else {
|
||||
var anotherClassName = i8n.singularize(anotherClass).toLowerCase();
|
||||
for (var name in this.dataSource.modelBuilder.models) {
|
||||
if (name.toLowerCase() === anotherClassName) {
|
||||
anotherClass = this.dataSource.modelBuilder.models[name];
|
||||
}
|
||||
}
|
||||
anotherClass = lookupModel(this.dataSource.modelBuilder.models, anotherClassName);
|
||||
}
|
||||
}
|
||||
var methodName = params.as || i8n.camelize(anotherClass.pluralModelName, true);
|
||||
|
@ -130,9 +138,13 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
|||
|
||||
function find(id, cb) {
|
||||
anotherClass.findById(id, function (err, inst) {
|
||||
if (err) return cb(err);
|
||||
if (!inst) return cb(new Error('Not found'));
|
||||
if (inst[fk] && inst[fk].toString() == this[idName].toString()) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
if (!inst) {
|
||||
return cb(new Error('Not found'));
|
||||
}
|
||||
if (inst[fk] && inst[fk].toString() === this[idName].toString()) {
|
||||
cb(null, inst);
|
||||
} else {
|
||||
cb(new Error('Permission denied'));
|
||||
|
@ -143,9 +155,13 @@ Relation.hasMany = function hasMany(anotherClass, params) {
|
|||
function destroy(id, cb) {
|
||||
var self = this;
|
||||
anotherClass.findById(id, function (err, inst) {
|
||||
if (err) return cb(err);
|
||||
if (!inst) return cb(new Error('Not found'));
|
||||
if (inst[fk] && inst[fk].toString() == self[idName].toString()) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
if (!inst) {
|
||||
return cb(new Error('Not found'));
|
||||
}
|
||||
if (inst[fk] && inst[fk].toString() === self[idName].toString()) {
|
||||
inst.destroy(cb);
|
||||
} else {
|
||||
cb(new Error('Permission denied'));
|
||||
|
@ -186,11 +202,7 @@ Relation.belongsTo = function (anotherClass, params) {
|
|||
anotherClass = params.model;
|
||||
} else {
|
||||
var anotherClassName = anotherClass.toLowerCase();
|
||||
for (var name in this.dataSource.modelBuilder.models) {
|
||||
if (name.toLowerCase() === anotherClassName) {
|
||||
anotherClass = this.dataSource.modelBuilder.models[name];
|
||||
}
|
||||
}
|
||||
anotherClass = lookupModel(this.dataSource.modelBuilder.models, anotherClassName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,16 +219,20 @@ Relation.belongsTo = function (anotherClass, params) {
|
|||
};
|
||||
|
||||
this.dataSource.defineForeignKey(this.modelName, fk, anotherClass.modelName);
|
||||
this.prototype['__finders__'] = this.prototype['__finders__'] || {};
|
||||
this.prototype.__finders__ = this.prototype.__finders__ || {};
|
||||
|
||||
this.prototype['__finders__'][methodName] = function (id, cb) {
|
||||
this.prototype.__finders__[methodName] = function (id, cb) {
|
||||
if (id === null) {
|
||||
cb(null, null);
|
||||
return;
|
||||
}
|
||||
anotherClass.findById(id, function (err, inst) {
|
||||
if (err) return cb(err);
|
||||
if (!inst) return cb(null, null);
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
if (!inst) {
|
||||
return cb(null, null);
|
||||
}
|
||||
if (inst[idName] === this[fk]) {
|
||||
cb(null, inst);
|
||||
} else {
|
||||
|
@ -234,7 +250,7 @@ Relation.belongsTo = function (anotherClass, params) {
|
|||
}
|
||||
var self = this;
|
||||
var cachedValue;
|
||||
if (!refresh && this.__cachedRelations && (typeof this.__cachedRelations[methodName] !== 'undefined')) {
|
||||
if (!refresh && this.__cachedRelations && (this.__cachedRelations[methodName] !== undefined)) {
|
||||
cachedValue = this.__cachedRelations[methodName];
|
||||
}
|
||||
if (p instanceof ModelBaseClass) { // acts as setter
|
||||
|
@ -277,7 +293,7 @@ Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params
|
|||
if (params.model) {
|
||||
anotherClass = params.model;
|
||||
} else {
|
||||
anotherClass = lookupModel(i8n.singularize(anotherClass)) ||
|
||||
anotherClass = lookupModel(models, i8n.singularize(anotherClass).toLowerCase()) ||
|
||||
anotherClass;
|
||||
}
|
||||
if (typeof anotherClass === 'string') {
|
||||
|
@ -288,7 +304,7 @@ Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params
|
|||
if (!params.through) {
|
||||
var name1 = this.modelName + anotherClass.modelName;
|
||||
var name2 = anotherClass.modelName + this.modelName;
|
||||
params.through = lookupModel(name1) || lookupModel(name2) ||
|
||||
params.through = lookupModel(models, name1) || lookupModel(models, name2) ||
|
||||
this.dataSource.define(name1);
|
||||
}
|
||||
params.through.belongsTo(this);
|
||||
|
@ -296,13 +312,4 @@ Relation.hasAndBelongsToMany = function hasAndBelongsToMany(anotherClass, params
|
|||
|
||||
this.hasMany(anotherClass, {as: params.as, through: params.through});
|
||||
|
||||
function lookupModel(modelName) {
|
||||
var lookupClassName = modelName.toLowerCase();
|
||||
for (var name in models) {
|
||||
if (name.toLowerCase() === lookupClassName) {
|
||||
return models[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
var utils = require('./utils');
|
||||
var defineCachedRelations = utils.defineCachedRelations;
|
||||
/**
|
||||
* Module exports
|
||||
*/
|
||||
|
@ -54,14 +56,12 @@ function defineScope(cls, targetClass, name, params, methods) {
|
|||
throw new Error('Method can be only called with one or two arguments');
|
||||
}
|
||||
|
||||
if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) {
|
||||
if (!this.__cachedRelations || (this.__cachedRelations[name] === undefined) || actualRefresh) {
|
||||
var self = this;
|
||||
var params = mergeParams(actualCond, caller._scope);
|
||||
return targetClass.find(params, function (err, data) {
|
||||
if (!err && saveOnCache) {
|
||||
if (!self.__cachedRelations) {
|
||||
self.__cachedRelations = {};
|
||||
}
|
||||
defineCachedRelations(self);
|
||||
self.__cachedRelations[name] = data;
|
||||
}
|
||||
cb(err, data);
|
||||
|
|
16
lib/sql.js
16
lib/sql.js
|
@ -19,6 +19,22 @@ util.inherits(BaseSQL, Connector);
|
|||
*/
|
||||
BaseSQL.prototype.relational = true;
|
||||
|
||||
/**
|
||||
* Get types associated with the connector
|
||||
* @returns {String[]} The types for the connector
|
||||
*/
|
||||
BaseSQL.prototype.getTypes = function() {
|
||||
return ['db', 'rdbms', 'sql'];
|
||||
};
|
||||
|
||||
/*!
|
||||
* Get the default data type for ID
|
||||
* @returns {Function}
|
||||
*/
|
||||
BaseSQL.prototype.getDefaultIdType = function() {
|
||||
return Number;
|
||||
};
|
||||
|
||||
BaseSQL.prototype.query = function () {
|
||||
throw new Error('query method should be declared in connector');
|
||||
};
|
||||
|
|
27
lib/utils.js
27
lib/utils.js
|
@ -4,6 +4,8 @@ exports.selectFields = selectFields;
|
|||
exports.removeUndefined = removeUndefined;
|
||||
exports.parseSettings = parseSettings;
|
||||
exports.mergeSettings = mergeSettings;
|
||||
exports.isPlainObject = isPlainObject;
|
||||
exports.defineCachedRelations = defineCachedRelations;
|
||||
|
||||
var traverse = require('traverse');
|
||||
|
||||
|
@ -176,4 +178,29 @@ function mergeSettings(target, src) {
|
|||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define an non-enumerable __cachedRelations property
|
||||
* @param {Object} obj The obj to receive the __cachedRelations
|
||||
*/
|
||||
function defineCachedRelations(obj) {
|
||||
if (!obj.__cachedRelations) {
|
||||
Object.defineProperty(obj, '__cachedRelations', {
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the argument is plain object
|
||||
* @param {*) obj The obj value
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPlainObject(obj) {
|
||||
return (typeof obj === 'object') && (obj !== null)
|
||||
&& (obj.constructor === Object);
|
||||
}
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loopback-datasource-juggler",
|
||||
"version": "1.2.13",
|
||||
"version": "1.3.0",
|
||||
"description": "LoopBack DataSoure Juggler",
|
||||
"keywords": [
|
||||
"StrongLoop",
|
||||
|
@ -27,10 +27,10 @@
|
|||
"mocha": "~1.12.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.2.9",
|
||||
"inflection": "~1.2.6",
|
||||
"traverse": "~0.6.5",
|
||||
"qs": "~0.6.5"
|
||||
"async": "~0.2.10",
|
||||
"inflection": "~1.3.3",
|
||||
"traverse": "~0.6.6",
|
||||
"qs": "~0.6.6"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
var should = require('./init.js');
|
||||
|
||||
describe('events', function() {
|
||||
beforeEach(function(done) {
|
||||
var test = this;
|
||||
this.db = getSchema();
|
||||
this.TestModel = this.db.define('TestModel');
|
||||
this.db.automigrate(function(err) {
|
||||
if(err) return done(err);
|
||||
test.TestModel.create(function(err, inst) {
|
||||
if(err) return done(err);
|
||||
test.inst = inst;
|
||||
done();
|
||||
});
|
||||
});
|
||||
this.shouldEmitEvent = function(eventName, listener, done) {
|
||||
var timeout = setTimeout(function() {
|
||||
done(new Error('did not emit ' + eventName));
|
||||
}, 100);
|
||||
this.TestModel.on(eventName, function() {
|
||||
clearTimeout(timeout);
|
||||
listener.apply(this, arguments);
|
||||
done();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('changed', function() {
|
||||
it('should be emitted after save', function(done) {
|
||||
var model = new this.TestModel({name: 'foobar'});
|
||||
this.shouldEmitEvent('changed', assertValidChangedArgs, done);
|
||||
model.save();
|
||||
});
|
||||
it('should be emitted after upsert', function(done) {
|
||||
this.shouldEmitEvent('changed', assertValidChangedArgs, done);
|
||||
this.TestModel.upsert({name: 'batbaz'});
|
||||
});
|
||||
it('should be emitted after create', function(done) {
|
||||
this.shouldEmitEvent('changed', assertValidChangedArgs, done);
|
||||
this.TestModel.create({name: '...'});
|
||||
});
|
||||
it('should be emitted after updateAttributes', function(done) {
|
||||
var test = this;
|
||||
this.TestModel.create({name: 'bazzy'}, function(err, model) {
|
||||
// prevent getting the changed event from "create"
|
||||
process.nextTick(function() {
|
||||
test.shouldEmitEvent('changed', assertValidChangedArgs, done);
|
||||
model.updateAttributes({name: 'foo'});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleted', function() {
|
||||
it('should be emitted after destroy', function(done) {
|
||||
this.shouldEmitEvent('deleted', assertValidDeletedArgs, done);
|
||||
this.inst.destroy();
|
||||
});
|
||||
it('should be emitted after deleteById', function(done) {
|
||||
this.shouldEmitEvent('deleted', assertValidDeletedArgs, done);
|
||||
this.TestModel.deleteById(this.inst.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletedAll', function() {
|
||||
it('should be emitted after destroyAll', function(done) {
|
||||
this.shouldEmitEvent('deletedAll', function(where) {
|
||||
where.name.should.equal('foo');
|
||||
}, done);
|
||||
this.TestModel.destroyAll({name: 'foo'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function assertValidChangedArgs(obj) {
|
||||
obj.should.have.property('id');
|
||||
}
|
||||
|
||||
function assertValidDeletedArgs(id) {
|
||||
id.should.be.ok;
|
||||
}
|
|
@ -402,6 +402,8 @@ describe('hooks', function () {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
function addHooks(name, done) {
|
||||
var called = false, random = String(Math.floor(Math.random() * 1000));
|
||||
User['before' + name] = function (next, data) {
|
||||
|
|
|
@ -13,6 +13,12 @@ describe('include', function () {
|
|||
passports.length.should.be.ok;
|
||||
passports.forEach(function (p) {
|
||||
p.__cachedRelations.should.have.property('owner');
|
||||
|
||||
// The relation should be promoted as the 'owner' property
|
||||
p.should.have.property('owner');
|
||||
// The __cachedRelations should be removed from json output
|
||||
p.toJSON().should.not.have.property('__cachedRelations');
|
||||
|
||||
var owner = p.__cachedRelations.owner;
|
||||
if (!p.ownerId) {
|
||||
should.not.exist(owner);
|
||||
|
@ -31,6 +37,11 @@ describe('include', function () {
|
|||
should.exist(users);
|
||||
users.length.should.be.ok;
|
||||
users.forEach(function (u) {
|
||||
// The relation should be promoted as the 'owner' property
|
||||
u.should.have.property('posts');
|
||||
// The __cachedRelations should be removed from json output
|
||||
u.toJSON().should.not.have.property('__cachedRelations');
|
||||
|
||||
u.__cachedRelations.should.have.property('posts');
|
||||
u.__cachedRelations.posts.forEach(function (p) {
|
||||
p.userId.should.equal(u.id);
|
||||
|
@ -47,6 +58,12 @@ describe('include', function () {
|
|||
passports.length.should.be.ok;
|
||||
passports.forEach(function (p) {
|
||||
p.__cachedRelations.should.have.property('owner');
|
||||
|
||||
// The relation should be promoted as the 'owner' property
|
||||
p.should.have.property('owner');
|
||||
// The __cachedRelations should be removed from json output
|
||||
p.toJSON().should.not.have.property('__cachedRelations');
|
||||
|
||||
var user = p.__cachedRelations.owner;
|
||||
if (!p.ownerId) {
|
||||
should.not.exist(user);
|
||||
|
@ -97,6 +114,12 @@ describe('include', function () {
|
|||
should.exist(users);
|
||||
users.length.should.be.ok;
|
||||
users.forEach(function (user) {
|
||||
// The relation should be promoted as the 'owner' property
|
||||
user.should.have.property('posts');
|
||||
user.should.have.property('passports');
|
||||
// The __cachedRelations should be removed from json output
|
||||
user.toJSON().should.not.have.property('__cachedRelations');
|
||||
|
||||
user.__cachedRelations.should.have.property('posts');
|
||||
user.__cachedRelations.should.have.property('passports');
|
||||
user.__cachedRelations.posts.forEach(function (p) {
|
||||
|
|
|
@ -46,11 +46,35 @@ describe('ModelBuilder define model', function () {
|
|||
|
||||
User.modelName.should.equal('User');
|
||||
user.should.be.a('object');
|
||||
assert(user.name === 'Joe');
|
||||
assert(user.age === undefined);
|
||||
assert(user.toObject().age === undefined);
|
||||
assert(user.toObject(true).age === undefined);
|
||||
assert(user.bio === undefined);
|
||||
user.should.have.property('name', 'Joe');
|
||||
user.should.not.have.property('age');
|
||||
user.toObject().should.not.have.property('age');
|
||||
user.toObject(true).should.not.have.property('age');
|
||||
user.should.not.have.property('bio');
|
||||
done(null, User);
|
||||
});
|
||||
|
||||
it('should ignore non-predefined properties in strict mode', function (done) {
|
||||
var modelBuilder = new ModelBuilder();
|
||||
|
||||
var User = modelBuilder.define('User', {name: String, bio: String}, {strict: true});
|
||||
|
||||
var user = new User({name: 'Joe'});
|
||||
user.age = 10;
|
||||
user.bio = 'me';
|
||||
|
||||
user.should.have.property('name', 'Joe');
|
||||
user.should.have.property('bio', 'me');
|
||||
|
||||
// Non predefined property age should be ignored in strict mode if schemaOnly parameter is not false
|
||||
user.toObject().should.not.have.property('age');
|
||||
user.toObject(true).should.not.have.property('age');
|
||||
user.toObject(false).should.have.property('age', 10);
|
||||
|
||||
// Predefined property bio should be kept in strict mode
|
||||
user.toObject().should.have.property('bio', 'me');
|
||||
user.toObject(true).should.have.property('bio', 'me');
|
||||
user.toObject(false).should.have.property('bio', 'me');
|
||||
done(null, User);
|
||||
});
|
||||
|
||||
|
@ -83,6 +107,31 @@ describe('ModelBuilder define model', function () {
|
|||
done(null, User);
|
||||
});
|
||||
|
||||
it('should take non-predefined properties in non-strict mode', function (done) {
|
||||
var modelBuilder = new ModelBuilder();
|
||||
|
||||
var User = modelBuilder.define('User', {name: String, bio: String}, {strict: false});
|
||||
|
||||
var user = new User({name: 'Joe'});
|
||||
user.age = 10;
|
||||
user.bio = 'me';
|
||||
|
||||
user.should.have.property('name', 'Joe');
|
||||
user.should.have.property('bio', 'me');
|
||||
|
||||
// Non predefined property age should be kept in non-strict mode
|
||||
user.toObject().should.have.property('age', 10);
|
||||
user.toObject(true).should.have.property('age', 10);
|
||||
user.toObject(false).should.have.property('age', 10);
|
||||
|
||||
// Predefined property bio should be kept
|
||||
user.toObject().should.have.property('bio', 'me');
|
||||
user.toObject(true).should.have.property('bio', 'me');
|
||||
user.toObject(false).should.have.property('bio', 'me');
|
||||
|
||||
done(null, User);
|
||||
});
|
||||
|
||||
it('should use false as the default value for strict', function (done) {
|
||||
var modelBuilder = new ModelBuilder();
|
||||
|
||||
|
@ -416,6 +465,61 @@ describe('DataSource define model', function () {
|
|||
});
|
||||
});
|
||||
|
||||
it('supports instance level strict mode', function () {
|
||||
var ds = new DataSource('memory');
|
||||
|
||||
var User = ds.define('User', {name: String, bio: String}, {strict: true});
|
||||
|
||||
var user = new User({name: 'Joe', age: 20}, {strict: false});
|
||||
|
||||
user.should.have.property('__strict', false);
|
||||
user.should.be.a('object');
|
||||
user.should.have.property('name', 'Joe');
|
||||
user.should.have.property('age', 20);
|
||||
user.toObject().should.have.property('age', 20);
|
||||
user.toObject(true).should.have.property('age', 20);
|
||||
|
||||
user.setStrict(true);
|
||||
user.toObject().should.not.have.property('age');
|
||||
user.toObject(true).should.not.have.property('age');
|
||||
user.toObject(false).should.have.property('age', 20);
|
||||
|
||||
});
|
||||
|
||||
it('injects id by default', function (done) {
|
||||
var ds = new ModelBuilder();
|
||||
|
||||
var User = ds.define('User', {});
|
||||
assert.deepEqual(User.definition.properties.id,
|
||||
{type: Number, id: 1, generated: true});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it('disables idInjection if the value is false', function (done) {
|
||||
var ds = new ModelBuilder();
|
||||
|
||||
var User1 = ds.define('User', {}, {idInjection: false});
|
||||
assert(!User1.definition.properties.id);
|
||||
done();
|
||||
});
|
||||
|
||||
it('updates generated id type by the connector', function (done) {
|
||||
var builder = new ModelBuilder();
|
||||
|
||||
var User = builder.define('User', {id: {type: String, generated: true, id: true}});
|
||||
assert.deepEqual(User.definition.properties.id,
|
||||
{type: String, id: 1, generated: true});
|
||||
|
||||
var ds = new DataSource('memory');// define models
|
||||
User.attachTo(ds);
|
||||
|
||||
assert.deepEqual(User.definition.properties.id,
|
||||
{type: Number, id: 1, generated: true});
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Load models with base', function () {
|
||||
|
@ -445,6 +549,42 @@ describe('Load models with base', function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('DataSource connector types', function() {
|
||||
it('should return an array of types', function() {
|
||||
var ds = new DataSource('memory');
|
||||
var types = ds.getTypes();
|
||||
assert.deepEqual(types, ['db', 'nosql', 'memory']);
|
||||
});
|
||||
|
||||
it('should test supported types by string', function() {
|
||||
var ds = new DataSource('memory');
|
||||
var result = ds.supportTypes('db');
|
||||
assert(result);
|
||||
});
|
||||
|
||||
it('should test supported types by array', function() {
|
||||
var ds = new DataSource('memory');
|
||||
var result = ds.supportTypes(['db', 'memory']);
|
||||
assert(result);
|
||||
});
|
||||
|
||||
it('should test unsupported types by string', function() {
|
||||
var ds = new DataSource('memory');
|
||||
var result = ds.supportTypes('rdbms');
|
||||
assert(!result);
|
||||
});
|
||||
|
||||
it('should test unsupported types by array', function() {
|
||||
var ds = new DataSource('memory');
|
||||
var result = ds.supportTypes(['rdbms', 'memory']);
|
||||
assert(!result);
|
||||
|
||||
result = ds.supportTypes(['rdbms']);
|
||||
assert(!result);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('DataSource constructor', function () {
|
||||
// Mocked require
|
||||
var loader = function (name) {
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
var jdb = require('../');
|
||||
var DataSource = jdb.DataSource;
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var assert = require('assert');
|
||||
var async = require('async');
|
||||
|
||||
describe('Memory connector', function () {
|
||||
var file = path.join(__dirname, 'memory.json');
|
||||
|
||||
function readModels(done) {
|
||||
fs.readFile(file, function (err, data) {
|
||||
var json = JSON.parse(data.toString());
|
||||
assert(json.models);
|
||||
assert(json.ids.User);
|
||||
done(err, json);
|
||||
});
|
||||
}
|
||||
|
||||
before(function (done) {
|
||||
fs.unlink(file, function (err) {
|
||||
if (!err || err.code === 'ENOENT') {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should save to a json file', function (done) {
|
||||
var ds = new DataSource({
|
||||
connector: 'memory',
|
||||
file: file
|
||||
});
|
||||
|
||||
var User = ds.createModel('User', {
|
||||
name: String,
|
||||
bio: String,
|
||||
approved: Boolean,
|
||||
joinedAt: Date,
|
||||
age: Number
|
||||
});
|
||||
|
||||
var count = 0;
|
||||
var ids = [];
|
||||
async.eachSeries(['John1', 'John2', 'John3'], function (item, cb) {
|
||||
User.create({name: item}, function (err, result) {
|
||||
ids.push(result.id);
|
||||
count++;
|
||||
readModels(function (err, json) {
|
||||
assert.equal(Object.keys(json.models.User).length, count);
|
||||
cb(err);
|
||||
});
|
||||
});
|
||||
}, function (err, results) {
|
||||
// Now try to delete one
|
||||
User.deleteById(ids[0], function (err) {
|
||||
readModels(function (err, json) {
|
||||
assert.equal(Object.keys(json.models.User).length, 2);
|
||||
User.upsert({id: ids[1], name: 'John'}, function(err, result) {
|
||||
readModels(function (err, json) {
|
||||
assert.equal(Object.keys(json.models.User).length, 2);
|
||||
var user = JSON.parse(json.models.User[ids[1]]);
|
||||
assert.equal(user.name, 'John');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// The saved memory.json from previous test should be loaded
|
||||
it('should load from the json file', function (done) {
|
||||
var ds = new DataSource({
|
||||
connector: 'memory',
|
||||
file: file
|
||||
});
|
||||
|
||||
var User = ds.createModel('User', {
|
||||
name: String,
|
||||
bio: String,
|
||||
approved: Boolean,
|
||||
joinedAt: Date,
|
||||
age: Number
|
||||
});
|
||||
|
||||
User.find(function (err, users) {
|
||||
// There should be 2 records
|
||||
assert.equal(users.length, 2);
|
||||
done(err);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue