loopback-datasource-juggler/lib/model-definition.js

356 lines
9.9 KiB
JavaScript
Raw Normal View History

2013-08-24 16:38:12 +00:00
var assert = require('assert');
var util = require('util');
var EventEmitter = require('events').EventEmitter;
var traverse = require('traverse');
var ModelBaseClass = require('./model');
var ModelBuilder = require('./model-builder');
2013-08-24 16:38:12 +00:00
/**
* Model definition
*/
module.exports = ModelDefinition;
/**
* Constructor for ModelDefinition
* @param {ModelBuilder} modelBuilder A model builder instance
* @param {String|Object} name The model name or the schema object
* @param {Object} properties The model properties, optional
* @param {Object} settings The model settings, optional
2013-08-24 16:38:12 +00:00
* @returns {ModelDefinition}
* @constructor
*
*/
function ModelDefinition(modelBuilder, name, properties, settings) {
2013-08-24 16:38:12 +00:00
if (!(this instanceof ModelDefinition)) {
// Allow to call ModelDefinition without new
return new ModelDefinition(modelBuilder, name, properties, settings);
2013-08-24 16:38:12 +00:00
}
this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
2013-08-24 16:38:12 +00:00
assert(name, 'name is missing');
if (arguments.length === 2 && typeof name === 'object') {
var schema = name;
this.name = schema.name;
this.rawProperties = schema.properties || {}; // Keep the raw property definitions
this.settings = schema.settings || {};
2013-08-24 16:38:12 +00:00
} else {
assert(typeof name === 'string', 'name must be a string');
this.name = name;
this.rawProperties = properties || {}; // Keep the raw property definitions
2013-08-24 16:38:12 +00:00
this.settings = settings || {};
}
this.associations = [];
this.properties = null;
2013-08-24 16:38:12 +00:00
}
util.inherits(ModelDefinition, EventEmitter);
// Set up types
require('./types')(ModelDefinition);
2013-08-24 16:38:12 +00:00
/**
* Return table name for specified `modelName`
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
*/
ModelDefinition.prototype.tableName = function (connectorType) {
var settings = this.settings;
if(settings[connectorType]) {
return settings[connectorType].table || this.name;
} else {
return this.name;
}
};
/**
* Return column name for specified modelName and propertyName
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
* @param propertyName The property name
* @returns {String} columnName
*/
ModelDefinition.prototype.columnName = function (connectorType, propertyName) {
if(!propertyName) {
return propertyName;
}
this.build();
2013-08-24 16:38:12 +00:00
var property = this.properties[propertyName];
if(property && property[connectorType]) {
return property[connectorType].columnName || propertyName;
} else {
return propertyName;
}
};
/**
* Return column metadata for specified modelName and propertyName
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
* @param propertyName The property name
* @returns {Object} column metadata
*/
ModelDefinition.prototype.columnMetadata = function (connectorType, propertyName) {
if(!propertyName) {
return propertyName;
}
this.build();
2013-08-24 16:38:12 +00:00
var property = this.properties[propertyName];
if(property && property[connectorType]) {
return property[connectorType];
} else {
return null;
}
};
/**
* Return column names for specified modelName
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
* @returns {String[]} column names
*/
ModelDefinition.prototype.columnNames = function (connectorType) {
this.build();
2013-08-24 16:38:12 +00:00
var props = this.properties;
var cols = [];
for(var p in props) {
if(props[p][connectorType]) {
cols.push(props[p][connectorType].columnName || p);
} else {
cols.push(p);
}
}
return cols;
};
/**
* Find the ID properties sorted by the index
* @returns {Object[]} property name/index for IDs
*/
ModelDefinition.prototype.ids = function () {
if(this._ids) {
return this._ids;
2013-08-24 16:38:12 +00:00
}
var ids = [];
this.build();
2013-08-24 16:38:12 +00:00
var props = this.properties;
for (var key in props) {
var id = props[key].id;
if(!id) {
continue;
}
if(typeof id !== 'number') {
id = 1;
}
ids.push({name: key, id: id});
}
ids.sort(function (a, b) {
return a.key - b.key;
});
this._ids = ids;
2013-08-24 16:38:12 +00:00
return ids;
};
/**
* Find the ID column name
* @param {String} modelName The model name
* @returns {String} columnName for ID
*/
ModelDefinition.prototype.idColumnName = function(connectorType) {
return this.columnName(connectorType, this.idName());
};
/**
* Find the ID property name
* @returns {String} property name for ID
*/
ModelDefinition.prototype.idName = function() {
var id = this.ids()[0];
2013-10-03 00:20:54 +00:00
if(this.properties.id && this.properties.id.id) {
return 'id';
}
2013-08-24 16:38:12 +00:00
return id && id.name;
};
/**
* Find the ID property names sorted by the index
* @returns {String[]} property names for IDs
*/
ModelDefinition.prototype.idNames = function () {
var ids = this.ids();
2013-08-24 16:38:12 +00:00
var names = ids.map(function (id) {
return id.name;
});
return names;
};
/**
*
* @returns {{}}
*/
ModelDefinition.prototype.indexes = function () {
this.build();
var indexes = {};
if (this.settings.indexes) {
for (var i in this.settings.indexes) {
indexes[i] = this.settings.indexes[i];
}
}
for (var p in this.properties) {
if (this.properties[p].index) {
indexes[p + '_index'] = this.properties[p].index;
}
}
return indexes;
};
/**
* Resolve the type string to be a function, for example, 'String' to String
* @param {String} type The type string, such as 'number', 'Number', 'boolean', or 'String'. It's case insensitive
* @returns {Function} if the type is resolved
*/
ModelDefinition.prototype.resolveType = function(type) {
if (!type) {
return type;
}
if (Array.isArray(type) && type.length > 0) {
// For array types, the first item should be the type string
var itemType = this.resolveType(type[0]);
if (typeof itemType === 'function') {
return [itemType];
}
else {
return itemType; // Not resolved, return the type string
}
}
if (typeof type === 'string') {
var schemaType = ModelDefinition.schemaTypes[type.toLowerCase()];
if (schemaType) {
return schemaType;
} else {
return type;
}
} else if (type.constructor.name === 'Object') {
// We also support the syntax {type: 'string', ...}
if (type.type) {
return this.resolveType(type.type);
} else {
if(!this.anonymousTypesCount) {
this.anonymousTypesCount = 0;
}
this.anonymousTypesCount++;
return this.modelBuilder.define('AnonymousType' + this.anonymousTypesCount,
type, {anonymous: true, idInjection: false});
/*
console.error(type);
throw new Error('Missing type property');
*/
}
} else if('function' === typeof type ) {
return type;
}
return type;
};
/**
* Build a model definition
* @param {Boolean} force Forcing rebuild
*/
ModelDefinition.prototype.build = function (forceRebuild) {
if(forceRebuild) {
this.properties = null;
this.associations = [];
}
if (this.properties) {
return this.properties;
}
this.properties = {};
for (var p in this.rawProperties) {
var type = this.resolveType(this.rawProperties[p]);
if (typeof type === 'string') {
if (Array.isArray(this.associations)) {
this.associations.push({
source: this.name,
target: type,
relation: Array.isArray(this.rawProperties[p]) ? 'hasMany' : 'belongsTo',
as: p
});
// delete this.rawProperties[p];
}
} else {
var typeDef = {
type: type
};
for (var a in this.rawProperties[p]) {
// Skip the type property but don't delete it Model.extend() shares same instances of the properties from the base class
if (a !== 'type') {
typeDef[a] = this.rawProperties[p][a];
}
}
this.properties[p] = typeDef;
}
}
return this.properties;
};
/**
* Define a property
* @param {String} propertyName The property name
* @param {Object} propertyDefinition The property definition
*/
ModelDefinition.prototype.defineProperty = function (propertyName, propertyDefinition) {
this.rawProperties[propertyName] = propertyDefinition;
this.build(true);
};
function isModelClass(cls) {
while (true) {
if (!cls) {
return false;
}
if (ModelBaseClass === cls) {
return true;
}
cls = cls.prototype;
}
}
ModelDefinition.prototype.toJSON = function(forceRebuild) {
if(forceRebuild) {
this.json = null;
}
if(this.json) {
return json;
}
var json = {
name: this.name,
properties: {},
settings: this.settings
};
this.build(forceRebuild);
var mapper = function(val) {
if(val === undefined || val === null) {
return val;
}
if('function' === typeof val.toJSON) {
// The value has its own toJSON() object
return val.toJSON();
}
if('function' === typeof val) {
if(isModelClass(val)) {
if(val.settings && val.settings.anonymous) {
return val.definition && val.definition.toJSON();
} else {
return val.modelName;
}
}
return val.name;
} else {
return val;
}
};
for(var p in this.properties) {
json.properties[p] = traverse(this.properties[p]).map(mapper);
}
this.json = json;
return json;
};