Skip to content

Instantly share code, notes, and snippets.

@Qard
Last active December 20, 2015 11:29
Show Gist options
  • Save Qard/6123597 to your computer and use it in GitHub Desktop.
Save Qard/6123597 to your computer and use it in GitHub Desktop.
ActiveRecord-like query builder
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('a@b.com')
.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);
});
{
"where": [
{
"type": "property",
"name": "username",
"value": "me",
"not": false
},
{
"type": "relation",
"name": "parent",
"value": {
"parent_id": 1
},
"not": false
},
{
"type": "property",
"name": "email",
"value": "a@b.com",
"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