Refactor the CRUD operations to DataAccessObject
This commit is contained in:
parent
3d82fc10b9
commit
630b991d1d
|
@ -3,7 +3,7 @@ doc
|
|||
coverage.html
|
||||
coverage
|
||||
v8.log
|
||||
|
||||
.idea
|
||||
.DS_Store
|
||||
benchmark.js
|
||||
analyse.r
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
var DataSource = require('../../jugglingdb').Schema;
|
||||
var dataSource = new DataSource();
|
||||
// define models
|
||||
var Post = dataSource.define('Post', {
|
||||
title: { type: String, length: 255 },
|
||||
content: { type: DataSource.Text },
|
||||
date: { type: Date, default: function () { return new Date;} },
|
||||
timestamp: { type: Number, default: Date.now },
|
||||
published: { type: Boolean, default: false, index: true }
|
||||
});
|
||||
|
||||
// simplier way to describe model
|
||||
var User = dataSource.define('User', {
|
||||
name: String,
|
||||
bio: DataSource.Text,
|
||||
approved: Boolean,
|
||||
joinedAt: Date,
|
||||
age: Number
|
||||
});
|
||||
|
||||
var Group = dataSource.define('Group', {name: String});
|
||||
|
||||
// define any custom method
|
||||
User.prototype.getNameAndAge = function () {
|
||||
return this.name + ', ' + this.age;
|
||||
};
|
||||
|
||||
var user = new User({name: 'Joe'});
|
||||
console.log(user);
|
||||
|
||||
console.log(dataSource.models);
|
||||
console.log(dataSource.definitions);
|
||||
|
||||
var user2 = User.create({name: 'Joe'});
|
||||
console.log(user2);
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,566 @@
|
|||
/**
|
||||
* Module exports class Model
|
||||
*/
|
||||
module.exports = DataAccessObject;
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
var util = require('util');
|
||||
var validations = require('./validations.js');
|
||||
var ValidationError = validations.ValidationError;
|
||||
var List = require('./list.js');
|
||||
require('./hooks.js');
|
||||
require('./relations.js');
|
||||
require('./include.js');
|
||||
|
||||
|
||||
/**
|
||||
* DAO class - base class for all persist objects
|
||||
* provides **common API** to access any database adapter.
|
||||
* This class describes only abstract behavior layer, refer to `lib/adapters/*.js`
|
||||
* to learn more about specific adapter implementations
|
||||
*
|
||||
* `DataAccessObject` mixes `Validatable` and `Hookable` classes methods
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} data - initial object data
|
||||
*/
|
||||
function DataAccessObject() {
|
||||
}
|
||||
|
||||
|
||||
DataAccessObject._forDB = function (data) {
|
||||
var res = {};
|
||||
Object.keys(data).forEach(function (propName) {
|
||||
if (this.whatTypeName(propName) === 'JSON' || data[propName] instanceof Array) {
|
||||
res[propName] = JSON.stringify(data[propName]);
|
||||
} else {
|
||||
res[propName] = data[propName];
|
||||
}
|
||||
}.bind(this));
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create new instance of Model class, saved in database
|
||||
*
|
||||
* @param data [optional]
|
||||
* @param callback(err, obj)
|
||||
* callback called with arguments:
|
||||
*
|
||||
* - err (null or Error)
|
||||
* - instance (null or Model)
|
||||
*/
|
||||
DataAccessObject.create = function (data, callback) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
var Model = this;
|
||||
var modelName = Model.modelName;
|
||||
|
||||
if (typeof data === 'function') {
|
||||
callback = data;
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
callback = function () {};
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (data instanceof Array) {
|
||||
var instances = [];
|
||||
var errors = Array(data.length);
|
||||
var gotError = false;
|
||||
var wait = data.length;
|
||||
if (wait === 0) callback(null, []);
|
||||
|
||||
var instances = [];
|
||||
for (var i = 0; i < data.length; i += 1) {
|
||||
(function(d, i) {
|
||||
instances.push(Model.create(d, function(err, inst) {
|
||||
if (err) {
|
||||
errors[i] = err;
|
||||
gotError = true;
|
||||
}
|
||||
modelCreated();
|
||||
}));
|
||||
})(data[i], i);
|
||||
}
|
||||
|
||||
return instances;
|
||||
|
||||
function modelCreated() {
|
||||
if (--wait === 0) {
|
||||
callback(gotError ? errors : null, instances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var obj;
|
||||
// if we come from save
|
||||
if (data instanceof Model && !data.id) {
|
||||
obj = data;
|
||||
} else {
|
||||
obj = new Model(data);
|
||||
}
|
||||
data = obj.toObject(true);
|
||||
|
||||
// validation required
|
||||
obj.isValid(function(valid) {
|
||||
if (valid) {
|
||||
create();
|
||||
} else {
|
||||
callback(new ValidationError(obj), obj);
|
||||
}
|
||||
}, data);
|
||||
|
||||
function create() {
|
||||
obj.trigger('create', function(createDone) {
|
||||
obj.trigger('save', function(saveDone) {
|
||||
|
||||
this._adapter().create(modelName, this.constructor._forDB(obj.toObject(true)), function (err, id, rev) {
|
||||
if (id) {
|
||||
obj.__data.id = id;
|
||||
obj.__dataWas.id = id;
|
||||
defineReadonlyProp(obj, 'id', id);
|
||||
}
|
||||
if (rev) {
|
||||
obj._rev = rev
|
||||
}
|
||||
if (err) {
|
||||
return callback(err, obj);
|
||||
}
|
||||
saveDone.call(obj, function () {
|
||||
createDone.call(obj, function () {
|
||||
callback(err, obj);
|
||||
});
|
||||
});
|
||||
}, obj);
|
||||
}, obj);
|
||||
}, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
function stillConnecting(schema, obj, args) {
|
||||
if (schema.connected) return false;
|
||||
if (schema.connecting) return true;
|
||||
var method = args.callee;
|
||||
schema.once('connected', function () {
|
||||
method.apply(obj, [].slice.call(args));
|
||||
});
|
||||
schema.connect();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update or insert
|
||||
*/
|
||||
DataAccessObject.upsert = DataAccessObject.updateOrCreate = function upsert(data, callback) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
var Model = this;
|
||||
if (!data.id) return this.create(data, callback);
|
||||
if (this.schema.adapter.updateOrCreate) {
|
||||
var inst = new Model(data);
|
||||
this.schema.adapter.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) {
|
||||
var obj;
|
||||
if (data) {
|
||||
inst._initProperties(data);
|
||||
obj = inst;
|
||||
} else {
|
||||
obj = null;
|
||||
}
|
||||
callback(err, obj);
|
||||
});
|
||||
} else {
|
||||
this.find(data.id, function (err, inst) {
|
||||
if (err) return callback(err);
|
||||
if (inst) {
|
||||
inst.updateAttributes(data, callback);
|
||||
} else {
|
||||
var obj = new Model(data);
|
||||
obj.save(data, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find one record, same as `all`, limited by 1 and return object, not collection,
|
||||
* if not found, create using data provided as second argument
|
||||
*
|
||||
* @param {Object} query - search conditions: {where: {test: 'me'}}.
|
||||
* @param {Object} data - object to create.
|
||||
* @param {Function} cb - callback called with (err, instance)
|
||||
*/
|
||||
DataAccessObject.findOrCreate = function findOrCreate(query, data, callback) {
|
||||
if (typeof query === 'undefined') {
|
||||
query = {where: {}};
|
||||
}
|
||||
if (typeof data === 'function' || typeof data === 'undefined') {
|
||||
callback = data;
|
||||
data = query && query.where;
|
||||
}
|
||||
if (typeof callback === 'undefined') {
|
||||
callback = function () {};
|
||||
}
|
||||
|
||||
var t = this;
|
||||
this.findOne(query, function (err, record) {
|
||||
if (err) return callback(err);
|
||||
if (record) return callback(null, record);
|
||||
t.create(data, callback);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether object exitst in database
|
||||
*
|
||||
* @param {id} id - identifier of object (primary key value)
|
||||
* @param {Function} cb - callbacl called with (err, exists: Bool)
|
||||
*/
|
||||
DataAccessObject.exists = function exists(id, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (id) {
|
||||
this.schema.adapter.exists(this.modelName, id, cb);
|
||||
} else {
|
||||
cb(new Error('Model::exists requires positive id argument'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find object by id
|
||||
*
|
||||
* @param {id} id - primary key value
|
||||
* @param {Function} cb - callback called with (err, instance)
|
||||
*/
|
||||
DataAccessObject.find = function find(id, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
this.schema.adapter.find(this.modelName, id, function (err, data) {
|
||||
var obj = null;
|
||||
if (data) {
|
||||
if (!data.id) {
|
||||
data.id = id;
|
||||
}
|
||||
obj = new this();
|
||||
obj._initProperties(data, false);
|
||||
}
|
||||
cb(err, obj);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all instances of Model, matched by query
|
||||
* make sure you have marked as `index: true` fields for filter or sort
|
||||
*
|
||||
* @param {Object} params (optional)
|
||||
*
|
||||
* - where: Object `{ key: val, key2: {gt: 'val2'}}`
|
||||
* - include: String, Object or Array. See DataAccessObject.include documentation.
|
||||
* - order: String
|
||||
* - limit: Number
|
||||
* - skip: Number
|
||||
*
|
||||
* @param {Function} callback (required) called with arguments:
|
||||
*
|
||||
* - err (null or Error)
|
||||
* - Array of instances
|
||||
*/
|
||||
DataAccessObject.all = function all(params, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (arguments.length === 1) {
|
||||
cb = params;
|
||||
params = null;
|
||||
}
|
||||
var constr = this;
|
||||
this.schema.adapter.all(this.modelName, params, function (err, data) {
|
||||
if (data && data.forEach) {
|
||||
data.forEach(function (d, i) {
|
||||
var obj = new constr;
|
||||
obj._initProperties(d, false);
|
||||
if (params && params.include && params.collect) {
|
||||
data[i] = obj.__cachedRelations[params.collect];
|
||||
} else {
|
||||
data[i] = obj;
|
||||
}
|
||||
});
|
||||
if (data && data.countBeforeLimit) {
|
||||
data.countBeforeLimit = data.countBeforeLimit;
|
||||
}
|
||||
cb(err, data);
|
||||
}
|
||||
else
|
||||
cb(err, []);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find one record, same as `all`, limited by 1 and return object, not collection
|
||||
*
|
||||
* @param {Object} params - search conditions: {where: {test: 'me'}}
|
||||
* @param {Function} cb - callback called with (err, instance)
|
||||
*/
|
||||
DataAccessObject.findOne = function findOne(params, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
}
|
||||
params.limit = 1;
|
||||
this.all(params, function (err, collection) {
|
||||
if (err || !collection || !collection.length > 0) return cb(err, null);
|
||||
cb(err, collection[0]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy all records
|
||||
* @param {Function} cb - callback called with (err)
|
||||
*/
|
||||
DataAccessObject.destroyAll = function destroyAll(cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
this.schema.adapter.destroyAll(this.modelName, function (err) {
|
||||
if ('function' === typeof cb) {
|
||||
cb(err);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return count of matched records
|
||||
*
|
||||
* @param {Object} where - search conditions (optional)
|
||||
* @param {Function} cb - callback, called with (err, count)
|
||||
*/
|
||||
DataAccessObject.count = function (where, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (typeof where === 'function') {
|
||||
cb = where;
|
||||
where = null;
|
||||
}
|
||||
this.schema.adapter.count(this.modelName, cb, where);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save instance. When instance haven't id, create method called instead.
|
||||
* Triggers: validate, save, update | create
|
||||
* @param options {validate: true, throws: false} [optional]
|
||||
* @param callback(err, obj)
|
||||
*/
|
||||
DataAccessObject.prototype.save = function (options, callback) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
if (typeof options == 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
callback = callback || function () {};
|
||||
options = options || {};
|
||||
|
||||
if (!('validate' in options)) {
|
||||
options.validate = true;
|
||||
}
|
||||
if (!('throws' in options)) {
|
||||
options.throws = false;
|
||||
}
|
||||
|
||||
var inst = this;
|
||||
var data = inst.toObject(true);
|
||||
var Model = this.constructor;
|
||||
var modelName = Model.modelName;
|
||||
|
||||
if (!this.id) {
|
||||
return Model.create(this, callback);
|
||||
}
|
||||
|
||||
// validate first
|
||||
if (!options.validate) {
|
||||
return save();
|
||||
}
|
||||
|
||||
inst.isValid(function (valid) {
|
||||
if (valid) {
|
||||
save();
|
||||
} else {
|
||||
var err = new ValidationError(inst);
|
||||
// throws option is dangerous for async usage
|
||||
if (options.throws) {
|
||||
throw err;
|
||||
}
|
||||
callback(err, inst);
|
||||
}
|
||||
});
|
||||
|
||||
// then save
|
||||
function save() {
|
||||
inst.trigger('save', function (saveDone) {
|
||||
inst.trigger('update', function (updateDone) {
|
||||
inst._adapter().save(modelName, inst.constructor._forDB(data), function (err) {
|
||||
if (err) {
|
||||
return callback(err, inst);
|
||||
}
|
||||
inst._initProperties(data, false);
|
||||
updateDone.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
callback(err, inst);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, data);
|
||||
}, data);
|
||||
}
|
||||
};
|
||||
|
||||
DataAccessObject.prototype.isNewRecord = function () {
|
||||
return !this.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return adapter of current record
|
||||
* @private
|
||||
*/
|
||||
DataAccessObject.prototype._adapter = function () {
|
||||
return this.schema.adapter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete object from persistence
|
||||
*
|
||||
* @triggers `destroy` hook (async) before and after destroying object
|
||||
*/
|
||||
DataAccessObject.prototype.destroy = function (cb) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
this.trigger('destroy', function (destroyed) {
|
||||
this._adapter().destroy(this.constructor.modelName, this.id, function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
destroyed(function () {
|
||||
if(cb) cb();
|
||||
});
|
||||
}.bind(this));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update single attribute
|
||||
*
|
||||
* equals to `updateAttributes({name: value}, cb)
|
||||
*
|
||||
* @param {String} name - name of property
|
||||
* @param {Mixed} value - value of property
|
||||
* @param {Function} callback - callback called with (err, instance)
|
||||
*/
|
||||
DataAccessObject.prototype.updateAttribute = function updateAttribute(name, value, callback) {
|
||||
var data = {};
|
||||
data[name] = value;
|
||||
this.updateAttributes(data, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update set of attributes
|
||||
*
|
||||
* this method performs validation before updating
|
||||
*
|
||||
* @trigger `validation`, `save` and `update` hooks
|
||||
* @param {Object} data - data to update
|
||||
* @param {Function} callback - callback called with (err, instance)
|
||||
*/
|
||||
DataAccessObject.prototype.updateAttributes = function updateAttributes(data, cb) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
var inst = this;
|
||||
var model = this.constructor.modelName;
|
||||
|
||||
if (typeof data === 'function') {
|
||||
cb = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
|
||||
// update instance's properties
|
||||
Object.keys(data).forEach(function (key) {
|
||||
inst[key] = data[key];
|
||||
});
|
||||
|
||||
inst.isValid(function (valid) {
|
||||
if (!valid) {
|
||||
if (cb) {
|
||||
cb(new ValidationError(inst), inst);
|
||||
}
|
||||
} else {
|
||||
inst.trigger('save', function (saveDone) {
|
||||
inst.trigger('update', function (done) {
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
inst[key] = data[key];
|
||||
});
|
||||
|
||||
inst._adapter().updateAttributes(model, inst.id, inst.constructor._forDB(data), function (err) {
|
||||
if (!err) {
|
||||
// update _was attrs
|
||||
Object.keys(data).forEach(function (key) {
|
||||
inst.__dataWas[key] = inst.__data[key];
|
||||
});
|
||||
}
|
||||
done.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
cb(err, inst);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, data);
|
||||
}, data);
|
||||
}
|
||||
}, data);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Reload object from persistence
|
||||
*
|
||||
* @requires `id` member of `object` to be able to call `find`
|
||||
* @param {Function} callback - called with (err, instance) arguments
|
||||
*/
|
||||
DataAccessObject.prototype.reload = function reload(callback) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
this.constructor.find(this.id, callback);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Define readonly property on object
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
* @param {Mixed} value
|
||||
*/
|
||||
function defineReadonlyProp(obj, key, value) {
|
||||
Object.defineProperty(obj, key, {
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: value
|
||||
});
|
||||
}
|
|
@ -6,7 +6,7 @@ exports.Hookable = Hookable;
|
|||
/**
|
||||
* Hooks mixins for ./model.js
|
||||
*/
|
||||
var Hookable = require('./model.js');
|
||||
var Hookable = require('./dao.js');
|
||||
|
||||
/**
|
||||
* List of hooks available
|
||||
|
@ -23,10 +23,11 @@ Hookable.afterUpdate = null;
|
|||
Hookable.beforeDestroy = null;
|
||||
Hookable.afterDestroy = null;
|
||||
|
||||
// TODO: Evaluate https://github.com/bnoguchi/hooks-js/
|
||||
Hookable.prototype.trigger = function trigger(actionName, work, data) {
|
||||
var capitalizedName = capitalize(actionName);
|
||||
var beforeHook = this.constructor["before" + capitalizedName];
|
||||
var afterHook = this.constructor["after" + capitalizedName];
|
||||
var beforeHook = this.constructor["before" + capitalizedName] || this.constructor["pre" + capitalizedName];
|
||||
var afterHook = this.constructor["after" + capitalizedName] || this.constructor["post" + capitalizedName];
|
||||
if (actionName === 'validate') {
|
||||
beforeHook = beforeHook || this.constructor.beforeValidation;
|
||||
afterHook = afterHook || this.constructor.afterValidation;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Include mixin for ./model.js
|
||||
*/
|
||||
var AbstractClass = require('./model.js');
|
||||
var DataAccessObject = require('./dao.js');
|
||||
|
||||
/**
|
||||
* Allows you to load relations of several objects and optimize numbers of requests.
|
||||
|
@ -22,7 +22,7 @@ var AbstractClass = require('./model.js');
|
|||
* - Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); // ...
|
||||
*
|
||||
*/
|
||||
AbstractClass.include = function (objects, include, cb) {
|
||||
DataAccessObject.include = function (objects, include, cb) {
|
||||
var self = this;
|
||||
|
||||
if (
|
||||
|
|
569
lib/model.js
569
lib/model.js
|
@ -1,18 +1,14 @@
|
|||
/**
|
||||
* Module exports class Model
|
||||
*/
|
||||
module.exports = AbstractClass;
|
||||
module.exports = ModelBaseClass;
|
||||
|
||||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
var util = require('util');
|
||||
var validations = require('./validations.js');
|
||||
var ValidationError = validations.ValidationError;
|
||||
var List = require('./list.js');
|
||||
require('./hooks.js');
|
||||
require('./relations.js');
|
||||
require('./include.js');
|
||||
|
||||
|
||||
var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
|
||||
|
||||
|
@ -22,16 +18,16 @@ var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
|
|||
* This class describes only abstract behavior layer, refer to `lib/adapters/*.js`
|
||||
* to learn more about specific adapter implementations
|
||||
*
|
||||
* `AbstractClass` mixes `Validatable` and `Hookable` classes methods
|
||||
* `ModelBaseClass` mixes `Validatable` and `Hookable` classes methods
|
||||
*
|
||||
* @constructor
|
||||
* @param {Object} data - initial object data
|
||||
*/
|
||||
function AbstractClass(data) {
|
||||
function ModelBaseClass(data) {
|
||||
this._initProperties(data, true);
|
||||
}
|
||||
|
||||
AbstractClass.prototype._initProperties = function (data, applySetters) {
|
||||
ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
||||
var self = this;
|
||||
var ctor = this.constructor;
|
||||
var ds = ctor.schema.definitions[ctor.modelName];
|
||||
|
@ -127,11 +123,11 @@ AbstractClass.prototype._initProperties = function (data, applySetters) {
|
|||
* @param {String} prop - property name
|
||||
* @param {Object} params - various property configuration
|
||||
*/
|
||||
AbstractClass.defineProperty = function (prop, params) {
|
||||
ModelBaseClass.defineProperty = function (prop, params) {
|
||||
this.schema.defineProperty(this.modelName, prop, params);
|
||||
};
|
||||
|
||||
AbstractClass.whatTypeName = function (propName) {
|
||||
ModelBaseClass.whatTypeName = function (propName) {
|
||||
var prop = this.schema.definitions[this.modelName].properties[propName];
|
||||
if (!prop || !prop.type) {
|
||||
throw new Error('Undefined type for ' + this.modelName + ':' + propName);
|
||||
|
@ -139,426 +135,19 @@ AbstractClass.whatTypeName = function (propName) {
|
|||
return prop.type.name;
|
||||
};
|
||||
|
||||
AbstractClass._forDB = function (data) {
|
||||
var res = {};
|
||||
Object.keys(data).forEach(function (propName) {
|
||||
if (this.whatTypeName(propName) === 'JSON' || data[propName] instanceof Array) {
|
||||
res[propName] = JSON.stringify(data[propName]);
|
||||
} else {
|
||||
res[propName] = data[propName];
|
||||
}
|
||||
}.bind(this));
|
||||
return res;
|
||||
};
|
||||
|
||||
AbstractClass.prototype.whatTypeName = function (propName) {
|
||||
ModelBaseClass.prototype.whatTypeName = function (propName) {
|
||||
return this.constructor.whatTypeName(propName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new instance of Model class, saved in database
|
||||
*
|
||||
* @param data [optional]
|
||||
* @param callback(err, obj)
|
||||
* callback called with arguments:
|
||||
*
|
||||
* - err (null or Error)
|
||||
* - instance (null or Model)
|
||||
*/
|
||||
AbstractClass.create = function (data, callback) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
var Model = this;
|
||||
var modelName = Model.modelName;
|
||||
|
||||
if (typeof data === 'function') {
|
||||
callback = data;
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
callback = function () {};
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
|
||||
if (data instanceof Array) {
|
||||
var instances = [];
|
||||
var errors = Array(data.length);
|
||||
var gotError = false;
|
||||
var wait = data.length;
|
||||
if (wait === 0) callback(null, []);
|
||||
|
||||
var instances = [];
|
||||
for (var i = 0; i < data.length; i += 1) {
|
||||
(function(d, i) {
|
||||
instances.push(Model.create(d, function(err, inst) {
|
||||
if (err) {
|
||||
errors[i] = err;
|
||||
gotError = true;
|
||||
}
|
||||
modelCreated();
|
||||
}));
|
||||
})(data[i], i);
|
||||
}
|
||||
|
||||
return instances;
|
||||
|
||||
function modelCreated() {
|
||||
if (--wait === 0) {
|
||||
callback(gotError ? errors : null, instances);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var obj;
|
||||
// if we come from save
|
||||
if (data instanceof Model && !data.id) {
|
||||
obj = data;
|
||||
} else {
|
||||
obj = new Model(data);
|
||||
}
|
||||
data = obj.toObject(true);
|
||||
|
||||
// validation required
|
||||
obj.isValid(function(valid) {
|
||||
if (valid) {
|
||||
create();
|
||||
} else {
|
||||
callback(new ValidationError(obj), obj);
|
||||
}
|
||||
}, data);
|
||||
|
||||
function create() {
|
||||
obj.trigger('create', function(createDone) {
|
||||
obj.trigger('save', function(saveDone) {
|
||||
|
||||
this._adapter().create(modelName, this.constructor._forDB(obj.toObject(true)), function (err, id, rev) {
|
||||
if (id) {
|
||||
obj.__data.id = id;
|
||||
obj.__dataWas.id = id;
|
||||
defineReadonlyProp(obj, 'id', id);
|
||||
}
|
||||
if (rev) {
|
||||
obj._rev = rev
|
||||
}
|
||||
if (err) {
|
||||
return callback(err, obj);
|
||||
}
|
||||
saveDone.call(obj, function () {
|
||||
createDone.call(obj, function () {
|
||||
callback(err, obj);
|
||||
});
|
||||
});
|
||||
}, obj);
|
||||
}, obj);
|
||||
}, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
function stillConnecting(schema, obj, args) {
|
||||
if (schema.connected) return false;
|
||||
if (schema.connecting) return true;
|
||||
var method = args.callee;
|
||||
schema.once('connected', function () {
|
||||
method.apply(obj, [].slice.call(args));
|
||||
});
|
||||
schema.connect();
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update or insert
|
||||
*/
|
||||
AbstractClass.upsert = AbstractClass.updateOrCreate = function upsert(data, callback) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
var Model = this;
|
||||
if (!data.id) return this.create(data, callback);
|
||||
if (this.schema.adapter.updateOrCreate) {
|
||||
var inst = new Model(data);
|
||||
this.schema.adapter.updateOrCreate(Model.modelName, inst.toObject(true), function (err, data) {
|
||||
var obj;
|
||||
if (data) {
|
||||
inst._initProperties(data);
|
||||
obj = inst;
|
||||
} else {
|
||||
obj = null;
|
||||
}
|
||||
callback(err, obj);
|
||||
});
|
||||
} else {
|
||||
this.find(data.id, function (err, inst) {
|
||||
if (err) return callback(err);
|
||||
if (inst) {
|
||||
inst.updateAttributes(data, callback);
|
||||
} else {
|
||||
var obj = new Model(data);
|
||||
obj.save(data, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find one record, same as `all`, limited by 1 and return object, not collection,
|
||||
* if not found, create using data provided as second argument
|
||||
*
|
||||
* @param {Object} query - search conditions: {where: {test: 'me'}}.
|
||||
* @param {Object} data - object to create.
|
||||
* @param {Function} cb - callback called with (err, instance)
|
||||
*/
|
||||
AbstractClass.findOrCreate = function findOrCreate(query, data, callback) {
|
||||
if (typeof query === 'undefined') {
|
||||
query = {where: {}};
|
||||
}
|
||||
if (typeof data === 'function' || typeof data === 'undefined') {
|
||||
callback = data;
|
||||
data = query && query.where;
|
||||
}
|
||||
if (typeof callback === 'undefined') {
|
||||
callback = function () {};
|
||||
}
|
||||
|
||||
var t = this;
|
||||
this.findOne(query, function (err, record) {
|
||||
if (err) return callback(err);
|
||||
if (record) return callback(null, record);
|
||||
t.create(data, callback);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether object exitst in database
|
||||
*
|
||||
* @param {id} id - identifier of object (primary key value)
|
||||
* @param {Function} cb - callbacl called with (err, exists: Bool)
|
||||
*/
|
||||
AbstractClass.exists = function exists(id, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (id) {
|
||||
this.schema.adapter.exists(this.modelName, id, cb);
|
||||
} else {
|
||||
cb(new Error('Model::exists requires positive id argument'));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Find object by id
|
||||
*
|
||||
* @param {id} id - primary key value
|
||||
* @param {Function} cb - callback called with (err, instance)
|
||||
*/
|
||||
AbstractClass.find = function find(id, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
this.schema.adapter.find(this.modelName, id, function (err, data) {
|
||||
var obj = null;
|
||||
if (data) {
|
||||
if (!data.id) {
|
||||
data.id = id;
|
||||
}
|
||||
obj = new this();
|
||||
obj._initProperties(data, false);
|
||||
}
|
||||
cb(err, obj);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Find all instances of Model, matched by query
|
||||
* make sure you have marked as `index: true` fields for filter or sort
|
||||
*
|
||||
* @param {Object} params (optional)
|
||||
*
|
||||
* - where: Object `{ key: val, key2: {gt: 'val2'}}`
|
||||
* - include: String, Object or Array. See AbstractClass.include documentation.
|
||||
* - order: String
|
||||
* - limit: Number
|
||||
* - skip: Number
|
||||
*
|
||||
* @param {Function} callback (required) called with arguments:
|
||||
*
|
||||
* - err (null or Error)
|
||||
* - Array of instances
|
||||
*/
|
||||
AbstractClass.all = function all(params, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (arguments.length === 1) {
|
||||
cb = params;
|
||||
params = null;
|
||||
}
|
||||
var constr = this;
|
||||
this.schema.adapter.all(this.modelName, params, function (err, data) {
|
||||
if (data && data.forEach) {
|
||||
data.forEach(function (d, i) {
|
||||
var obj = new constr;
|
||||
obj._initProperties(d, false);
|
||||
if (params && params.include && params.collect) {
|
||||
data[i] = obj.__cachedRelations[params.collect];
|
||||
} else {
|
||||
data[i] = obj;
|
||||
}
|
||||
});
|
||||
if (data && data.countBeforeLimit) {
|
||||
data.countBeforeLimit = data.countBeforeLimit;
|
||||
}
|
||||
cb(err, data);
|
||||
}
|
||||
else
|
||||
cb(err, []);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find one record, same as `all`, limited by 1 and return object, not collection
|
||||
*
|
||||
* @param {Object} params - search conditions: {where: {test: 'me'}}
|
||||
* @param {Function} cb - callback called with (err, instance)
|
||||
*/
|
||||
AbstractClass.findOne = function findOne(params, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (typeof params === 'function') {
|
||||
cb = params;
|
||||
params = {};
|
||||
}
|
||||
params.limit = 1;
|
||||
this.all(params, function (err, collection) {
|
||||
if (err || !collection || !collection.length > 0) return cb(err, null);
|
||||
cb(err, collection[0]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy all records
|
||||
* @param {Function} cb - callback called with (err)
|
||||
*/
|
||||
AbstractClass.destroyAll = function destroyAll(cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
this.schema.adapter.destroyAll(this.modelName, function (err) {
|
||||
if ('function' === typeof cb) {
|
||||
cb(err);
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* Return count of matched records
|
||||
*
|
||||
* @param {Object} where - search conditions (optional)
|
||||
* @param {Function} cb - callback, called with (err, count)
|
||||
*/
|
||||
AbstractClass.count = function (where, cb) {
|
||||
if (stillConnecting(this.schema, this, arguments)) return;
|
||||
|
||||
if (typeof where === 'function') {
|
||||
cb = where;
|
||||
where = null;
|
||||
}
|
||||
this.schema.adapter.count(this.modelName, cb, where);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return string representation of class
|
||||
*
|
||||
* @override default toString method
|
||||
*/
|
||||
AbstractClass.toString = function () {
|
||||
ModelBaseClass.toString = function () {
|
||||
return '[Model ' + this.modelName + ']';
|
||||
};
|
||||
|
||||
/**
|
||||
* Save instance. When instance haven't id, create method called instead.
|
||||
* Triggers: validate, save, update | create
|
||||
* @param options {validate: true, throws: false} [optional]
|
||||
* @param callback(err, obj)
|
||||
*/
|
||||
AbstractClass.prototype.save = function (options, callback) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
if (typeof options == 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
callback = callback || function () {};
|
||||
options = options || {};
|
||||
|
||||
if (!('validate' in options)) {
|
||||
options.validate = true;
|
||||
}
|
||||
if (!('throws' in options)) {
|
||||
options.throws = false;
|
||||
}
|
||||
|
||||
var inst = this;
|
||||
var data = inst.toObject(true);
|
||||
var Model = this.constructor;
|
||||
var modelName = Model.modelName;
|
||||
|
||||
if (!this.id) {
|
||||
return Model.create(this, callback);
|
||||
}
|
||||
|
||||
// validate first
|
||||
if (!options.validate) {
|
||||
return save();
|
||||
}
|
||||
|
||||
inst.isValid(function (valid) {
|
||||
if (valid) {
|
||||
save();
|
||||
} else {
|
||||
var err = new ValidationError(inst);
|
||||
// throws option is dangerous for async usage
|
||||
if (options.throws) {
|
||||
throw err;
|
||||
}
|
||||
callback(err, inst);
|
||||
}
|
||||
});
|
||||
|
||||
// then save
|
||||
function save() {
|
||||
inst.trigger('save', function (saveDone) {
|
||||
inst.trigger('update', function (updateDone) {
|
||||
inst._adapter().save(modelName, inst.constructor._forDB(data), function (err) {
|
||||
if (err) {
|
||||
return callback(err, inst);
|
||||
}
|
||||
inst._initProperties(data, false);
|
||||
updateDone.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
callback(err, inst);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, data);
|
||||
}, data);
|
||||
}
|
||||
};
|
||||
|
||||
AbstractClass.prototype.isNewRecord = function () {
|
||||
return !this.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return adapter of current record
|
||||
* @private
|
||||
*/
|
||||
AbstractClass.prototype._adapter = function () {
|
||||
return this.schema.adapter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert instance to Object
|
||||
*
|
||||
|
@ -567,7 +156,7 @@ AbstractClass.prototype._adapter = function () {
|
|||
* otherwise all enumerable properties returned
|
||||
* @returns {Object} - canonical object representation (no getters and setters)
|
||||
*/
|
||||
AbstractClass.prototype.toObject = function (onlySchema) {
|
||||
ModelBaseClass.prototype.toObject = function (onlySchema) {
|
||||
var data = {};
|
||||
var ds = this.constructor.schema.definitions[this.constructor.modelName];
|
||||
var properties = ds.properties;
|
||||
|
@ -594,113 +183,16 @@ AbstractClass.prototype.toObject = function (onlySchema) {
|
|||
return data;
|
||||
};
|
||||
|
||||
// AbstractClass.prototype.hasOwnProperty = function (prop) {
|
||||
// ModelBaseClass.prototype.hasOwnProperty = function (prop) {
|
||||
// return this.__data && this.__data.hasOwnProperty(prop) ||
|
||||
// Object.getOwnPropertyNames(this).indexOf(prop) !== -1;
|
||||
// };
|
||||
|
||||
AbstractClass.prototype.toJSON = function () {
|
||||
ModelBaseClass.prototype.toJSON = function () {
|
||||
return this.toObject();
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete object from persistence
|
||||
*
|
||||
* @triggers `destroy` hook (async) before and after destroying object
|
||||
*/
|
||||
AbstractClass.prototype.destroy = function (cb) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
this.trigger('destroy', function (destroyed) {
|
||||
this._adapter().destroy(this.constructor.modelName, this.id, function (err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
destroyed(function () {
|
||||
if(cb) cb();
|
||||
});
|
||||
}.bind(this));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Update single attribute
|
||||
*
|
||||
* equals to `updateAttributes({name: value}, cb)
|
||||
*
|
||||
* @param {String} name - name of property
|
||||
* @param {Mixed} value - value of property
|
||||
* @param {Function} callback - callback called with (err, instance)
|
||||
*/
|
||||
AbstractClass.prototype.updateAttribute = function updateAttribute(name, value, callback) {
|
||||
var data = {};
|
||||
data[name] = value;
|
||||
this.updateAttributes(data, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update set of attributes
|
||||
*
|
||||
* this method performs validation before updating
|
||||
*
|
||||
* @trigger `validation`, `save` and `update` hooks
|
||||
* @param {Object} data - data to update
|
||||
* @param {Function} callback - callback called with (err, instance)
|
||||
*/
|
||||
AbstractClass.prototype.updateAttributes = function updateAttributes(data, cb) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
var inst = this;
|
||||
var model = this.constructor.modelName;
|
||||
|
||||
if (typeof data === 'function') {
|
||||
cb = data;
|
||||
data = null;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
data = {};
|
||||
}
|
||||
|
||||
// update instance's properties
|
||||
Object.keys(data).forEach(function (key) {
|
||||
inst[key] = data[key];
|
||||
});
|
||||
|
||||
inst.isValid(function (valid) {
|
||||
if (!valid) {
|
||||
if (cb) {
|
||||
cb(new ValidationError(inst), inst);
|
||||
}
|
||||
} else {
|
||||
inst.trigger('save', function (saveDone) {
|
||||
inst.trigger('update', function (done) {
|
||||
|
||||
Object.keys(data).forEach(function (key) {
|
||||
inst[key] = data[key];
|
||||
});
|
||||
|
||||
inst._adapter().updateAttributes(model, inst.id, inst.constructor._forDB(data), function (err) {
|
||||
if (!err) {
|
||||
// update _was attrs
|
||||
Object.keys(data).forEach(function (key) {
|
||||
inst.__dataWas[key] = inst.__data[key];
|
||||
});
|
||||
}
|
||||
done.call(inst, function () {
|
||||
saveDone.call(inst, function () {
|
||||
cb(err, inst);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, data);
|
||||
}, data);
|
||||
}
|
||||
}, data);
|
||||
};
|
||||
|
||||
AbstractClass.prototype.fromObject = function (obj) {
|
||||
ModelBaseClass.prototype.fromObject = function (obj) {
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
this[key] = obj[key];
|
||||
}.bind(this));
|
||||
|
@ -712,29 +204,17 @@ AbstractClass.prototype.fromObject = function (obj) {
|
|||
* @param {String} attr - property name
|
||||
* @return Boolean
|
||||
*/
|
||||
AbstractClass.prototype.propertyChanged = function propertyChanged(attr) {
|
||||
ModelBaseClass.prototype.propertyChanged = function propertyChanged(attr) {
|
||||
return this.__data[attr] !== this.__dataWas[attr];
|
||||
};
|
||||
|
||||
/**
|
||||
* Reload object from persistence
|
||||
*
|
||||
* @requires `id` member of `object` to be able to call `find`
|
||||
* @param {Function} callback - called with (err, instance) arguments
|
||||
*/
|
||||
AbstractClass.prototype.reload = function reload(callback) {
|
||||
if (stillConnecting(this.constructor.schema, this, arguments)) return;
|
||||
|
||||
this.constructor.find(this.id, callback);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset dirty attributes
|
||||
*
|
||||
* this method does not perform any database operation it just reset object to it's
|
||||
* initial state
|
||||
*/
|
||||
AbstractClass.prototype.reset = function () {
|
||||
ModelBaseClass.prototype.reset = function () {
|
||||
var obj = this;
|
||||
Object.keys(obj).forEach(function (k) {
|
||||
if (k !== 'id' && !obj.constructor.schema.definitions[obj.constructor.modelName].properties[k]) {
|
||||
|
@ -746,7 +226,7 @@ AbstractClass.prototype.reset = function () {
|
|||
});
|
||||
};
|
||||
|
||||
AbstractClass.prototype.inspect = function () {
|
||||
ModelBaseClass.prototype.inspect = function () {
|
||||
return util.inspect(this.__data, false, 4, true);
|
||||
};
|
||||
|
||||
|
@ -761,18 +241,7 @@ function isdef(s) {
|
|||
return s !== undef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define readonly property on object
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
* @param {Mixed} value
|
||||
*/
|
||||
function defineReadonlyProp(obj, key, value) {
|
||||
Object.defineProperty(obj, key, {
|
||||
writable: false,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
value: value
|
||||
});
|
||||
ModelBaseClass.prototype.dataSource = function(name, settings) {
|
||||
require('./jutil').inherits(this.constructor, require('./dao'));
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ var defineScope = require('./scope.js').defineScope;
|
|||
/**
|
||||
* Relations mixins for ./model.js
|
||||
*/
|
||||
var Model = require('./model.js');
|
||||
var Model = require('./dao.js');
|
||||
|
||||
Model.relationNameFor = function relationNameFor(foreignKey) {
|
||||
for (var rel in this.relations) {
|
||||
|
|
123
lib/schema.js
123
lib/schema.js
|
@ -1,7 +1,8 @@
|
|||
/**
|
||||
* Module dependencies
|
||||
*/
|
||||
var AbstractClass = require('./model.js');
|
||||
var ModelBaseClass = require('./model.js');
|
||||
var DataAccessObject = require('./dao.js');
|
||||
var List = require('./list.js');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var util = require('util');
|
||||
|
@ -14,7 +15,7 @@ var existsSync = fs.existsSync || path.existsSync;
|
|||
* Export public API
|
||||
*/
|
||||
exports.Schema = Schema;
|
||||
// exports.AbstractClass = AbstractClass;
|
||||
// exports.ModelBaseClass = ModelBaseClass;
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
|
@ -60,7 +61,17 @@ Schema.registerType(Schema.JSON);
|
|||
* ```
|
||||
*/
|
||||
function Schema(name, settings) {
|
||||
// create blank models pool
|
||||
this.models = {};
|
||||
this.definitions = {};
|
||||
this.dataSource(name, settings);
|
||||
};
|
||||
|
||||
util.inherits(Schema, EventEmitter);
|
||||
|
||||
Schema.prototype.dataSource = function(name, settings) {
|
||||
var schema = this;
|
||||
|
||||
// just save everything we get
|
||||
this.name = name;
|
||||
this.settings = settings;
|
||||
|
@ -69,55 +80,55 @@ function Schema(name, settings) {
|
|||
this.connected = false;
|
||||
this.connecting = false;
|
||||
|
||||
// create blank models pool
|
||||
this.models = {};
|
||||
this.definitions = {};
|
||||
|
||||
// and initialize schema using adapter
|
||||
// this is only one initialization entry point of adapter
|
||||
// this module should define `adapter` member of `this` (schema)
|
||||
var adapter;
|
||||
if (typeof name === 'object') {
|
||||
adapter = name;
|
||||
this.name = adapter.name;
|
||||
} else if (name.match(/^\//)) {
|
||||
// try absolute path
|
||||
adapter = require(name);
|
||||
} else if (existsSync(__dirname + '/adapters/' + name + '.js')) {
|
||||
// try built-in adapter
|
||||
adapter = require('./adapters/' + name);
|
||||
} else {
|
||||
// try foreign adapter
|
||||
try {
|
||||
adapter = require('jugglingdb-' + name);
|
||||
} catch (e) {
|
||||
return console.log('\nWARNING: JugglingDB adapter "' + name + '" is not installed,\nso your models would not work, to fix run:\n\n npm install jugglingdb-' + name, '\n');
|
||||
if (name) {
|
||||
// and initialize schema using adapter
|
||||
// this is only one initialization entry point of adapter
|
||||
// this module should define `adapter` member of `this` (schema)
|
||||
var adapter;
|
||||
if (typeof name === 'object') {
|
||||
adapter = name;
|
||||
this.name = adapter.name;
|
||||
} else if (name.match(/^\//)) {
|
||||
// try absolute path
|
||||
adapter = require(name);
|
||||
} else if (existsSync(__dirname + '/adapters/' + name + '.js')) {
|
||||
// try built-in adapter
|
||||
adapter = require('./adapters/' + name);
|
||||
} else {
|
||||
// try foreign adapter
|
||||
try {
|
||||
adapter = require('jugglingdb-' + name);
|
||||
} catch (e) {
|
||||
return console.log('\nWARNING: JugglingDB adapter "' + name + '" is not installed,\nso your models would not work, to fix run:\n\n npm install jugglingdb-' + name, '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adapter.initialize(this, function () {
|
||||
if (adapter) {
|
||||
adapter.initialize(this, function () {
|
||||
|
||||
// we have an adaper now?
|
||||
if (!this.adapter) {
|
||||
throw new Error('Adapter is not defined correctly: it should create `adapter` member of schema');
|
||||
}
|
||||
// we have an adaper now?
|
||||
if (!this.adapter) {
|
||||
throw new Error('Adapter is not defined correctly: it should create `adapter` member of schema');
|
||||
}
|
||||
|
||||
this.adapter.log = function (query, start) {
|
||||
schema.log(query, start);
|
||||
};
|
||||
|
||||
this.adapter.logger = function (query) {
|
||||
var t1 = Date.now();
|
||||
var log = this.log;
|
||||
return function (q) {
|
||||
log(q || query, t1);
|
||||
this.adapter.log = function (query, start) {
|
||||
schema.log(query, start);
|
||||
};
|
||||
};
|
||||
|
||||
this.connected = true;
|
||||
this.emit('connected');
|
||||
this.adapter.logger = function (query) {
|
||||
var t1 = Date.now();
|
||||
var log = this.log;
|
||||
return function (q) {
|
||||
log(q || query, t1);
|
||||
};
|
||||
};
|
||||
|
||||
}.bind(this));
|
||||
this.connected = true;
|
||||
this.emit('connected');
|
||||
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
schema.connect = function(cb) {
|
||||
var schema = this;
|
||||
|
@ -139,9 +150,7 @@ function Schema(name, settings) {
|
|||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
util.inherits(Schema, EventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define class
|
||||
|
@ -190,7 +199,7 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
|||
if (!(this instanceof ModelConstructor)) {
|
||||
return new ModelConstructor(data);
|
||||
}
|
||||
AbstractClass.call(this, data);
|
||||
ModelBaseClass.call(this, data);
|
||||
hiddenProperty(this, 'schema', schema || this.constructor.schema);
|
||||
};
|
||||
|
||||
|
@ -198,12 +207,20 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
|||
hiddenProperty(NewClass, 'modelName', className);
|
||||
hiddenProperty(NewClass, 'relations', {});
|
||||
|
||||
// inherit AbstractClass methods
|
||||
for (var i in AbstractClass) {
|
||||
NewClass[i] = AbstractClass[i];
|
||||
// inherit ModelBaseClass methods
|
||||
for (var i in ModelBaseClass) {
|
||||
NewClass[i] = ModelBaseClass[i];
|
||||
}
|
||||
for (var j in AbstractClass.prototype) {
|
||||
NewClass.prototype[j] = AbstractClass.prototype[j];
|
||||
for (var j in ModelBaseClass.prototype) {
|
||||
NewClass.prototype[j] = ModelBaseClass.prototype[j];
|
||||
}
|
||||
|
||||
// inherit DataAccessObject methods
|
||||
for (var m in DataAccessObject) {
|
||||
NewClass[m] = DataAccessObject[m];
|
||||
}
|
||||
for (var n in DataAccessObject.prototype) {
|
||||
NewClass.prototype[n] = DataAccessObject.prototype[n];
|
||||
}
|
||||
|
||||
NewClass.getter = {};
|
||||
|
@ -218,12 +235,14 @@ Schema.prototype.define = function defineClass(className, properties, settings)
|
|||
settings: settings
|
||||
};
|
||||
|
||||
if(this.adapter) {
|
||||
// pass control to adapter
|
||||
this.adapter.define({
|
||||
model: NewClass,
|
||||
properties: properties,
|
||||
settings: settings
|
||||
});
|
||||
}
|
||||
|
||||
NewClass.prototype.__defineGetter__('id', function () {
|
||||
return this.__data.id;
|
||||
|
|
|
@ -6,7 +6,7 @@ exports.defineScope = defineScope;
|
|||
/**
|
||||
* Scope mixin for ./model.js
|
||||
*/
|
||||
var Model = require('./model.js');
|
||||
var Model = require('./dao.js');
|
||||
|
||||
/**
|
||||
* Define scope
|
||||
|
|
|
@ -18,7 +18,7 @@ exports.ValidationError = ValidationError;
|
|||
* In more complicated cases it can be {Hash} of messages (for each case):
|
||||
* `User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});`
|
||||
*/
|
||||
var Validatable = require('./model.js');
|
||||
var Validatable = require('./dao.js');
|
||||
|
||||
/**
|
||||
* Validate presence. This validation fails when validated field is blank.
|
||||
|
|
Loading…
Reference in New Issue