Skip to content

Commit

Permalink
Add relationsChecker
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Mironchik committed Jul 24, 2016
1 parent 291839a commit 8953a6d
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 0 deletions.
73 changes: 73 additions & 0 deletions server/utils/relationsChecker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const _ = require('lodash');
const Promise = require('bluebird');
const db = require('../db');

module.exports = {
hierarchy: ['users', 'boards', 'lists', 'cards'],

check(data) {
const entities = _.keys(data);
const higherEntity = this._getHigherEntity(entities);
const lowerEntity = _.without(entities, higherEntity)[0];
const sql = this._getSql(higherEntity, lowerEntity, _.values(data));
return db.result(sql)
.then(result => result.rowCount >= 1);
},

_getSql(higherEntity, lowerEntity, values) {
const joinSql = this._getJoinSql(higherEntity, lowerEntity);
const idsNames = this._getIdsNames([higherEntity, lowerEntity]);
const higherEntityChild = this._getChildEntity(higherEntity);
const table = `${higherEntity}_${higherEntityChild}`;
return `SELECT * FROM ${table} AS p ${joinSql}` +
`WHERE ${idsNames[0]} = '${values[0]}' AND ${idsNames[1]} = '${values[1]}'`;
},

_getJoinSql(higherEntity, lowerEntity) {
const innerEntities = this._getInnerEntities(higherEntity, lowerEntity);
const idsNames = this._getIdsNames(innerEntities);
return _.reduce(innerEntities, (acc, entity, i) => {
const nextEntity = innerEntities[i + 1] || lowerEntity;
const t0 = i === 0 ? 'p' : `t${i}`;
const t = `t${i + 1}`;
const idName = idsNames[i];
return acc + `JOIN ${entity}_${nextEntity} AS ${t} ON (${t0}.${idName} = ${t}.${idName})`;
}, '');
},

_getIdsNames(entities) {
return entities.map(entity => {
return entity.slice(0, -1) + '_id';
});
},

_getHigherEntity(entities) {
if (entities.filter(e => typeof e !== 'string').length) {
throw new Error('All entities must be a strings');
}

const i0 = this._getIndexInHierarchy(entities[0]);
const i1 = this._getIndexInHierarchy(entities[1]);

return i0 < i1 ? entities[0] : entities[1];
},

_getInnerEntities(higherEntity, lowerEntity) {
const highInd = this._getIndexInHierarchy(higherEntity);
const lowInd = this._getIndexInHierarchy(lowerEntity);

return this.hierarchy.filter((entity, i) => {
return i > highInd && i < lowInd;
});
},

_getChildEntity(entity) {
const index = this._getIndexInHierarchy(entity);
const child = this.hierarchy[index + 1];
return child || null;
},

_getIndexInHierarchy(entity) {
return this.hierarchy.indexOf(entity);
},
};
155 changes: 155 additions & 0 deletions test/server/utils/relationsChecker.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { assert } from 'chai';
import { recreateTables } from '../helpers';
import db from 'server/db';
import relationsChecker from 'server/utils/relationsChecker';

const initialHierarchy = relationsChecker.hierarchy;

