Skip to content

Instantly share code, notes, and snippets.

@jamesperi
Created June 8, 2017 19:02
Show Gist options
  • Save jamesperi/ed0c00bf21ceb16be1ded59cf647afd0 to your computer and use it in GitHub Desktop.
Save jamesperi/ed0c00bf21ceb16be1ded59cf647afd0 to your computer and use it in GitHub Desktop.
import {
concat
, get
, includes
, isArray
, isBoolean
, isPlainObject
, isString
, keys
, map
, omit
, pickBy
, reduce
, toLower
, toUpper
} from 'lodash'
const isLastIteration = (idx, accumulator, collection) => (idx === collection.length - 1 && accumulator.length === 0 )
export default class QueryBuilder {
static defaultOrder = [ ['id', 'DESC'] ];
static validSortValues = ['ASC', 'DESC'];
constructor(props) {
this.props = props
const { columnFilters, order, maxId, searchCol, searchString, contextKey, tableName, limit, Sequelize } = this.props
if(!limit) throw new Error(`queryBuilder requires a limit. invalid (${limit})`)
this.Sequelize = Sequelize
this.contextKey = contextKey
this.maxId = maxId
this.tableName = tableName
this.isSearch = (searchString && searchCol)
this.dateFilters = pickBy(columnFilters, isPlainObject)
this.multiValueFilters = pickBy(columnFilters, isArray)
this.columnSearchFilters = pickBy(columnFilters, isString)
this.booleanFilters = pickBy(columnFilters, isBoolean)
this._getColumnFilterQueries = this._getColumnFilterQueries.bind(this)
this._getDateRangeQuery = this._getDateRangeQuery.bind(this)
this._getColumnMultiValueQuery = this._getColumnMultiValueQuery.bind(this)
this._getColumnValueQuery = this._getColumnValueQuery.bind(this)
this._getBooleanQuery = this._getBooleanQuery.bind(this)
this._getCaseInsensitiveLike = this._getCaseInsensitiveLike.bind(this)
this.getPageId = this._getPageId.bind(this)
this.getSearch = this._getSearch.bind(this)
this._getOrder = this._getOrder.bind(this)
this._countQuery = this._countQuery.bind(this)
this.build = this.build.bind(this)
this.defaultSearchQuery = this.isSearch
? { [searchCol]: this._getCaseInsensitiveLike(searchCol, searchString) }
: {}
this.include = this._getIncludes()
this.limit = limit
this.order = this._getOrder(order)
}
_getPageId($and) {
return this.maxId
? Object.assign({}, $and, { id: { $lt: this.maxId } })
: $and
}
_getCaseInsensitiveLike(searchCol, searchString) {
return this.Sequelize.where(
this.Sequelize.fn('lower', this.Sequelize.col(searchCol)),
{ $like: `%${toLower(searchString)}%` })
}
_getOrder(order = [ 'empty' ]) {
return reduce(order, (acc, option, idx, collection) => {
if(isArray(option) && option.length == 2 ) {
const direction = toUpper(option[1])
const colName = option[0]
if(includes(QueryBuilder.validSortValues, direction)) {
return concat(acc, [ [colName, direction] ])
}
}
if(isLastIteration(idx, acc, collection)) {
return concat(QueryBuilder.defaultOrder)
}
return acc
}, [])
}
_getSearch($and) {
return Object.assign({}, $and, this.defaultSearchQuery )
}
_getIncludes() {
return { include: this.props.include || [] }
}
_getColumnFilterQueries($and) {
$and = this._getDateRangeQuery($and)
$and = this._getColumnMultiValueQuery($and)
$and = this._getColumnValueQuery($and)
$and = this._getBooleanQuery($and)
return $and
}
_getDateRangeQuery($and) {
/*
{
columnName: { type, start, end },
columnName: { type, start, end }
}
*/
return Object.assign({}, $and,
reduce(this.dateFilters, (acc, { start, end, type }, columnName) => {
let filter
if(type == 'before') {
filter = { $lt: new Date(end) }
} else if(type == 'after') {
filter = { $gt: new Date(start) }
} else {
filter = { $between: [new Date(start), new Date(end)] }
}
return Object.assign({}, acc, {[columnName]: filter })
}, {})
)
}
_getColumnMultiValueQuery($and) {
/*
{
columnName: ['value', 'value', 'value']
columnName: ['value', 'value', 'value']
}
*/
// {
// status: {$or: [ {status: 'OPEN'}, {status: 'CLOSED'} ]}
// }
return Object.assign({}, $and,
reduce(this.multiValueFilters, (acc, values, columnName) => {
return Object.assign({}, acc, {[columnName]: {$or: reduce(values, (agg, value) => concat(agg, { $eq: value }), []) } })
}, {})
)
}
_getColumnValueQuery($and) {
return Object.assign({}, $and,
reduce(this.columnSearchFilters, (acc, searchString, columnName) => {
return Object.assign({}, acc, {[columnName]: this._getCaseInsensitiveLike(columnName, searchString)})
}, {})
)
}
_getBooleanQuery($and) {
return $and
}
_countQuery(query) {
// console.log(`countQuery`, JSON.stringify(query))
let $and = omit(get(query, 'where.$and'), ['id']) // remvoes the paged ID from the count query i.e.. $and {id < maxId }
let where = omit(get(query, 'where'), ['$and'])
where = Object.assign({}, where, { $and })
return Object.assign({}, omit(query, ['limit', 'order', 'include']), { where })
}
build() {
try {
let $and = {}, $or = []
$and = this._getColumnFilterQueries($and)
$and = this._getPageId($and)
if(this.isSearch) {
$and = this.getSearch($and)
}
switch(this.tableName) {
case 'sizings':
$or = (this.contextKey && this.contextKey !== 'all')
? concat($or, { created_by: { $eq: this.contextKey } })
: $or
break;
case 'tasks':
if(this.contextKey && this.contextKey !== 'all') {
$or = concat($or, { responder: { $eq: this.contextKey } } )
}
$and = Object.assign({}, $and, { closed: { $eq: false }})
break;
case 'fundings':
$or = (this.contextKey && this.contextKey !== 'all')
? concat($or, { created_by: { $eq: this.contextKey } })
: $or
break;
case 'requests':
$or = (this.contextKey && this.contextKey !== 'all')
? concat($or, [{scoe_requestor: { $eq: this.contextKey } }, {cse_owner: { $eq: this.contextKey } }, {current_cse_owner: { $eq: this.contextKey } }])
: $or
break;
default:
}
let where, query = { limit: this.limit, order: this.order }
if($or.length > 0) {
$and = Object.assign({}, $and, { $or })
}
if(keys($and).length > 0) {
where = { $and }
}
if(keys(where).length > 0) {
query = Object.assign({}, query, { where })
}
if(this.include.include.length > 0) {
query = Object.assign({}, query, this.include)
}
return {
query
, countQuery: this._countQuery(query)
}
}
catch (e) {
console.log(`queryBuilder ${this.tableName} caught error `, e)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment