Last active
December 20, 2015 11:29
-
-
Save Qard/6123597 to your computer and use it in GitHub Desktop.
ActiveRecord-like query builder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var events = require('events'); | |
var actions = [ | |
'find' | |
, 'create' | |
, 'update' | |
, 'remove' | |
]; | |
// These accept hashes to transform to key/val property pairs | |
var hashers = [ | |
'where' | |
]; | |
// These do nothing with arguments | |
var noops = [ | |
'and' | |
, 'not' | |
]; | |
var sorters = [ | |
'sortBy' | |
, 'sort_by' | |
]; | |
var sorterDirections = [ | |
'ascending' | |
, 'descending' | |
]; | |
var other = [ | |
'include' | |
]; | |
// All valid flaggers are grouped together | |
var flaggers = other | |
.concat(sorters) | |
.concat(sorterDirections) | |
.concat(actions) | |
.concat(noops) | |
.concat(hashers); | |
function Qwry (table, options) { | |
var relations = options.relations || []; | |
var props = options.properties || []; | |
var ev = new events.EventEmitter(); | |
var current; | |
var state = { | |
where: [] | |
, sort: [] | |
}; | |
// Not lets you turn negation on for the next property | |
var not = false; | |
ev.on('not', function () { | |
not = true; | |
}); | |
// Sort DESC by default | |
ev.on('ascending', function () { | |
var last = state.sort[state.sort.length - 1]; | |
last.order = 'ASC'; | |
}); | |
// End the currently running command and run it's behaviour | |
function dispatcher () { | |
var args = Array.prototype.slice.call(arguments); | |
ev.emit.apply(ev, [current].concat(args)); | |
current = null; | |
return dispatcher; | |
} | |
// Expose state externally | |
// Probably better to add a serializer function instead | |
dispatcher.state = state; | |
dispatcher.emitter = ev; | |
// The flagger flags that we are using a particular command | |
// This lets us fake getters as actual callers | |
function flagger (cmd) { | |
dispatcher.__defineGetter__(cmd, function () { | |
if (current) dispatcher(); | |
current = cmd; | |
return dispatcher; | |
}); | |
} | |
// All tokens and properties are turned into legal flag functions | |
flaggers.forEach(flagger); | |
relations.forEach(flagger); | |
props.forEach(flagger); | |
// Hashes change mode and forward hash data to property receivers | |
hashers.forEach(function (mode) { | |
ev.on(mode, function (props) { | |
state.mode = mode; | |
if (typeof props === 'object') { | |
Object.keys(props).forEach(function (prop) { | |
ev.emit(prop, props[prop]); | |
}); | |
} | |
}); | |
}); | |
// Listen to sorts from both `sort` and `by` | |
sorters.forEach(function (sorter) { | |
ev.on(sorter, function (props) { | |
state.mode = 'sort'; | |
if (typeof props === 'object') { | |
Object.keys(props).forEach(function (prop) { | |
ev.emit(prop, props[prop]); | |
}); | |
} | |
}); | |
}); | |
actions.forEach(function (action) { | |
ev.on(action, function (cb) { | |
state.action = action; | |
ev.emit('execute', state, cb); | |
}); | |
}); | |
// Push key/value pairs to the relevant mode list | |
[['property', props], ['relation', relations]].forEach(function (group) { | |
group[1].forEach(function (prop) { | |
ev.on(prop, function (value) { | |
if (state.mode === 'sort') { | |
state.sort.push({ | |
type: group[0] | |
, name: prop | |
, direction: value || 'DESC' | |
}); | |
} else { | |
state[state.mode].push({ | |
type: group[0] | |
, name: prop | |
, value: value | |
, not: !!not | |
}); | |
// Remember to disable negation after | |
not = false; | |
} | |
}); | |
}); | |
}); | |
return dispatcher; | |
} | |
// Create new query chain builder | |
var User = Qwry('user', { | |
properties: ['username','email','banned'] | |
, relations: ['parent'] | |
}); | |
// Test all the parts | |
var q = User.where | |
.username('me') | |
.parent({ parent_id: 1 }) | |
.and.email('[email protected]') | |
.and.not.banned(true) | |
.sort_by.email('ASC') | |
.sort_by({ username: 'DESC' }); | |
// Fake the execution for now... | |
q.emitter.on('execute', function (state, cb) { | |
console.log('state is', state); | |
var user = {}; | |
state.where.forEach(function (c) { | |
user[c.name] = c.value; | |
}); | |
cb(user); | |
}); | |
// Find user | |
q.find(function (user) { | |
console.log(user); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"where": [ | |
{ | |
"type": "property", | |
"name": "username", | |
"value": "me", | |
"not": false | |
}, | |
{ | |
"type": "relation", | |
"name": "parent", | |
"value": { | |
"parent_id": 1 | |
}, | |
"not": false | |
}, | |
{ | |
"type": "property", | |
"name": "email", | |
"value": "[email protected]", | |
"not": false | |
}, | |
{ | |
"type": "property", | |
"name": "banned", | |
"value": true, | |
"not": true | |
} | |
], | |
"sort": [ | |
{ | |
"type": "property", | |
"name": "email", | |
"direction": "ASC" | |
}, | |
{ | |
"type": "property", | |
"name": "username", | |
"direction": "DESC" | |
} | |
], | |
"mode": "sort", | |
"action": "find" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment