Create model foreign key matching type of opposite part of relation (even if it has a custom field type)

This commit is contained in:
Andrey Loukhnov 2015-02-03 13:13:00 +03:00
parent ac547433b1
commit e68ecb461a
1 changed files with 44 additions and 39 deletions

View File

@ -188,8 +188,8 @@ function connectorModuleNames(name) {
} }
} }
// Only try the short name if the connector is not from StrongLoop // Only try the short name if the connector is not from StrongLoop
if(['mongodb', 'oracle', 'mysql', 'postgresql', 'mssql', 'rest', 'soap'] if (['mongodb', 'oracle', 'mysql', 'postgresql', 'mssql', 'rest', 'soap']
.indexOf(name) === -1) { .indexOf(name) === -1) {
names.push(name); names.push(name);
} }
return names; return names;
@ -225,7 +225,7 @@ DataSource._resolveConnector = function (name, loader) {
if (!connector) { if (!connector) {
error = util.format('\nWARNING: LoopBack connector "%s" is not installed ' + error = util.format('\nWARNING: LoopBack connector "%s" is not installed ' +
'as any of the following modules:\n\n %s\n\nTo fix, run:\n\n npm install %s\n', 'as any of the following modules:\n\n %s\n\nTo fix, run:\n\n npm install %s\n',
name, names.join('\n'), names[names.length -1]); name, names.join('\n'), names[names.length - 1]);
} }
return { return {
connector: connector, connector: connector,
@ -265,7 +265,7 @@ DataSource.prototype.setup = function (name, settings) {
this.settings.debug = this.settings.debug || debug.enabled; this.settings.debug = this.settings.debug || debug.enabled;
if(this.settings.debug) { if (this.settings.debug) {
debug('Settings: %j', this.settings); debug('Settings: %j', this.settings);
} }
@ -379,8 +379,8 @@ function isModelDataSourceAttached(model) {
* @param scopes * @param scopes
*/ */
DataSource.prototype.defineScopes = function (modelClass, scopes) { DataSource.prototype.defineScopes = function (modelClass, scopes) {
if(scopes) { if (scopes) {
for(var s in scopes) { for (var s in scopes) {
defineScope(modelClass, modelClass, s, scopes[s], {}, scopes[s].options); defineScope(modelClass, modelClass, s, scopes[s], {}, scopes[s].options);
} }
} }
@ -429,15 +429,15 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
// Set up the relations // Set up the relations
if (relations) { if (relations) {
Object.keys(relations).forEach(function(rn) { Object.keys(relations).forEach(function (rn) {
var r = relations[rn]; var r = relations[rn];
assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type); assert(DataSource.relationTypes.indexOf(r.type) !== -1, "Invalid relation type: " + r.type);
var targetModel, polymorphicName; var targetModel, polymorphicName;
if (r.polymorphic && r.type !== 'belongsTo' && !r.model) { if (r.polymorphic && r.type !== 'belongsTo' && !r.model) {
throw new Error('No model specified for polymorphic ' + r.type + ': ' + rn); throw new Error('No model specified for polymorphic ' + r.type + ': ' + rn);
} }
if (r.polymorphic) { if (r.polymorphic) {
polymorphicName = typeof r.model === 'string' ? r.model : rn; polymorphicName = typeof r.model === 'string' ? r.model : rn;
if (typeof r.polymorphic === 'string') { if (typeof r.polymorphic === 'string') {
@ -446,17 +446,17 @@ DataSource.prototype.defineRelations = function (modelClass, relations) {
polymorphicName = r.polymorphic.as; polymorphicName = r.polymorphic.as;
} }
} }
if (r.model) { if (r.model) {
targetModel = isModelClass(r.model) ? r.model : self.getModel(r.model, true); targetModel = isModelClass(r.model) ? r.model : self.getModel(r.model, true);
} }
var throughModel = null; var throughModel = null;
if (r.through) { if (r.through) {
throughModel = isModelClass(r.through) ? r.through : self.getModel(r.through, true); throughModel = isModelClass(r.through) ? r.through : self.getModel(r.through, true);
} }
if ((targetModel && !isModelDataSourceAttached(targetModel)) if ((targetModel && !isModelDataSourceAttached(targetModel))
|| (throughModel && !isModelDataSourceAttached(throughModel))) { || (throughModel && !isModelDataSourceAttached(throughModel))) {
// Create a listener to defer the relation set up // Create a listener to defer the relation set up
createListener(rn, r, targetModel, throughModel); createListener(rn, r, targetModel, throughModel);
@ -484,13 +484,13 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) {
// Check if the id property should be generated // Check if the id property should be generated
var idName = modelClass.definition.idName(); var idName = modelClass.definition.idName();
var idProp = modelClass.definition.rawProperties[idName]; var idProp = modelClass.definition.rawProperties[idName];
if(idProp && idProp.generated && this.connector.getDefaultIdType) { if (idProp && idProp.generated && this.connector.getDefaultIdType) {
// Set the default id type from connector's ability // Set the default id type from connector's ability
var idType = this.connector.getDefaultIdType() || String; var idType = this.connector.getDefaultIdType() || String;
idProp.type = idType; idProp.type = idType;
modelClass.definition.properties[idName].type = idType; modelClass.definition.properties[idName].type = idType;
if (settings.forceId) { if (settings.forceId) {
modelClass.validatesAbsenceOf(idName, { if: 'isNewRecord' }); modelClass.validatesAbsenceOf(idName, {if: 'isNewRecord'});
} }
} }
if (this.connector.define) { if (this.connector.define) {
@ -525,7 +525,7 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) {
* The first (String) argument specifying the model name is required. * The first (String) argument specifying the model name is required.
* You can provide one or two JSON object arguments, to provide configuration options. * You can provide one or two JSON object arguments, to provide configuration options.
* See [Model definition reference](http://docs.strongloop.com/display/DOC/Model+definition+reference) for details. * See [Model definition reference](http://docs.strongloop.com/display/DOC/Model+definition+reference) for details.
* *
* Simple example: * Simple example:
* ``` * ```
* var User = dataSource.createModel('User', { * var User = dataSource.createModel('User', {
@ -546,7 +546,7 @@ DataSource.prototype.setupDataAccess = function (modelClass, settings) {
* }); * });
* ``` * ```
* You can also define an ACL when you create a new data source with the `DataSource.create()` method. For example: * You can also define an ACL when you create a new data source with the `DataSource.create()` method. For example:
* *
* ```js * ```js
* var Customer = ds.createModel('Customer', { * var Customer = ds.createModel('Customer', {
* name: { * name: {
@ -749,9 +749,9 @@ DataSource.prototype.defineProperty = function (model, prop, params) {
/** /**
* Drop each model table and re-create. * Drop each model table and re-create.
* This method applies only to database connectors. For MongoDB, it drops and creates indexes. * This method applies only to database connectors. For MongoDB, it drops and creates indexes.
* *
* **WARNING**: Calling this function deletes all data! Use `autoupdate()` to preserve data. * **WARNING**: Calling this function deletes all data! Use `autoupdate()` to preserve data.
* *
* @param {String} model Model to migrate. If not present, apply to all models. Can also be an array of Strings. * @param {String} model Model to migrate. If not present, apply to all models. Can also be an array of Strings.
* @param {Function} [callback] Callback function. Optional. * @param {Function} [callback] Callback function. Optional.
* *
@ -784,15 +784,15 @@ DataSource.prototype.automigrate = function (models, cb) {
return cb && process.nextTick(cb); return cb && process.nextTick(cb);
} }
var invalidModels = models.filter(function(m) { var invalidModels = models.filter(function (m) {
return !(m in attachedModels); return !(m in attachedModels);
}); });
if (invalidModels.length) { if (invalidModels.length) {
return process.nextTick(function() { return process.nextTick(function () {
if (cb) { if (cb) {
cb(new Error('Cannot migrate models not attached to this datasource: ' + cb(new Error('Cannot migrate models not attached to this datasource: ' +
invalidModels.join(' '))); invalidModels.join(' ')));
} }
}); });
} }
@ -808,7 +808,7 @@ DataSource.prototype.automigrate = function (models, cb) {
* @param {String} model Model to migrate. If not present, apply to all models. Can also be an array of Strings. * @param {String} model Model to migrate. If not present, apply to all models. Can also be an array of Strings.
* @param {Function} [cb] The callback function * @param {Function} [cb] The callback function
*/ */
DataSource.prototype.autoupdate = function(models, cb) { DataSource.prototype.autoupdate = function (models, cb) {
this.freeze(); this.freeze();
if ((!cb) && ('function' === typeof models)) { if ((!cb) && ('function' === typeof models)) {
@ -835,15 +835,15 @@ DataSource.prototype.autoupdate = function(models, cb) {
return process.nextTick(cb); return process.nextTick(cb);
} }
var invalidModels = models.filter(function(m) { var invalidModels = models.filter(function (m) {
return !(m in attachedModels); return !(m in attachedModels);
}); });
if (invalidModels.length) { if (invalidModels.length) {
return process.nextTick(function() { return process.nextTick(function () {
if (cb) { if (cb) {
cb(new Error('Cannot migrate models not attached to this datasource: ' + cb(new Error('Cannot migrate models not attached to this datasource: ' +
invalidModels.join(' '))); invalidModels.join(' ')));
} }
}); });
} }
@ -1027,7 +1027,7 @@ DataSource.prototype.discoverForeignKeysSync = function (modelName, options) {
/** /**
* Retrieves a description of the foreign key columns that reference the given table's primary key columns * Retrieves a description of the foreign key columns that reference the given table's primary key columns
* (the foreign keys exported by a table), ordered by fkTableOwner, fkTableName, and keySeq. * (the foreign keys exported by a table), ordered by fkTableOwner, fkTableName, and keySeq.
* *
* Callback function return value is an object that can have the following properties: * Callback function return value is an object that can have the following properties:
* *
*| Key | Type | Description | *| Key | Type | Description |
@ -1092,7 +1092,7 @@ function fromDBName(dbName, camelCase) {
/** /**
* Discover one schema from the given model without following the relations. * Discover one schema from the given model without following the relations.
**Example schema from oracle connector:** **Example schema from oracle connector:**
* *
* ```js * ```js
* { * {
@ -1194,7 +1194,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) {
var tasks = [ var tasks = [
this.discoverModelProperties.bind(this, modelName, options), this.discoverModelProperties.bind(this, modelName, options),
this.discoverPrimaryKeys.bind(this, modelName, options) ]; this.discoverPrimaryKeys.bind(this, modelName, options)];
var followingRelations = options.associations || options.relations; var followingRelations = options.associations || options.relations;
if (followingRelations) { if (followingRelations) {
@ -1230,8 +1230,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) {
options: { options: {
idInjection: false // DO NOT add id property idInjection: false // DO NOT add id property
}, },
properties: { properties: {}
}
}; };
schema.options[schemaName] = { schema.options[schemaName] = {
@ -1246,7 +1245,7 @@ DataSource.prototype.discoverSchemas = function (modelName, options, cb) {
schema.properties[propName] = { schema.properties[propName] = {
type: item.type, type: item.type,
required: (item.nullable === 'N' || item.nullable === 'NO' required: (item.nullable === 'N' || item.nullable === 'NO'
|| item.nullable === 0 || item.nullable === false), || item.nullable === 0 || item.nullable === false),
length: item.dataLength, length: item.dataLength,
precision: item.dataPrecision, precision: item.dataPrecision,
scale: item.dataScale scale: item.dataScale
@ -1373,8 +1372,7 @@ DataSource.prototype.discoverSchemasSync = function (modelName, options) {
options: { options: {
idInjection: false // DO NOT add id property idInjection: false // DO NOT add id property
}, },
properties: { properties: {}
}
}; };
schema.options[schemaName] = { schema.options[schemaName] = {
@ -1600,7 +1598,7 @@ DataSource.prototype.log = function (sql, t) {
* Freeze dataSource. Behavior depends on connector * Freeze dataSource. Behavior depends on connector
*/ */
DataSource.prototype.freeze = function freeze() { DataSource.prototype.freeze = function freeze() {
if(!this.connector) { if (!this.connector) {
throw new Error('The connector has not been initialized.'); throw new Error('The connector has not been initialized.');
} }
if (this.connector.freezeDataSource) { if (this.connector.freezeDataSource) {
@ -1711,11 +1709,18 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key
return; return;
} }
var fkDef = {type: pkType};
var foreignMeta = this.columnMetadata(foreignClassName, key);
if(foreignMeta && foreignMeta.dataType) {
fkDef[this.connector.name] = {};
fkDef[this.connector.name].dataType = foreignMeta.dataType;
}
if (this.connector.defineForeignKey) { if (this.connector.defineForeignKey) {
var cb = function (err, keyType) { var cb = function (err, keyType) {
if (err) throw err; if (err) throw err;
fkDef.type = keyType || pkType;
// Add the foreign key property to the data source _models // Add the foreign key property to the data source _models
this.defineProperty(className, key, {type: keyType || pkType}); this.defineProperty(className, key, fkDef);
}.bind(this); }.bind(this);
switch (this.connector.defineForeignKey.length) { switch (this.connector.defineForeignKey.length) {
case 4: case 4:
@ -1728,7 +1733,7 @@ DataSource.prototype.defineForeignKey = function defineForeignKey(className, key
} }
} else { } else {
// Add the foreign key property to the data source _models // Add the foreign key property to the data source _models
this.defineProperty(className, key, {type: pkType}); this.defineProperty(className, key, fkDef);
} }
}; };
@ -1849,7 +1854,7 @@ DataSource.prototype.enableRemote = function (operation) {
/** /**
* Disable remote access to a data source operation. Each [connector](#connector) has its own set of set enabled * Disable remote access to a data source operation. Each [connector](#connector) has its own set of set enabled
* and disabled operations. To list the operations, call `dataSource.operations()`. * and disabled operations. To list the operations, call `dataSource.operations()`.
* *
*```js *```js
* var oracle = loopback.createDataSource({ * var oracle = loopback.createDataSource({
* connector: require('loopback-connector-oracle'), * connector: require('loopback-connector-oracle'),
@ -1859,7 +1864,7 @@ DataSource.prototype.enableRemote = function (operation) {
* oracle.disableRemote('destroyAll'); * oracle.disableRemote('destroyAll');
* ``` * ```
* **Notes:** * **Notes:**
* *
* - Disabled operations will not be added to attached models. * - Disabled operations will not be added to attached models.
* - Disabling the remoting for a method only affects client access (it will still be available from server models). * - Disabling the remoting for a method only affects client access (it will still be available from server models).
* - Data sources must enable / disable operations before attaching or creating models. * - Data sources must enable / disable operations before attaching or creating models.