UNPKG

sequelize-cmd

Version:
426 lines (373 loc) 15.8 kB
'use strict'; var _ = require('lodash'); module.exports = Compare; /** * Compare models using previous migration model snapshots. * @returns {function} * @constructor */ function Compare () { var self = this; function compare (current, previous) { // all up/down properties are arrays of string. var build = { up: { createTable: [], dropTable: [], dropTables: [], addColumn: [], changeColumn: [], removeColumn: [] //renameTable: [], // TODO future feature. //renameColumn: [], // TODO future feature. //addIndex: [], // TODO future feature. //removeIndex: [] // TODO future feature. }, down: { createTable: [], dropTable: [], dropTables: [], addColumn: [], changeColumn: [], removeColumn: [] //renameTable: [], // TODO future feature. //renameColumn: [], // TODO future feature. //addIndex: [], // TODO future feature. //removeIndex: [] // TODO future feature. }, options: [] }, validAttrs = self.migConf.validAttributes, excludeAttrs = self.migConf.excludeAttributes, typeVariable = self.migConf.typeVariable || 'types', modelName = current.name, baseTabs = 2; function getTabs(int, addBase) { var tabs = ''; if(addBase) int += baseTabs; while(int){ tabs += '\t'; --int; } return tabs; } function isValid(attr) { return validAttrs.indexOf(attr) !== -1; } function qwrap(str) { return "'" + str + "'"; } function getActions(type) { var actions = []; _.forEach(type, function (v) { if(v.length) actions.push(v); }); return actions; } function filterAttributes(obj) { var clone = _.clone(obj); _.forEach(clone, function (v,k) { if(!isValid(k)) delete clone[k]; }); return clone; } function arrayToString(arr, def) { var val = '['; def = def || undefined; if(!arr || !arr.length) return def; if(!_.isString(arr[0])){ val += arr.join(','); }else { _.forEach(arr, function (v) { val += qwrap(v) + ','; }); val = val.slice(0, val.length -1); } val += ']'; return val; } function funcToString(func, level) { var split = func.split('\n'), tmp = []; _.forEach(split, function (v, k) { v = v.replace(/^\s+/, '').replace('\r', ''); if(k +1 === split.length) tmp.push(getTabs(level + 1, true) + v); else if(k > 0) tmp.push(getTabs(level + 2, true) + v); else tmp.push(v); }); return tmp.join('\n'); } /** * Concats all up migration events to string. * @private * @memberof Compare * @returns {string} */ function upToString() { var upStr = '', ctr = 0, items = getActions(build.up), tab, suffix; // build the output string for up. _.forEach(items, function (v,k) { _.forEach(v, function(item, key){ suffix = (ctr < v.length -1) || (k+1 < items.length) ? '\n' : ''; tab = ctr > 0 ? getTabs(0, true) : ''; upStr += (tab + item + suffix); ctr += 1; }); }); return upStr; } /** * Concats all down migration events to string. * @private * @memberof Compare * @returns {string} */ function downToString() { var dwnStr = '', ctr = 0, items = getActions(build.down), tab, suffix; // build the output string for down. _.forEach(items, function (v,k) { _.forEach(v, function(item, key){ suffix = (ctr < v.length -1) || (k+1 < items.length) ? '\n' : ''; tab = ctr > 0 ? getTabs(0, true) : ''; dwnStr += (tab + item + suffix); ctr += 1; }); }); return dwnStr; } /** * Recurse child options concat to string. * @private * @memberof Compare * @param {object} obj - the child object. * @returns {string} */ function recurseChildOptions(obj, level){ var child = '', len = Object.keys(obj).length, ctr = 0, prefix; level = level || 1; for(var prop in obj) { if(obj.hasOwnProperty(prop)){ var tab = getTabs(level +1, true), val; prefix = ctr > 0 ? ',\n' : ''; if(_.isPlainObject(obj[prop])){ if(!_.isEmpty(obj[prop])){ var subChild = recurseChildOptions(obj[prop], level + 1); if(subChild && subChild.length) child += (tab + prop + ': { \n' + subChild + '\n' + tab + '}'); } } else { if(_.isArray(obj[prop])){ val = arrayToString(obj[prop]); if(val){ tab = ctr === 0 ? getTabs(level) : getTabs(level +1, true); child += (prefix + tab + prop + ': ' + arrayToString(obj[prop])); } } else { var isFunc; val = obj[prop]; isFunc = _.isFunction(val); if(_.isString(val)){ val = "'" + val.toString() + "'"; } else { val = val.toString(); if(isFunc) val = funcToString(val, level); } child += (prefix + tab + prop + ': ' + val); } } ctr +=1; } } return child; } /** * Parse options for create model migrations. * @private * @memberof Compare * @param {object} obj - the object of options. */ function parseOptions(obj) { var ctr = 0, tab = getTabs(1, true); for(var prop in obj) { if(obj.hasOwnProperty(prop)){ if(_.isPlainObject(obj[prop])){ if(!_.isEmpty(obj[prop])){ var child = recurseChildOptions(obj[prop]); if(child && child.length) build.options.push(tab + prop + ': { \n' + child + ' \n' + tab + '}'); } } else { if(_.isArray(obj[prop])){ build.options.push(tab + prop + ': ' + arrayToString(obj[prop])); } else { var val = obj[prop]; if(_.isString(val)) val = qwrap(val); else val = val.toString(); build.options.push(tab + prop + ': ' + val); } } } ctr +=1; } } /** * Converts a property's atttributes to string. * @private * @memberof Compare * @param {object} obj - the object of attributes. * @param {object} prevObj - the previous object of attributes from last snapshot. * @returns {string} */ function attributesToString(obj, prevObj, level) { var attrs = '', prefix, tab; level = level || 1; tab = getTabs(level, true); _.forEach(obj, function (v, k) { if(excludeAttrs.indexOf(k) === -1){ var prev = prevObj && prevObj[k] !== undefined ? prevObj[k] : undefined; prefix = attrs.length ? ',\n' : ''; if(_.isPlainObject(v)){ var subAttr = attributesToString(v, prev, level +1); if(subAttr && subAttr.length) attrs += (prefix + tab + k + ': {\n' + subAttr + '\n' + tab + '}'); } else { if(prev !== v){ var val = k === 'type' ? 'types.' + v : v; if(_.isArray(val)) val = arrayToString(val); if(_.isFunction(val)){ val = funcToString(val.toString(), 2); } attrs += (prefix + tab + k + ': ' + val); } } } }); return attrs; } /** * Iterate table/model properties and compare or create new. * @private * @memberof Compare */ function compareMigration(){ var tab // the model/table already exists. if(previous){ tab = getTabs(0, true), // compare to previous table checking for // any dropped columns. _.forEach(previous.attributes, function (v,k) { if (v._modelAttribute) { var curCol = current && current.attributes ? current.attributes[k] : undefined, removeAttrs; // if curCol is undefined it was removed. if(!curCol){ removeAttrs = attributesToString(filterAttributes(v)); if(removeAttrs) { build.up.removeColumn.push( 'migration.removeColumn(' + qwrap(modelName) + ', ' + qwrap(k) + ');' ); build.down.addColumn.push( 'migration.addColumn(' + qwrap(modelName) + ', ' + qwrap(k) + ',\n' + tab + '{\n'+ removeAttrs + '\n' + tab + '});' ); } } } }); // iterate current defined attrs // looking for adds & changes. _.forEach(current.attributes, function (v,k) { if(v._modelAttribute){ // check if is new column or existing. var prevCol = previous.attributes ? previous.attributes[k] : undefined, prevAttrs, curAttrs; if(prevCol){ curAttrs = attributesToString(filterAttributes(v), filterAttributes(prevCol)); prevAttrs = attributesToString(filterAttributes(prevCol), filterAttributes(v)); if(curAttrs) { build.up.changeColumn.push( 'migration.changeColumn(' + qwrap(modelName) + ', ' + qwrap(k) + ',\n' + tab + '{\n' + curAttrs + '\n' + tab + '});' ); } if(prevAttrs) { build.down.changeColumn.push( 'migration.changeColumn(' + qwrap(modelName) + ', ' + qwrap(k) + ',\n' + tab + '{\n' + prevAttrs + '\n' + tab +'});' ); } } else { curAttrs = attributesToString(filterAttributes(v)); // column needs to be added. if(curAttrs) { build.up.addColumn.push( 'migration.addColumn(' + qwrap(modelName) + ', ' + qwrap(k) + ',\n' + '{\n' + curAttrs + '\n' + tab + '});' ); build.down.removeColumn.push( 'migration.removeColumn(' + qwrap(modelName) + ', ' + qwrap(k) + ');' ); } } } ctr += 1; }); } // this is a new model/table. else { // this is a new table so we need to parse options. var newAttrs = [], ctr = 0, attrTab = getTabs(1, true), newOpts; tab = getTabs(0, true); parseOptions(current.options); newOpts = build.options.join(',\n') _.forEach(current.attributes, function (v,k) { //if (v._modelAttribute) { var attrs = attributesToString(filterAttributes(v), null, 2); if(attrs) newAttrs.push(attrTab + k + ': {\n' + attrs + '\n' + attrTab + '}'); //} ctr +=1; }); build.up.createTable.push( 'migration.createTable(' + qwrap(modelName) + ',\n' + tab + '{\n' + newAttrs.join(',\n') + '\n' + tab + '},\n' + tab + '{\n' + newOpts + '\n' + tab + '});' ); build.down.dropTable.push('migration.dropTable(' + qwrap(modelName) + ');'); } build.up.toString = upToString; build.down.toString = downToString; } // parse properties attributes & options. compareMigration(); return build; }; return compare; };