feat: remove all references to a Model
Add API allowing applications to hide a Model from the REST API and remove all references to it, allowing Garbage Collector to claim all memory used by the model.
This commit is contained in:
parent
77a35231dc
commit
0cd380c590
|
@ -163,6 +163,44 @@ app.model = function(Model, config) {
|
|||
return Model;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove all references to a previously registered Model.
|
||||
*
|
||||
* The method emits "modelDeleted" event as a counter-part to "modelRemoted"
|
||||
* event.
|
||||
*
|
||||
* @param {String} modelName The name of the model to remove.
|
||||
*/
|
||||
app.deleteModelByName = function(modelName) {
|
||||
const ModelCtor = this.models[modelName];
|
||||
delete this.models[modelName];
|
||||
delete this.models[classify(modelName)];
|
||||
delete this.models[camelize(modelName)];
|
||||
|
||||
if (ModelCtor) {
|
||||
ModelCtor.removeAllListeners();
|
||||
|
||||
const ix = this._models.indexOf(ModelCtor);
|
||||
if (ix > -1) {
|
||||
this._models.splice(ix, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const remotes = this.remotes();
|
||||
remotes.deleteClassByName(modelName);
|
||||
remotes.deleteTypeByName(modelName);
|
||||
|
||||
if (ModelCtor && ModelCtor.dataSource) {
|
||||
ModelCtor.dataSource.deleteModelByName(modelName);
|
||||
} else {
|
||||
this.registry.modelBuilder.deleteModelByName(modelName);
|
||||
}
|
||||
|
||||
clearHandlerCache(this);
|
||||
|
||||
this.emit('modelDeleted', ModelCtor || modelName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the models exported by the app. Returns only models defined using `app.model()`
|
||||
*
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
"inflection": "^1.6.0",
|
||||
"isemail": "^2.2.1",
|
||||
"loopback-connector-remote": "^3.0.0",
|
||||
"loopback-datasource-juggler": "^3.15.0",
|
||||
"loopback-datasource-juggler": "^3.18.0",
|
||||
"loopback-filters": "^1.0.0",
|
||||
"loopback-phase": "^3.0.0",
|
||||
"nodemailer": "^2.5.0",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"serve-favicon": "^2.2.0",
|
||||
"stable": "^0.1.5",
|
||||
"strong-globalize": "^3.1.0",
|
||||
"strong-remoting": "^3.0.0",
|
||||
"strong-remoting": "^3.11.0",
|
||||
"uid2": "0.0.3",
|
||||
"underscore.string": "^3.0.3"
|
||||
},
|
||||
|
|
|
@ -17,11 +17,12 @@ var describe = require('./util/describe');
|
|||
var expect = require('./helpers/expect');
|
||||
var it = require('./util/it');
|
||||
var request = require('supertest');
|
||||
const sinon = require('sinon');
|
||||
|
||||
describe('app', function() {
|
||||
var app;
|
||||
beforeEach(function() {
|
||||
app = loopback();
|
||||
app = loopback({localRegistry: true, loadBuiltinModels: true});
|
||||
});
|
||||
|
||||
describe.onServer('.middleware(phase, handler)', function() {
|
||||
|
@ -763,6 +764,66 @@ describe('app', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('app.deleteModelByName()', () => {
|
||||
let TestModel;
|
||||
beforeEach(setupTestModel);
|
||||
|
||||
it('removes the model from app registries', () => {
|
||||
expect(Object.keys(app.models))
|
||||
.to.contain('test-model')
|
||||
.and.contain('TestModel')
|
||||
.and.contain('testModel');
|
||||
expect(app.models().map(m => m.modelName))
|
||||
.to.contain('test-model');
|
||||
|
||||
app.deleteModelByName('test-model');
|
||||
|
||||
expect(Object.keys(app.models))
|
||||
.to.not.contain('test-model')
|
||||
.and.not.contain('TestModel')
|
||||
.and.not.contain('testModel');
|
||||
expect(app.models().map(m => m.modelName))
|
||||
.to.not.contain('test-model');
|
||||
});
|
||||
|
||||
it('removes the model from juggler registries', () => {
|
||||
expect(Object.keys(app.registry.modelBuilder.models))
|
||||
.to.contain('test-model');
|
||||
|
||||
app.deleteModelByName('test-model');
|
||||
|
||||
expect(Object.keys(app.registry.modelBuilder.models))
|
||||
.to.not.contain('test-model');
|
||||
});
|
||||
|
||||
it('removes the model from remoting registries', () => {
|
||||
expect(Object.keys(app.remotes()._classes))
|
||||
.to.contain('test-model');
|
||||
|
||||
app.deleteModelByName('test-model');
|
||||
|
||||
expect(Object.keys(app.remotes()._classes))
|
||||
.to.not.contain('test-model');
|
||||
});
|
||||
|
||||
it('emits "modelDeleted" event', () => {
|
||||
const spy = sinon.spy();
|
||||
app.on('modelDeleted', spy);
|
||||
|
||||
app.deleteModelByName('test-model');
|
||||
|
||||
sinon.assert.calledWith(spy, TestModel);
|
||||
});
|
||||
|
||||
function setupTestModel() {
|
||||
TestModel = app.registry.createModel({
|
||||
name: 'test-model',
|
||||
base: 'Model',
|
||||
});
|
||||
app.model(TestModel, {dataSource: null});
|
||||
}
|
||||
});
|
||||
|
||||
describe('app.models', function() {
|
||||
it('is unique per app instance', function() {
|
||||
app.dataSource('db', {connector: 'memory'});
|
||||
|
|
|
@ -200,6 +200,18 @@ describe('loopback.rest', function() {
|
|||
}, done);
|
||||
});
|
||||
|
||||
it('rebuilds REST endpoints after a model was deleted', () => {
|
||||
app.model(MyModel);
|
||||
app.use(loopback.rest());
|
||||
|
||||
return request(app).get('/mymodels').expect(200)
|
||||
.then(() => {
|
||||
app.deleteModelByName('MyModel');
|
||||
|
||||
return request(app).get('/mymodels').expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
function givenUserModelWithAuth() {
|
||||
var AccessToken = app.registry.getModel('AccessToken');
|
||||
app.model(AccessToken, {dataSource: 'db'});
|
||||
|
|
Loading…
Reference in New Issue