describe('relationsChecker', () => {
before(() => {
relationsChecker.hierarchy = ['users', 'boards', 'lists', 'cards'];
});

after(() => {
relationsChecker.hierarchy = initialHierarchy;
});

describe('check', () => {
beforeEach(() => {
return recreateTables()
.then(() => {
return db.none(`
INSERT INTO users (id, username, email, hash, salt)
VALUES ('1', 'test', '[email protected]', 'hash', 'salt');
INSERT INTO boards (id, title)
VALUES ('7', 'board 7'), ('10', 'board 10'), ('15', 'board 15');
INSERT INTO users_boards
VALUES ('1', '10'), ('1', '15');
INSERT INTO lists (id, title)
VALUES ('4', 'list 4'), ('5', 'list 5');
INSERT INTO boards_lists
VALUES ('15', '4');
`);
});
});

it('should resolve true if entries are related', () => {
return relationsChecker.check({
users: '1',
lists: '4'
}).then(result => assert.isTrue(result));
});

it('should resolve false if entries are not related', () => {
return relationsChecker.check({
users: '1',
lists: '5'
}).then(result => assert.isFalse(result));
});

it('should resolve false if higher entry does not exist', () => {
return relationsChecker.check({
users: '27',
lists: '5'
}).then(result => assert.isFalse(result));
});

it('should resolve false if lower entry does not exist', () => {
return relationsChecker.check({
users: '1',
lists: '50'
}).then(result => assert.isFalse(result));
});
});

describe('_getHigherEntity', () => {
it('should return higher entity name', () => {
const higher1 = relationsChecker._getHigherEntity(['cards', 'users']);
assert.equal(higher1, 'users');
const higher2 = relationsChecker._getHigherEntity(['cards', 'boards']);
assert.equal(higher2, 'boards');
});
});

describe('_getIndexInHierarchy', () => {
it('should return index of entity in hierarchy', () => {
const index = relationsChecker._getIndexInHierarchy('lists');
assert.equal(index, 2);
});
});

describe('_getChildEntity', () => {
it('should return next entity in hierarchy', () => {
const childEntity = relationsChecker._getChildEntity('lists');
assert.equal(childEntity, 'cards');
});

it('should return null, if entity has no children', () => {
const childEntity = relationsChecker._getChildEntity('cards');
assert.isNull(childEntity);
});
});

describe('_getInnerEntities', () => {
it('should return array entities between two given entities', () => {
const insideEntities = relationsChecker._getInnerEntities('users', 'cards');
assert.deepEqual(insideEntities, ['boards', 'lists']);
});

it('should return [], if entities are siblings', () => {
const insideEntities = relationsChecker._getInnerEntities('lists', 'cards');
assert.deepEqual(insideEntities, []);
});
});

describe('_getIdsNames', () => {
it('should return ids columns names in relations table', () => {
const idsNames = relationsChecker._getIdsNames(['boards', 'lists', 'cards']);
assert.deepEqual(idsNames, ['board_id', 'list_id', 'card_id']);
});
});

describe('_getJoinSql', () => {
it('should return sql with join', () => {
const sql = relationsChecker._getJoinSql('users', 'cards', ['user_id', 'card_id']);
assert.equal(sql,
'JOIN boards_lists AS t1 ON (p.board_id = t1.board_id)' +
'JOIN lists_cards AS t2 ON (t1.list_id = t2.list_id)'
);
});

it('should return empty string, when given entities are siblings', () => {
const sql = relationsChecker._getJoinSql('users', 'boards', ['user_id', 'board_id']);
assert.equal(sql, '');
});
});

describe('_getSql', () => {
it('should return sql for checking relations where between entities are 2 or more entities', () => {
const sql = relationsChecker._getSql('users', 'cards', [10, 5]);
assert.equal(sql,
"SELECT * FROM users_boards AS p " +
"JOIN boards_lists AS t1 ON (p.board_id = t1.board_id)" +
"JOIN lists_cards AS t2 ON (t1.list_id = t2.list_id)" +
"WHERE user_id = '10' AND card_id = '5'"
);
});

it('should return sql for checking relations where between entities 1 entity', () => {
const sql = relationsChecker._getSql('users', 'lists', [10, 5]);
assert.equal(sql,
"SELECT * FROM users_boards AS p " +
"JOIN boards_lists AS t1 ON (p.board_id = t1.board_id)" +
"WHERE user_id = '10' AND list_id = '5'"
);
});

it('should return sql for checking relations where entities are siblings', () => {
const sql = relationsChecker._getSql('users', 'boards', [10, 5]);
assert.equal(sql,
"SELECT * FROM users_boards AS p " +
"WHERE user_id = '10' AND board_id = '5'"
);
});
});
});

0 comments on commit 8953a6d

Please sign in to comment.