2013-04-11 23:23:34 +00:00
|
|
|
/**
|
|
|
|
* Module exports class Model
|
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
module.exports = ModelBaseClass;
|
2013-04-11 23:23:34 +00:00
|
|
|
|
2011-10-10 13:22:51 +00:00
|
|
|
/**
|
2012-03-27 14:22:24 +00:00
|
|
|
* Module dependencies
|
2011-10-10 13:22:51 +00:00
|
|
|
*/
|
2013-05-23 23:38:14 +00:00
|
|
|
|
2011-10-10 13:22:51 +00:00
|
|
|
var util = require('util');
|
2013-07-26 20:06:43 +00:00
|
|
|
var traverse = require('traverse');
|
2013-05-28 05:20:30 +00:00
|
|
|
var jutil = require('./jutil');
|
|
|
|
var List = require('./list');
|
|
|
|
var Hookable = require('./hooks');
|
2013-06-05 21:33:52 +00:00
|
|
|
var validations = require('./validations.js');
|
2011-10-10 13:22:51 +00:00
|
|
|
|
2013-04-11 23:23:34 +00:00
|
|
|
var BASE_TYPES = ['String', 'Boolean', 'Number', 'Date', 'Text'];
|
2011-10-10 13:22:51 +00:00
|
|
|
|
2011-10-08 17:11:26 +00:00
|
|
|
/**
|
2013-04-11 23:23:34 +00:00
|
|
|
* Model class - base class for all persist objects
|
2013-07-23 18:16:43 +00:00
|
|
|
* provides **common API** to access any database connector.
|
2013-07-23 21:40:44 +00:00
|
|
|
* This class describes only abstract behavior layer, refer to `lib/connectors/*.js`
|
2013-07-23 18:16:43 +00:00
|
|
|
* to learn more about specific connector implementations
|
2012-03-27 14:22:24 +00:00
|
|
|
*
|
2013-05-17 15:49:57 +00:00
|
|
|
* `ModelBaseClass` mixes `Validatable` and `Hookable` classes methods
|
2012-03-27 14:22:24 +00:00
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
* @param {Object} data - initial object data
|
2011-10-08 17:11:26 +00:00
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
function ModelBaseClass(data) {
|
2012-04-19 15:01:40 +00:00
|
|
|
this._initProperties(data, true);
|
|
|
|
}
|
|
|
|
|
2013-07-26 20:06:43 +00:00
|
|
|
// FIXME: [rfeng] We need to make sure the input data should not be mutated. Disabled cloning for now to get tests passing
|
|
|
|
function clone(data) {
|
|
|
|
/*
|
|
|
|
if(!(data instanceof ModelBaseClass)) {
|
|
|
|
if(data && (Array.isArray(data) || 'object' === typeof data)) {
|
|
|
|
return traverse(data).clone();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Initialize properties
|
|
|
|
* @param data
|
|
|
|
* @param applySetters
|
|
|
|
* @private
|
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.prototype._initProperties = function (data, applySetters) {
|
2011-10-08 17:11:26 +00:00
|
|
|
var self = this;
|
2012-04-19 15:01:40 +00:00
|
|
|
var ctor = this.constructor;
|
2013-07-01 23:49:43 +00:00
|
|
|
|
2013-10-02 22:18:50 +00:00
|
|
|
var properties = ctor.definition.build();
|
2011-10-08 17:11:26 +00:00
|
|
|
data = data || {};
|
|
|
|
|
2012-10-29 23:37:19 +00:00
|
|
|
Object.defineProperty(this, '__cachedRelations', {
|
|
|
|
writable: true,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
value: {}
|
|
|
|
});
|
|
|
|
|
2012-10-13 13:59:25 +00:00
|
|
|
Object.defineProperty(this, '__data', {
|
|
|
|
writable: true,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
value: {}
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(this, '__dataWas', {
|
|
|
|
writable: true,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
value: {}
|
|
|
|
});
|
|
|
|
|
2012-12-16 17:05:36 +00:00
|
|
|
if (data['__cachedRelations']) {
|
|
|
|
this.__cachedRelations = data['__cachedRelations'];
|
|
|
|
}
|
|
|
|
|
2013-07-25 05:54:47 +00:00
|
|
|
// Check if the strict option is set to false for the model
|
2013-10-02 22:18:50 +00:00
|
|
|
var strict = ctor.definition.settings.strict;
|
2013-07-25 05:54:47 +00:00
|
|
|
|
2013-04-18 09:05:11 +00:00
|
|
|
for (var i in data) {
|
|
|
|
if (i in properties) {
|
2013-07-26 20:06:43 +00:00
|
|
|
this.__data[i] = this.__dataWas[i] = clone(data[i]);
|
2013-04-18 09:05:11 +00:00
|
|
|
} else if (i in ctor.relations) {
|
|
|
|
this.__data[ctor.relations[i].keyFrom] = this.__dataWas[i] = data[i][ctor.relations[i].keyTo];
|
|
|
|
this.__cachedRelations[i] = data[i];
|
2013-07-25 05:54:47 +00:00
|
|
|
} else {
|
|
|
|
if(strict === false) {
|
2013-07-26 20:06:43 +00:00
|
|
|
this.__data[i] = this.__dataWas[i] = clone(data[i]);
|
2013-07-25 05:54:47 +00:00
|
|
|
} else if(strict === 'throw') {
|
|
|
|
throw new Error('Unknown property: ' + i);
|
|
|
|
}
|
2013-04-18 09:05:11 +00:00
|
|
|
}
|
|
|
|
}
|
2012-10-13 13:59:25 +00:00
|
|
|
|
2013-04-07 13:59:24 +00:00
|
|
|
if (applySetters === true) {
|
2013-10-07 04:27:02 +00:00
|
|
|
for(var propertyName in data) {
|
2013-10-02 05:14:21 +00:00
|
|
|
if((propertyName in properties) || (propertyName in ctor.relations)) {
|
|
|
|
self[propertyName] = self.__data[propertyName] || data[propertyName];
|
2013-08-30 23:51:17 +00:00
|
|
|
}
|
2013-10-07 04:27:02 +00:00
|
|
|
}
|
2013-08-30 23:51:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set the unknown properties as properties to the object
|
|
|
|
if(strict === false) {
|
2013-10-07 04:27:02 +00:00
|
|
|
for(var propertyName in data) {
|
2013-10-02 05:14:21 +00:00
|
|
|
if(!(propertyName in properties)) {
|
|
|
|
self[propertyName] = self.__data[propertyName] || data[propertyName];
|
2013-07-25 05:54:47 +00:00
|
|
|
}
|
2013-10-07 04:27:02 +00:00
|
|
|
}
|
2012-10-13 13:59:25 +00:00
|
|
|
}
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
ctor.forEachProperty(function (propertyName) {
|
2012-10-13 13:59:25 +00:00
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
if ('undefined' === typeof self.__data[propertyName]) {
|
|
|
|
self.__data[propertyName] = self.__dataWas[propertyName] = getDefault(propertyName);
|
2012-10-13 13:59:25 +00:00
|
|
|
} else {
|
2013-10-02 05:14:21 +00:00
|
|
|
self.__dataWas[propertyName] = self.__data[propertyName];
|
2012-10-13 13:59:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
ctor.forEachProperty(function (propertyName) {
|
2011-10-08 17:11:26 +00:00
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
var type = properties[propertyName].type;
|
2013-06-26 03:31:00 +00:00
|
|
|
|
2012-09-10 15:57:21 +00:00
|
|
|
if (BASE_TYPES.indexOf(type.name) === -1) {
|
2013-10-02 05:14:21 +00:00
|
|
|
if (typeof self.__data[propertyName] !== 'object' && self.__data[propertyName]) {
|
2012-09-11 16:51:31 +00:00
|
|
|
try {
|
2013-10-02 05:14:21 +00:00
|
|
|
self.__data[propertyName] = JSON.parse(self.__data[propertyName] + '');
|
2012-09-11 16:51:31 +00:00
|
|
|
} catch (e) {
|
2013-10-02 05:14:21 +00:00
|
|
|
self.__data[propertyName] = String(self.__data[propertyName]);
|
2012-09-10 15:57:21 +00:00
|
|
|
}
|
2012-09-11 16:51:31 +00:00
|
|
|
}
|
2013-07-26 20:06:43 +00:00
|
|
|
if (type.name === 'Array' || Array.isArray(type)) {
|
2013-10-02 05:14:21 +00:00
|
|
|
if(!(self.__data[propertyName] instanceof List)) {
|
|
|
|
self.__data[propertyName] = new List(self.__data[propertyName], type, self);
|
2013-07-26 20:06:43 +00:00
|
|
|
}
|
2012-09-10 15:57:21 +00:00
|
|
|
}
|
2012-05-29 11:16:24 +00:00
|
|
|
}
|
|
|
|
|
2012-10-13 13:59:25 +00:00
|
|
|
});
|
2011-10-11 19:51:32 +00:00
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
function getDefault(propertyName) {
|
|
|
|
var def = properties[propertyName]['default'];
|
|
|
|
if (def !== undefined) {
|
2011-10-11 19:51:32 +00:00
|
|
|
if (typeof def === 'function') {
|
|
|
|
return def();
|
|
|
|
} else {
|
|
|
|
return def;
|
|
|
|
}
|
|
|
|
} else {
|
2013-03-24 21:25:10 +00:00
|
|
|
return undefined;
|
2011-10-11 19:51:32 +00:00
|
|
|
}
|
|
|
|
}
|
2011-11-17 04:24:43 +00:00
|
|
|
|
2013-03-24 21:25:10 +00:00
|
|
|
this.trigger('initialize');
|
2012-07-13 13:53:22 +00:00
|
|
|
}
|
2011-10-08 17:11:26 +00:00
|
|
|
|
2012-03-27 14:22:24 +00:00
|
|
|
/**
|
|
|
|
* @param {String} prop - property name
|
|
|
|
* @param {Object} params - various property configuration
|
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.defineProperty = function (prop, params) {
|
2013-07-23 18:16:43 +00:00
|
|
|
this.dataSource.defineProperty(this.modelName, prop, params);
|
2011-12-09 15:23:29 +00:00
|
|
|
};
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
ModelBaseClass.getPropertyType = function (propName) {
|
2013-10-03 21:49:03 +00:00
|
|
|
var prop = this.definition.properties[propName];
|
2013-08-26 20:38:24 +00:00
|
|
|
if(!prop) {
|
|
|
|
// The property is not part of the definition
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
if (!prop.type) {
|
|
|
|
throw new Error('Type not defined for property ' + this.modelName + '.' + propName);
|
|
|
|
// return null;
|
2013-04-23 14:10:56 +00:00
|
|
|
}
|
|
|
|
return prop.type.name;
|
2012-05-29 11:16:24 +00:00
|
|
|
};
|
|
|
|
|
2013-10-02 05:14:21 +00:00
|
|
|
ModelBaseClass.prototype.getPropertyType = function (propName) {
|
|
|
|
return this.constructor.getPropertyType(propName);
|
2012-01-18 15:20:05 +00:00
|
|
|
};
|
|
|
|
|
2012-03-27 14:22:24 +00:00
|
|
|
/**
|
|
|
|
* Return string representation of class
|
|
|
|
*
|
|
|
|
* @override default toString method
|
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.toString = function () {
|
2011-10-08 17:11:26 +00:00
|
|
|
return '[Model ' + this.modelName + ']';
|
2012-07-13 13:53:22 +00:00
|
|
|
};
|
2011-10-08 17:11:26 +00:00
|
|
|
|
2012-03-27 14:22:24 +00:00
|
|
|
/**
|
|
|
|
* Convert instance to Object
|
|
|
|
*
|
2013-07-23 18:16:43 +00:00
|
|
|
* @param {Boolean} onlySchema - restrict properties to dataSource only, default false
|
|
|
|
* when onlySchema == true, only properties defined in dataSource returned,
|
2012-03-27 14:22:24 +00:00
|
|
|
* otherwise all enumerable properties returned
|
|
|
|
* @returns {Object} - canonical object representation (no getters and setters)
|
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.prototype.toObject = function (onlySchema) {
|
2011-10-08 17:11:26 +00:00
|
|
|
var data = {};
|
2012-10-13 13:59:25 +00:00
|
|
|
var self = this;
|
|
|
|
|
2013-10-02 22:18:50 +00:00
|
|
|
var schemaLess = this.constructor.definition.settings.strict === false || !onlySchema;
|
2013-10-02 05:14:21 +00:00
|
|
|
this.constructor.forEachProperty(function (propertyName) {
|
|
|
|
if (self[propertyName] instanceof List) {
|
|
|
|
data[propertyName] = self[propertyName].toObject(!schemaLess);
|
|
|
|
} else if (self.__data.hasOwnProperty(propertyName)) {
|
|
|
|
if(self[propertyName] !== undefined && self[propertyName]!== null && self[propertyName].toObject) {
|
|
|
|
data[propertyName] = self[propertyName].toObject(!schemaLess);
|
2013-07-13 01:59:43 +00:00
|
|
|
} else {
|
2013-10-02 05:14:21 +00:00
|
|
|
data[propertyName] = self[propertyName];
|
2013-07-13 01:59:43 +00:00
|
|
|
}
|
2012-09-10 15:57:21 +00:00
|
|
|
} else {
|
2013-10-02 05:14:21 +00:00
|
|
|
data[propertyName] = null;
|
2012-09-10 15:57:21 +00:00
|
|
|
}
|
2012-10-13 13:59:25 +00:00
|
|
|
});
|
|
|
|
|
2013-08-29 04:49:05 +00:00
|
|
|
if (schemaLess) {
|
2013-10-07 04:27:02 +00:00
|
|
|
for(var propertyName in self.__data) {
|
2013-10-02 05:14:21 +00:00
|
|
|
if (!data.hasOwnProperty(propertyName)) {
|
2013-10-04 21:32:51 +00:00
|
|
|
var val = self.hasOwnProperty(propertyName) ? self[propertyName] : self.__data[propertyName];
|
2013-08-29 04:49:05 +00:00
|
|
|
if(val !== undefined && val!== null && val.toObject) {
|
2013-10-02 05:14:21 +00:00
|
|
|
data[propertyName] = val.toObject(!schemaLess);
|
2013-07-25 05:54:47 +00:00
|
|
|
} else {
|
2013-10-02 05:14:21 +00:00
|
|
|
data[propertyName] = val;
|
2013-07-25 05:54:47 +00:00
|
|
|
}
|
2012-10-13 13:59:25 +00:00
|
|
|
}
|
2013-10-07 04:27:02 +00:00
|
|
|
}
|
2012-10-13 13:59:25 +00:00
|
|
|
}
|
2011-10-08 17:11:26 +00:00
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
2013-05-17 15:49:57 +00:00
|
|
|
// ModelBaseClass.prototype.hasOwnProperty = function (prop) {
|
2012-10-15 21:29:30 +00:00
|
|
|
// return this.__data && this.__data.hasOwnProperty(prop) ||
|
|
|
|
// Object.getOwnPropertyNames(this).indexOf(prop) !== -1;
|
|
|
|
// };
|
2012-10-13 13:59:25 +00:00
|
|
|
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.prototype.toJSON = function () {
|
2012-09-10 15:57:21 +00:00
|
|
|
return this.toObject();
|
|
|
|
};
|
|
|
|
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.prototype.fromObject = function (obj) {
|
2013-10-07 04:27:02 +00:00
|
|
|
for(var key in obj) {
|
2012-04-09 16:24:35 +00:00
|
|
|
this[key] = obj[key];
|
2013-10-07 04:27:02 +00:00
|
|
|
}
|
2012-04-09 16:24:35 +00:00
|
|
|
};
|
|
|
|
|
2011-10-08 17:11:26 +00:00
|
|
|
/**
|
|
|
|
* Checks is property changed based on current property and initial value
|
2012-03-27 14:22:24 +00:00
|
|
|
*
|
2013-10-02 05:14:21 +00:00
|
|
|
* @param {String} propertyName - property name
|
2011-10-08 17:11:26 +00:00
|
|
|
* @return Boolean
|
|
|
|
*/
|
2013-10-02 05:14:21 +00:00
|
|
|
ModelBaseClass.prototype.propertyChanged = function propertyChanged(propertyName) {
|
|
|
|
return this.__data[propertyName] !== this.__dataWas[propertyName];
|
2011-10-08 17:11:26 +00:00
|
|
|
};
|
|
|
|
|
2012-03-27 14:22:24 +00:00
|
|
|
/**
|
|
|
|
* Reset dirty attributes
|
|
|
|
*
|
|
|
|
* this method does not perform any database operation it just reset object to it's
|
|
|
|
* initial state
|
|
|
|
*/
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.prototype.reset = function () {
|
2011-11-28 16:31:01 +00:00
|
|
|
var obj = this;
|
2013-10-07 04:27:02 +00:00
|
|
|
for(var k in obj) {
|
2013-07-23 18:16:43 +00:00
|
|
|
if (k !== 'id' && !obj.constructor.dataSource.definitions[obj.constructor.modelName].properties[k]) {
|
2011-12-11 08:58:34 +00:00
|
|
|
delete obj[k];
|
|
|
|
}
|
2011-11-28 16:31:01 +00:00
|
|
|
if (obj.propertyChanged(k)) {
|
2013-10-02 05:14:21 +00:00
|
|
|
obj[k] = obj[k + '$was'];
|
2011-11-28 16:31:01 +00:00
|
|
|
}
|
2013-10-07 04:27:02 +00:00
|
|
|
}
|
2011-11-28 16:31:01 +00:00
|
|
|
};
|
|
|
|
|
2013-05-17 15:49:57 +00:00
|
|
|
ModelBaseClass.prototype.inspect = function () {
|
2012-10-15 23:15:29 +00:00
|
|
|
return util.inspect(this.__data, false, 4, true);
|
|
|
|
};
|
|
|
|
|
2013-05-28 20:50:59 +00:00
|
|
|
ModelBaseClass.mixin = function(anotherClass, options) {
|
|
|
|
return jutil.mixin(this, anotherClass, options);
|
2013-10-02 05:14:21 +00:00
|
|
|
};
|
2013-05-28 20:50:59 +00:00
|
|
|
|
2013-10-31 17:51:33 +00:00
|
|
|
ModelBaseClass.prototype.getDataSource = function () {
|
|
|
|
return this.__dataSource || this.constructor.dataSource;
|
|
|
|
}
|
|
|
|
ModelBaseClass.getDataSource = function () {
|
|
|
|
return this.dataSource;
|
|
|
|
}
|
|
|
|
|
2013-05-28 05:20:30 +00:00
|
|
|
jutil.mixin(ModelBaseClass, Hookable);
|
2013-10-04 21:32:51 +00:00
|
|
|
jutil.mixin(ModelBaseClass, validations.Validatable);
|