Skip to content
This repository was archived by the owner on Oct 14, 2020. It is now read-only.
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 199 additions & 91 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const random = require('lodash.random');
const shuffle = require('lodash.shuffle');
const traverse = require('traverse');
const { inspect } = require('util');
const _ = require('lodash');

const hashPassword = require('../lib/feathersjs/hash-password');

Expand All @@ -65,17 +66,7 @@ module.exports = function (data /* modified in place */, options = {}) {
const tablesInfo = {};

tableNames.forEach(tableName => {
const table = data[tableName];
const sampleRec = table[0];
const keyName = !sampleRec ? '_id' : ('id' in sampleRec ? 'id' : '_id');

tablesInfo[tableName] = {
keyName,
currRec: -1,
isShuffled: false,
len: table.length,
keys: table.map((record, i) => i)
};
prepareTableInfo(tableName);
});

debug('tablesInfo:', tablesInfo);
Expand All @@ -84,107 +75,224 @@ module.exports = function (data /* modified in place */, options = {}) {
const ctx = Object.assign({}, { data, faker, tablesInfo, hashPassword }, options.expContext || {});

// process each table
tableNames.forEach(tableName => {
tableNames.forEach(tableName => processTable(tableName));

function processTable(tableName)
{
const ourKeyName = tablesInfo[tableName].keyName;
debug('process table:', tableName, ourKeyName);

// process each record
//sort the records to make sure expressions are ran last and process each record
data[tableName].forEach((rec, ix) => {
debug('.process row', rec);
data[tableName][ix] = sortRecord(rec);
processRecord(data[tableName][ix], ix);
});

}

ctx.dataCurrIndex = ix;

// no table keys have been shuffled yet for this record
tableNames.forEach(tableName => {
const tableInfo = tablesInfo[tableName];
tableInfo.isShuffled = false;
if (!testModeIndex) tableInfo.currRec = -1;
});
function processRecord(rec, ix)
{
debug('.process row', rec);

// debug('..prep tablesInfo', tablesInfo)
ctx.dataCurrIndex = ix;

// no table keys have been shuffled yet for this record
tableNames.forEach(tableName => {
const tableInfo = tablesInfo[tableName];
tableInfo.isShuffled = false;
if (!testModeIndex) tableInfo.currRec = -1;
});

// debug('..prep tablesInfo', tablesInfo)

// traverse the row's properties
traverse(rec).forEach(replacePlaceholders); // `rec` is converted in place.

function replacePlaceholders (value) {
if (!this.isLeaf) return;
function replacePlaceholders (value) {
if (!this.isLeaf) return;

// handle foreign keys
if (typeof value === 'string' && value.substr(0, fkLeaderLen) === fkLeader) {
replaceForeignKey(this);
}
// handle foreign keys
if (typeof value === 'string' && value.substr(0, fkLeaderLen) === fkLeader) {
replaceForeignKey(this);
}

// handle expressions
if (typeof value === 'string' && value.substr(0, expLeaderLen) === expLeader) {
replaceExpression(this);
}
// handle expressions
if (typeof value === 'string' && value.substr(0, expLeaderLen) === expLeader) {
replaceExpression(this);
}

function replaceForeignKey (that) {
debug('...fk leaf:', that.key, value);

// Validate placeholder
const ph = value.substr(fkLeaderLen);
let [targetTableName = '', randomType = 'random', fieldName] = ph.split(':');

const targetTableInfo = tablesInfo[targetTableName];
debug('...ph', targetTableName, randomType, fieldName, targetTableInfo);
if (!targetTableInfo) throw new Error(`${value}: table not found. (seeder-foreign-keys)`);

// Get target table information
let { keyName, isShuffled, len, keys } = targetTableInfo;
fieldName = fieldName || keyName;
debug('...target', keyName, isShuffled, len, keys);

if (len === 0) throw new Error(`${value}: table has no records. (seeder-foreign-keys)`);

// Get index of target record
let index;
switch (randomType) {
case 'random':
index = testModeIndex ? bumpCurrRec(targetTableInfo) : random(len - 1);
break;
case 'next':
debug('...next', isShuffled, testModeIndex);
if (!isShuffled && !testModeIndex) {
keys = targetTableInfo.keys = shuffle(keys);
targetTableInfo.currRec = -1;
function replaceForeignKey (that) {
debug('...fk leaf:', that.key, value);

// Validate placeholder
const ph = value.substr(fkLeaderLen);
let [targetTableName = '', randomType = 'random', fieldName, whereName] = ph.split(':');



//create a reduced table using where name


if(whereName)
{

//get value to match on
let value = rec[whereName];

if(!value) throw new Error(`${name}: must exist in record schema. (seeder-foreign-keys)`);

//field is also a faker, need to replace the referenced node first
if(typeof value === 'string'
&& (value.indexOf(fkLeader) > -1 || value.indexOf(expLeader) > -1)
) {
replacePlaceholders.call({
key: whereName,
isLeaf: true,
update: (newValue) => {
rec[whereName] = newValue;
value = newValue;
}

targetTableInfo.isShuffled = true;
index = keys[bumpCurrRec(targetTableInfo)];
break;
case 'curr':
if (!isShuffled) throw new Error(`${value}: no prior "next". (seeder-foreign-keys)`);

index = tablesInfo[targetTableName].currRec;
break;
default:
throw new Error(`${value}: invalid random type. (seeder-foreign-keys)`);
}, value);
}

let newTableName = targetTableName + '_' + whereName + '_' + value;

if(!tablesInfo[newTableName])
{
createReducedTable(whereName, value, targetTableName, newTableName);
}
debug('...index', index, len, testModeIndex);

// Replace by target field value
const targetValue = get(data[targetTableName][index], fieldName);
that.update(targetValue);
targetTableName = newTableName;
}

function replaceExpression (that) {
debug('...exp leaf:', that.key, value);
const exp = value.substr(expLeaderLen);

try {
const func = new Function('rec', 'ctx', `return ${exp};`);
const val = func(rec, ctx, data);
debug('val', val);
that.update(val);
} catch (err) {
console.log(err);
throw new Error(`${value}: ${err.message}. (seeder-foreign-keys)`);
}
const targetTableInfo = tablesInfo[targetTableName];
debug('...ph', targetTableName, randomType, fieldName, targetTableInfo);
if (!targetTableInfo) throw new Error(`${value}: table not found. (seeder-foreign-keys)`);

// Get target table information
let { keyName, isShuffled, len, keys } = targetTableInfo;
fieldName = fieldName || keyName;
debug('...target', keyName, isShuffled, len, keys);

if (len === 0) throw new Error(`${targetTableName}: table has no records. (seeder-foreign-keys)`);

// Get index of target record
let index;
switch (randomType) {
case 'random':
index = testModeIndex ? bumpCurrRec(targetTableInfo) : random(len - 1);
break;
case 'next':
debug('...next', isShuffled, testModeIndex);
if (!isShuffled && !testModeIndex) {
keys = targetTableInfo.keys = shuffle(keys);
targetTableInfo.currRec = -1;
}

targetTableInfo.isShuffled = true;
index = keys[bumpCurrRec(targetTableInfo)];
break;
case 'curr':
if (!isShuffled) throw new Error(`${value}: no prior "next". (seeder-foreign-keys)`);

index = tablesInfo[targetTableName].currRec;
break;
default:
throw new Error(`${value}: invalid random type. (seeder-foreign-keys)`);
}
debug('...index', index, len, testModeIndex);

// Replace by target field value
const targetValue = get(data[targetTableName][index], fieldName);
that.update(targetValue);
}
});
});

function replaceExpression (that) {
debug('...exp leaf:', that.key, value);
const exp = value.substr(expLeaderLen);

try {
const func = new Function('rec', 'ctx', `return ${exp};`);
const val = func(rec, ctx, data);
debug('val', val);
that.update(val);
} catch (err) {
console.log(err);
throw new Error(`${value}: ${err.message}. (seeder-foreign-keys)`);
}
}

//given a name to match on, create a tableInfo with only keys of records that match
function createReducedTable(name, value, oldTableName, newTableName)
{

const oldTable = data[oldTableName];

const sampleRec = oldTable[0];

if(typeof sampleRec[name] === 'string'
&& (sampleRec[name].indexOf(fkLeader) > -1 || sampleRec[name].indexOf(expLeader) > -1)
) {

//the table we are referring to needs to be processed first
processTable(oldTableName);
}

const table = data[newTableName] = _.filter(oldTable, [name, value]);

prepareTableInfo(newTableName);
}
}
}

function prepareTableInfo(tableName)
{
const table = data[tableName];
const sampleRec = table[0];
const keyName = !sampleRec ? '_id' : ('id' in sampleRec ? 'id' : '_id');

tablesInfo[tableName] = {
keyName,
currRec: -1,
isShuffled: false,
len: table.length,
keys: table.map((record, i) => i)
};
}

//assure expressions are ran after all fakers
function sortRecord(rec)
{
let keys = [], fk = [], exp = [];
let obj = {};

//fk first
for(var i in rec) {
let r = rec[i];
if(isFk(r)) fk.push(i);
else if(isExp(r)) exp.push(i);
else keys.push(i);
}

keys.concat(fk, exp).forEach(key => {
obj[key] = rec[key];
})

return obj;
}

function isFk(value)
{
return (Array.isArray(value) && isFk(value[0]) || (typeof value === 'string' && value.substr(0, fkLeaderLen) === fkLeader));
}

function isExp(value)
{
return (Array.isArray(value) && isExp(value[0]) || (typeof value === 'string' && value.substr(0, expLeaderLen) === expLeader));
}


};

function bumpCurrRec (targetTableInfo /* modified */) {
Expand Down