Skip to content

Commit

Permalink
Refactor for compatibility with model package
Browse files Browse the repository at this point in the history
  • Loading branch information
Nataniel López committed Jan 13, 2020
1 parent 0c705ad commit 616d7a0
Show file tree
Hide file tree
Showing 5 changed files with 441 additions and 627 deletions.
241 changes: 82 additions & 159 deletions lib/mongodb-index-creator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

const path = require('path');

const DatabaseDispatcher = require('@janiscommerce/database-dispatcher');

const MongodbIndexCreatorError = require('./mongodb-index-creator-error');
const ModelClient = require('./model-client');

const { isObject, areEqualObjects } = require('./utils/utils');
const { isObject, ModelGenerator } = require('./utils/utils');

const logger = require('./utils/colorful-lllog')();

const DEFAULT_SCHEMAS_PATH = path.join(process.cwd(), 'schemas', 'mongo');
Expand Down Expand Up @@ -43,58 +42,21 @@ class MongodbIndexCreator {
}
}

_saveResults(database, results) {
_saveResults(type, result) {

if(!this.results[database])
this.results[database] = {};
if(!this.results)
this.results = {};

this.results[database] = {
...this.results[database],
...results
};
if(this.results[type])
this.results[type] += result;
else
this.results[type] = result;
}

_showResults() {
logger.info(`Changes summary:\n${JSON.stringify(this.results, null, 2)}`);
}

_getMongoDbInstanceByKey(databaseKey) {

const mongodbInstance = DatabaseDispatcher.getDatabaseByKey(databaseKey);

if(mongodbInstance.constructor.name !== 'MongoDB') {
throw new MongodbIndexCreatorError(`Invalid database type for databaseKey '${databaseKey}': Should be MongoDB.`,
MongodbIndexCreatorError.codes.INVALID_DATABASE_TYPE);
}

return mongodbInstance;
}

_getMongoDbInstanceByClient(client, useReadDB) {

const mongodbInstance = DatabaseDispatcher.getDatabaseByClient(client, useReadDB);

if(mongodbInstance.constructor.name !== 'MongoDB') {
throw new MongodbIndexCreatorError('Invalid database type for client: Should be MongoDB.',
MongodbIndexCreatorError.codes.INVALID_DATABASE_TYPE);
}

return mongodbInstance;
}

async _getMongoDbConnection(mongodbInstance) {

try {

await mongodbInstance.checkConnection();

} catch(err) {
throw new MongodbIndexCreatorError(`Unable to connect to MongoDB: ${err.message}.`, MongodbIndexCreatorError.codes.MONGODB_CONNECTION_FAILED);
}

return mongodbInstance;
}

_validateCollections(collections) {

if(!isObject(collections)) {
Expand Down Expand Up @@ -129,150 +91,117 @@ class MongodbIndexCreator {
});
}

_prepareCoreIndexes() {

if(!isObject(this.coreSchemas)) {
throw new MongodbIndexCreatorError('Invalid core schemas: Should exist and must be an object.',
MongodbIndexCreatorError.codes.INVALID_CORE_SCHEMAS);
}
async _getCurrentIndexes(modelInstance) {

return Object.entries(this.coreSchemas).map(([databaseKey, collections]) => {
const indexes = await modelInstance.getIndexes();

this._validateCollections(collections);

return {
mongodbInstance: this._getMongoDbInstanceByKey(databaseKey),
collections
};
});
return indexes.filter(({ name }) => name !== DEFAULT_ID_INDEX_NAME);
}

async _prepareClientIndexes(clients) {

if(!isObject(this.clientSchemas)) {
throw new MongodbIndexCreatorError('Invalid client schemas: Should exist and must be an object.',
MongodbIndexCreatorError.codes.INVALID_CLIENT_SCHEMAS);
}

this._validateCollections(this.clientSchemas);

return clients.map(client => {

const mongodbInstances = {
write: this._getMongoDbInstanceByClient(client)
};

try {

mongodbInstances.read = this._getMongoDbInstanceByClient(client, true);

} catch(err) {
// Should not throw when the read database config not exists
}

if(mongodbInstances.read && areEqualObjects(mongodbInstances.write.config, mongodbInstances.read.config))
delete mongodbInstances.read;

return {
mongodbInstances,
collections: this.clientSchemas
};
});
_getIndexesDifference(indexesA, indexesB) {
return indexesA.filter(indexA => !indexesB.some(({ name }) => indexA.name === name));
}

async _getCurrentIndexes(mongodbConnection, collection) {

const indexes = await mongodbConnection.db.collection(collection).indexes();
async _createIndexes(modelInstance, currentIndexes, indexes) {

if(!Array.isArray(indexes))
return [];

return indexes.map(({ key, name, unique }) => ({ key, name, unique })).filter(({ name }) => name !== DEFAULT_ID_INDEX_NAME);
}
const indexesToCreate = this._getIndexesDifference(indexes, currentIndexes);

_getIndexesDifference(indexesA, indexesB) {
return indexesA.filter(indexA => !indexesB.some(({ name }) => indexA.name === name));
}
if(!indexesToCreate.length)
return this._saveResults('skipped', indexes.length);

async _updateCollectionIndexes(mongodbConnection, collection, indexes) {
this._saveResults('skipped', indexes.length - indexesToCreate.length);

const currentIndexes = await this._getCurrentIndexes(mongodbConnection, collection);
const result = await modelInstance.createIndexes(indexesToCreate);

return Promise.all([
this._createCollectionIndexes(mongodbConnection, collection, currentIndexes, indexes),
this._dropCollectionIndexes(mongodbConnection, collection, currentIndexes, indexes)
]);
this._saveResults(
result ? 'created' : 'createFailed',
indexesToCreate.length
);
}

async _dropCollectionIndexes(mongodbConnection, collection, currentIndexes, indexes) {
async _dropIndexes(modelInstance, currentIndexes, indexes) {

const indexesToDrop = this._getIndexesDifference(currentIndexes, indexes);

if(!indexesToDrop.length)
return;

this._saveResults(mongodbConnection.config.database, {
dropped: indexesToDrop.length
});
const result = await modelInstance.dropIndexes(
indexesToDrop.map(({ name }) => name)
);

return mongodbConnection.db
.collection(collection)
.dropIndexes(
indexesToDrop.map(({ name }) => name)
);
this._saveResults(
result ? 'dropped' : 'dropFailed',
indexesToDrop.length
);
}

async _createCollectionIndexes(mongodbConnection, collection, currentIndexes, indexes) {
async _updateIndexes(modelInstance, indexes, useReadDB) {

const indexesToCreate = this._getIndexesDifference(indexes, currentIndexes);
modelInstance.useReadDB = !!useReadDB; // false if useReadDB is false or not exists, true otherwise

if(!indexesToCreate.length) {
const currentIndexes = await this._getCurrentIndexes(modelInstance);

this._saveResults(mongodbConnection.config.database, {
skipped: indexes.length
});
return Promise.all([
this._createIndexes(modelInstance, currentIndexes, indexes),
this._dropIndexes(modelInstance, currentIndexes, indexes)
]);
}

return;
_prepareCoreIndexes() {

if(!isObject(this.coreSchemas)) {
throw new MongodbIndexCreatorError('Invalid core schemas: Should exist and must be an object.',
MongodbIndexCreatorError.codes.INVALID_CORE_SCHEMAS);
}

this._saveResults(mongodbConnection.config.database, {
created: indexesToCreate.length,
skipped: indexes.length - indexesToCreate.length
});
return Object.entries(this.coreSchemas).reduce((prev, [databaseKey, collections]) => {

return Promise.all(
this._validateCollections(collections);

indexesToCreate.map(({ key, name, unique }) => {
return [
...prev,
...Object.entries(collections).map(([collection, indexes]) => (
{
modelInstance: ModelGenerator.getInstanceByDatabaseKey(databaseKey, collection),
indexes
}
))
];

}, []);
}

const params = { name };
_prepareClientIndexes(clients) {

if(unique)
params.unique = unique;
if(!isObject(this.clientSchemas)) {
throw new MongodbIndexCreatorError('Invalid client schemas: Should exist and must be an object.',
MongodbIndexCreatorError.codes.INVALID_CLIENT_SCHEMAS);
}

return mongodbConnection.db.collection(collection).createIndex(key, params);
})
);
}
this._validateCollections(this.clientSchemas);

async _updateIndexes(mongodbInstance, collections) {
return clients.reduce((prev, client) => {

const mongodbConnection = await this._getMongoDbConnection(mongodbInstance);
return [
...prev,
...Object.entries(this.clientSchemas).map(([collection, indexes]) => (
{
modelInstance: ModelGenerator.getSessionInstance(client, collection),
indexes
}
))
];

return Promise.all(
Object.entries(collections).map(([collection, indexes]) => (
this._updateCollectionIndexes(mongodbConnection, collection, indexes)
))
);
}, []);
}

async createCoreIndexes() {

const databases = this._prepareCoreIndexes();

return Promise.all(
databases.reduce((prev, { mongodbInstance, collections }) => (
[...prev, this._updateIndexes(mongodbInstance, collections)]
), [])
databases.map(({ modelInstance, indexes }) => this._updateIndexes(modelInstance, indexes))
);
}

Expand All @@ -282,20 +211,14 @@ class MongodbIndexCreator {

return Promise.all(

databases.reduce((prev, { mongodbInstances, collections }) => {
databases.reduce((prev, { modelInstance, indexes }) => {

const promisesToAdd = [
this._updateIndexes(mongodbInstances.write, collections)
return [
...prev,
this._updateIndexes(modelInstance, indexes),
this._updateIndexes(modelInstance, indexes, true)
];

if(mongodbInstances.read) {

promisesToAdd.push(
this._updateIndexes(mongodbInstances.read, collections)
);
}

return [...prev, ...promisesToAdd];
}, [])
);
}
Expand Down
45 changes: 31 additions & 14 deletions lib/utils/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const assert = require('assert');
const Model = require('@janiscommerce/model');

/**
* Validates if the received item is an object and not an array
Expand All @@ -11,25 +11,42 @@ function isObject(object) {
return object !== null && typeof object === 'object' && !Array.isArray(object);
}

/**
* Validates if the received objects are exactly equal
* @param {Object} objectA first object to compare
* @param {Object} objectB second object to compare
* @returns {Boolean} true if both objects are equal, false otherwise
*/
function areEqualObjects(objectA, objectB) {
class ModelGenerator {

static getInstanceByDatabaseKey(databaseKey, table) {

class FakeModel extends Model {

get databaseKey() {
return databaseKey;
}

static get table() {
return table;
}
}

return new FakeModel();
}

static getSessionInstance(client, table) {

class FakeModel extends Model {

static get table() {
return table;
}
}

try {
const fakeModel = new FakeModel();

assert.deepStrictEqual(objectA, objectB);
return true;
fakeModel.session = { client };

} catch(err) {
return false;
return fakeModel;
}
}

module.exports = {
isObject,
areEqualObjects
ModelGenerator
};
Loading

0 comments on commit 616d7a0

Please sign in to comment.