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

315 lines
8.1 KiB
JavaScript
Raw Normal View History

2016-04-01 22:25:16 +00:00
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
// Node module: loopback-datasource-juggler
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
2016-08-22 19:55:22 +00:00
'use strict';
2016-04-01 22:25:16 +00:00
2018-12-07 14:54:29 +00:00
const assert = require('assert');
const util = require('util');
const EventEmitter = require('events').EventEmitter;
const traverse = require('traverse');
const ModelBaseClass = require('./model');
const 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) {
2014-01-24 17:09:53 +00:00
if (!(this instanceof ModelDefinition)) {
// Allow to call ModelDefinition without new
return new ModelDefinition(modelBuilder, name, properties, settings);
}
this.modelBuilder = modelBuilder || ModelBuilder.defaultInstance;
assert(name, 'name is missing');
2013-08-24 16:38:12 +00:00
2014-01-24 17:09:53 +00:00
if (arguments.length === 2 && typeof name === 'object') {
2018-12-07 14:54:29 +00:00
const schema = name;
2014-01-24 17:09:53 +00:00
this.name = schema.name;
this.rawProperties = schema.properties || {}; // Keep the raw property definitions
this.settings = schema.settings || {};
} else {
assert(typeof name === 'string', 'name must be a string');
this.name = name;
this.rawProperties = properties || {}; // Keep the raw property definitions
this.settings = settings || {};
}
this.relations = [];
this.properties = null;
this.build();
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'
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.tableName = function(connectorType) {
2018-12-07 14:54:29 +00:00
const settings = this.settings;
2014-01-24 17:09:53 +00:00
if (settings[connectorType]) {
return settings[connectorType].table || settings[connectorType].tableName || this.name;
} else {
return this.name;
}
2013-08-24 16:38:12 +00:00
};
/**
* 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
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.columnName = function(connectorType, propertyName) {
2014-01-24 17:09:53 +00:00
if (!propertyName) {
return propertyName;
}
this.build();
2018-12-07 14:54:29 +00:00
const property = this.properties[propertyName];
2014-01-24 17:09:53 +00:00
if (property && property[connectorType]) {
return property[connectorType].column || property[connectorType].columnName || propertyName;
} else {
return propertyName;
}
2013-08-24 16:38:12 +00:00
};
/**
* 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
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.columnMetadata = function(connectorType, propertyName) {
2014-01-24 17:09:53 +00:00
if (!propertyName) {
return propertyName;
}
this.build();
2018-12-07 14:54:29 +00:00
const property = this.properties[propertyName];
2014-01-24 17:09:53 +00:00
if (property && property[connectorType]) {
return property[connectorType];
} else {
return null;
}
2013-08-24 16:38:12 +00:00
};
/**
* Return column names for specified modelName
* @param {String} connectorType The connector type, such as 'oracle' or 'mongodb'
* @returns {String[]} column names
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.columnNames = function(connectorType) {
2014-01-24 17:09:53 +00:00
this.build();
2018-12-07 14:54:29 +00:00
const props = this.properties;
const cols = [];
for (const p in props) {
2014-01-24 17:09:53 +00:00
if (props[p][connectorType]) {
cols.push(props[p][connectorType].column || props[p][connectorType].columnName || p);
2014-01-24 17:09:53 +00:00
} else {
cols.push(p);
2013-08-24 16:38:12 +00:00
}
2014-01-24 17:09:53 +00:00
}
return cols;
2013-08-24 16:38:12 +00:00
};
/**
* Find the ID properties sorted by the index
* @returns {Object[]} property name/index for IDs
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.ids = function() {
2014-01-24 17:09:53 +00:00
if (this._ids) {
return this._ids;
}
2018-12-07 14:54:29 +00:00
const ids = [];
2014-01-24 17:09:53 +00:00
this.build();
2018-12-07 14:54:29 +00:00
const props = this.properties;
for (const key in props) {
let id = props[key].id;
2014-01-24 17:09:53 +00:00
if (!id) {
continue;
2013-08-24 16:38:12 +00:00
}
2014-01-24 17:09:53 +00:00
if (typeof id !== 'number') {
id = 1;
2013-08-24 16:38:12 +00:00
}
2016-08-19 17:46:59 +00:00
ids.push({name: key, id: id, property: props[key]});
2014-01-24 17:09:53 +00:00
}
2016-04-01 11:48:17 +00:00
ids.sort(function(a, b) {
return a.id - b.id;
2014-01-24 17:09:53 +00:00
});
this._ids = ids;
return ids;
2013-08-24 16:38:12 +00:00
};
/**
* Find the ID column name
* @param {String} modelName The model name
* @returns {String} columnName for ID
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.idColumnName = function(connectorType) {
2014-01-24 17:09:53 +00:00
return this.columnName(connectorType, this.idName());
2013-08-24 16:38:12 +00:00
};
/**
* Find the ID property name
* @returns {String} property name for ID
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.idName = function() {
2018-12-07 14:54:29 +00:00
const id = this.ids()[0];
2014-01-24 17:09:53 +00:00
if (this.properties.id && this.properties.id.id) {
return 'id';
} else {
return id && id.name;
2014-01-24 17:09:53 +00:00
}
2013-08-24 16:38:12 +00:00
};
/**
* Find the ID property names sorted by the index
* @returns {String[]} property names for IDs
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.idNames = function() {
2018-12-07 14:54:29 +00:00
const ids = this.ids();
const names = ids.map(function(id) {
2014-01-24 17:09:53 +00:00
return id.name;
});
return names;
2013-08-24 16:38:12 +00:00
};
/**
*
* @returns {{}}
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.indexes = function() {
2014-01-24 17:09:53 +00:00
this.build();
2018-12-07 14:54:29 +00:00
const indexes = {};
2014-01-24 17:09:53 +00:00
if (this.settings.indexes) {
2018-12-07 14:54:29 +00:00
for (const i in this.settings.indexes) {
2014-01-24 17:09:53 +00:00
indexes[i] = this.settings.indexes[i];
}
2014-01-24 17:09:53 +00:00
}
2018-12-07 14:54:29 +00:00
for (const p in this.properties) {
2014-01-24 17:09:53 +00:00
if (this.properties[p].index) {
indexes[p + '_index'] = this.properties[p].index;
}
2014-01-24 17:09:53 +00:00
}
return indexes;
};
/**
* Build a model definition
* @param {Boolean} force Forcing rebuild
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.build = function(forceRebuild) {
2014-01-24 17:09:53 +00:00
if (forceRebuild) {
this.properties = null;
this.relations = [];
this._ids = null;
this.json = null;
2014-01-24 17:09:53 +00:00
}
if (this.properties) {
return this.properties;
}
this.properties = {};
2018-12-07 14:54:29 +00:00
for (const p in this.rawProperties) {
const prop = this.rawProperties[p];
const type = this.modelBuilder.resolveType(prop);
2014-01-24 17:09:53 +00:00
if (typeof type === 'string') {
this.relations.push({
source: this.name,
target: type,
type: Array.isArray(prop) ? 'hasMany' : 'belongsTo',
2016-04-01 11:48:17 +00:00
as: p,
2014-01-24 17:09:53 +00:00
});
} else {
2018-12-07 14:54:29 +00:00
const typeDef = {
2016-04-01 11:48:17 +00:00
type: type,
2014-01-24 17:09:53 +00:00
};
if (typeof prop === 'object' && prop !== null) {
2018-12-07 14:54:29 +00:00
for (const a in prop) {
2014-01-24 17:09:53 +00:00
// 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] = prop[a];
}
}
2014-01-24 17:09:53 +00:00
}
this.properties[p] = typeDef;
}
2014-01-24 17:09:53 +00:00
}
return this.properties;
};
/**
* Define a property
* @param {String} propertyName The property name
* @param {Object} propertyDefinition The property definition
*/
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.defineProperty = function(propertyName, propertyDefinition) {
2014-01-24 17:09:53 +00:00
this.rawProperties[propertyName] = propertyDefinition;
this.build(true);
};
function isModelClass(cls) {
2014-01-24 17:09:53 +00:00
if (!cls) {
return false;
}
return cls.prototype instanceof ModelBaseClass;
}
2016-04-01 11:48:17 +00:00
ModelDefinition.prototype.toJSON = function(forceRebuild) {
2014-01-24 17:09:53 +00:00
if (forceRebuild) {
this.json = null;
}
if (this.json) {
2014-08-17 16:01:52 +00:00
return this.json;
2014-01-24 17:09:53 +00:00
}
2018-12-07 14:54:29 +00:00
const json = {
2014-01-24 17:09:53 +00:00
name: this.name,
properties: {},
2016-04-01 11:48:17 +00:00
settings: this.settings,
2014-01-24 17:09:53 +00:00
};
this.build(forceRebuild);
2018-12-07 14:54:29 +00:00
const mapper = function(val) {
2014-01-24 17:09:53 +00:00
if (val === undefined || val === null) {
return val;
}
2014-01-24 17:09:53 +00:00
if ('function' === typeof val.toJSON) {
// The value has its own toJSON() object
return val.toJSON();
}
2014-01-24 17:09:53 +00:00
if ('function' === typeof val) {
if (isModelClass(val)) {
if (val.settings && val.settings.anonymous) {
return val.definition && val.definition.toJSON().properties;
} else {
2014-01-24 17:09:53 +00:00
return val.modelName;
}
2014-01-24 17:09:53 +00:00
}
return val.name;
} else {
return val;
}
2014-01-24 17:09:53 +00:00
};
2018-12-07 14:54:29 +00:00
for (const p in this.properties) {
2014-01-24 17:09:53 +00:00
json.properties[p] = traverse(this.properties[p]).map(mapper);
}
this.json = json;
return json;
};
2015-08-26 22:23:35 +00:00
ModelDefinition.prototype.hasPK = function() {
return this.ids().length > 0;
};