Skip to content

Commit

Permalink
feat: remove all references to a Model
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bajtos committed Apr 17, 2018
1 parent 77a3523 commit 0cd380c
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 3 deletions.
38 changes: 38 additions & 0 deletions lib/application.js
Expand Up @@ -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()`
*
Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -49,15 +49,15 @@
"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",
"nodemailer-stub-transport": "^1.0.0",
"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"
},
Expand Down
63 changes: 62 additions & 1 deletion test/app.test.js
Expand Up @@ -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() {
Expand Down Expand Up @@ -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'});
Expand Down
12 changes: 12 additions & 0 deletions test/rest.middleware.test.js
Expand Up @@ -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'});
Expand Down

0 comments on commit 0cd380c

Please sign in to comment.