Merge pull request #1534 from strongloop/feature/correct-model-ctor-name

Set model constructor name to model name
This commit is contained in:
Miroslav Bajtoš 2018-01-09 09:36:40 +01:00 committed by GitHub
commit 9189dba001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 96 additions and 10 deletions

View File

@ -185,16 +185,8 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
// Create the ModelClass if it doesn't exist or it's resolved (override)
// TODO: [rfeng] We need to decide what names to use for built-in models such as User.
if (!ModelClass || !ModelClass.settings.unresolved) {
// every class can receive hash of data as optional param
ModelClass = function ModelConstructor(data, options) {
if (!(this instanceof ModelConstructor)) {
return new ModelConstructor(data, options);
}
if (ModelClass.settings.unresolved) {
throw new Error(g.f('Model %s is not defined.', ModelClass.modelName));
}
ModelBaseClass.apply(this, arguments);
};
ModelClass = createModelClassCtor(className, ModelBaseClass);
// mix in EventEmitter (don't inherit from)
var events = new EventEmitter();
// The model can have more than 10 listeners for lazy relationship setup
@ -663,6 +655,44 @@ ModelBuilder.prototype.define = function defineClass(className, properties, sett
return ModelClass;
};
function createModelClassCtor(name, ModelBaseClass) {
// A simple sanitization to handle most common characters
// that are used in model names but cannot be used as a function/class name.
// Note that the rules for valid JS indentifiers are way too complex,
// implementing a fully spec-compliant sanitization is not worth the effort.
// See https://mathiasbynens.be/notes/javascript-identifiers-es6
name = name.replace(/[-.:]/g, '_');
try {
// It is not possible to access closure variables like "ModelBaseClass"
// from a dynamically defined function. The solution is to
// create a dynamically defined factory function that accepts
// closure variables as arguments.
const factory = new Function('ModelBaseClass', `
// every class can receive hash of data as optional param
return function ${name}(data, options) {
if (!(this instanceof ${name})) {
return new ${name}(data, options);
}
if (${name}.settings.unresolved) {
throw new Error(g.f('Model %s is not defined.', ${JSON.stringify(name)}));
}
ModelBaseClass.apply(this, arguments);
};`);
return factory(ModelBaseClass);
} catch (err) {
// modelName is not a valid function/class name, e.g. 'grand-child'
// and our simple sanitization was not good enough.
// Falling back to legacy 'ModelConstructor' name.
if (err.name === 'SyntaxError') {
return createModelClassCtor('ModelConstructor', ModelBaseClass);
} else {
throw err;
}
}
}
// DataType for Date
function DateType(arg) {
if (arg === null) return null;

View File

@ -0,0 +1,56 @@
// Copyright IBM Corp. 2018. 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
'use strict';
const should = require('./init.js');
const juggler = require('../');
var ModelBuilder = juggler.ModelBuilder;
describe('ModelBuilder', () => {
describe('define()', () => {
let builder;
beforeEach(givenModelBuilderInstance);
it('sets correct "modelName" property', () => {
const MyModel = builder.define('MyModel');
MyModel.should.have.property('modelName', 'MyModel');
});
it('sets correct "name" property on model constructor', () => {
const MyModel = builder.define('MyModel');
MyModel.should.have.property('name', 'MyModel');
});
describe('model class name sanitization', () => {
it('converts "-" to "_"', () => {
const MyModel = builder.define('Grand-child');
MyModel.should.have.property('name', 'Grand_child');
});
it('converts "." to "_"', () => {
const MyModel = builder.define('Grand.child');
MyModel.should.have.property('name', 'Grand_child');
});
it('converts ":" to "_"', () => {
const MyModel = builder.define('local:User');
MyModel.should.have.property('name', 'local_User');
});
it('falls back to legacy "ModelConstructor" in other cases', () => {
const MyModel = builder.define('Grand\tchild');
MyModel.should.have.property('name', 'ModelConstructor');
});
});
function givenModelBuilderInstance() {
builder = new ModelBuilder();
}
});
});