loopback-datasource-juggler/lib/model.js

253 lines
6.8 KiB
JavaScript
Raw Normal View History

2013-04-11 23:23:34 +00:00
/**
* Module exports class Model
*/
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
*/
2011-10-10 13:22:51 +00:00
var util = require('util');
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
2012-03-27 14:22:24 +00:00
* 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
*
* `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
*/
function ModelBaseClass(data) {
2012-04-19 15:01:40 +00:00
this._initProperties(data, true);
}
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
2012-04-19 15:01:40 +00:00
var ds = ctor.schema.definitions[ctor.modelName];
2013-05-24 00:29:03 +00:00
2011-10-08 17:11:26 +00:00
var properties = ds.properties;
data = data || {};
2012-10-29 23:37:19 +00:00
Object.defineProperty(this, '__cachedRelations', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
Object.defineProperty(this, '__data', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
Object.defineProperty(this, '__dataWas', {
writable: true,
enumerable: false,
configurable: true,
value: {}
});
if (data['__cachedRelations']) {
this.__cachedRelations = data['__cachedRelations'];
}
for (var i in data) {
if (i in properties) {
this.__data[i] = this.__dataWas[i] = data[i];
} 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-04-07 13:59:24 +00:00
if (applySetters === true) {
Object.keys(data).forEach(function (attr) {
self[attr] = data[attr];
2011-10-08 17:11:26 +00:00
});
}
ctor.forEachProperty(function (attr) {
if ('undefined' === typeof self.__data[attr]) {
self.__data[attr] = self.__dataWas[attr] = getDefault(attr);
} else {
self.__dataWas[attr] = self.__data[attr];
}
});
ctor.forEachProperty(function (attr) {
2011-10-08 17:11:26 +00:00
2012-09-10 15:57:21 +00:00
var type = properties[attr].type;
2013-06-26 03:31:00 +00:00
2012-09-10 15:57:21 +00:00
if (BASE_TYPES.indexOf(type.name) === -1) {
if (typeof self.__data[attr] !== 'object' && self.__data[attr]) {
2012-09-11 16:51:31 +00:00
try {
self.__data[attr] = JSON.parse(self.__data[attr] + '');
2012-09-11 16:51:31 +00:00
} catch (e) {
self.__data[attr] = String(self.__data[attr]);
2012-09-10 15:57:21 +00:00
}
2012-09-11 16:51:31 +00:00
}
if (type.name === 'Array' || typeof type === 'object' && type.constructor.name === 'Array') {
self.__data[attr] = new List(self.__data[attr], type, self);
2012-09-10 15:57:21 +00:00
}
2012-05-29 11:16:24 +00:00
}
});
2011-10-11 19:51:32 +00:00
function getDefault(attr) {
2012-07-13 13:53:22 +00:00
var def = properties[attr]['default'];
2011-10-11 19:51:32 +00:00
if (isdef(def)) {
if (typeof def === 'function') {
return def();
} else {
return def;
}
} else {
return undefined;
2011-10-11 19:51:32 +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
*/
ModelBaseClass.defineProperty = function (prop, params) {
2011-12-09 15:23:29 +00:00
this.schema.defineProperty(this.modelName, prop, params);
};
ModelBaseClass.whatTypeName = function (propName) {
2013-04-23 14:10:56 +00:00
var prop = this.schema.definitions[this.modelName].properties[propName];
if (!prop || !prop.type) {
throw new Error('Undefined type for ' + this.modelName + ':' + propName);
}
return prop.type.name;
2012-05-29 11:16:24 +00:00
};
ModelBaseClass.prototype.whatTypeName = function (propName) {
2012-01-18 15:20:05 +00:00
return this.constructor.whatTypeName(propName);
};
2012-03-27 14:22:24 +00:00
/**
* Return string representation of class
*
* @override default toString method
*/
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
*
* @param {Boolean} onlySchema - restrict properties to schema only, default false
* when onlySchema == true, only properties defined in schema returned,
* otherwise all enumerable properties returned
* @returns {Object} - canonical object representation (no getters and setters)
*/
ModelBaseClass.prototype.toObject = function (onlySchema) {
2011-10-08 17:11:26 +00:00
var data = {};
var ds = this.constructor.schema.definitions[this.constructor.modelName];
var properties = ds.properties;
var self = this;
this.constructor.forEachProperty(function (attr) {
if (self[attr] instanceof List) {
data[attr] = self[attr].toObject();
} else if (self.__data.hasOwnProperty(attr)) {
data[attr] = self[attr];
2012-09-10 15:57:21 +00:00
} else {
data[attr] = null;
2012-09-10 15:57:21 +00:00
}
});
if (!onlySchema) {
Object.keys(self).forEach(function (attr) {
if (!data.hasOwnProperty(attr)) {
data[attr] = this[attr];
}
});
}
2011-10-08 17:11:26 +00:00
return data;
};
// ModelBaseClass.prototype.hasOwnProperty = function (prop) {
// return this.__data && this.__data.hasOwnProperty(prop) ||
// Object.getOwnPropertyNames(this).indexOf(prop) !== -1;
// };
ModelBaseClass.prototype.toJSON = function () {
2012-09-10 15:57:21 +00:00
return this.toObject();
};
ModelBaseClass.prototype.fromObject = function (obj) {
Object.keys(obj).forEach(function (key) {
this[key] = obj[key];
}.bind(this));
};
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
*
* @param {String} attr - property name
2011-10-08 17:11:26 +00:00
* @return Boolean
*/
ModelBaseClass.prototype.propertyChanged = function propertyChanged(attr) {
return this.__data[attr] !== this.__dataWas[attr];
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
*/
ModelBaseClass.prototype.reset = function () {
2011-11-28 16:31:01 +00:00
var obj = this;
Object.keys(obj).forEach(function (k) {
2011-12-11 08:58:34 +00:00
if (k !== 'id' && !obj.constructor.schema.definitions[obj.constructor.modelName].properties[k]) {
delete obj[k];
}
2011-11-28 16:31:01 +00:00
if (obj.propertyChanged(k)) {
obj[k] = obj[k + '_was'];
}
});
};
ModelBaseClass.prototype.inspect = function () {
2012-10-15 23:15:29 +00:00
return util.inspect(this.__data, false, 4, true);
};
2012-03-27 14:22:24 +00:00
/**
* Check whether `s` is not undefined
* @param {Mixed} s
* @return {Boolean} s is undefined
*/
2011-10-08 17:11:26 +00:00
function isdef(s) {
var undef;
return s !== undef;
}
2013-05-28 20:50:59 +00:00
ModelBaseClass.mixin = function(anotherClass, options) {
return jutil.mixin(this, anotherClass, options);
}
2013-05-28 05:20:30 +00:00
jutil.mixin(ModelBaseClass, Hookable);
2013-07-01 23:49:43 +00:00
jutil.mixin(ModelBaseClass, validations.Validatable);