-
-
Save lancejpollard/4689178 to your computer and use it in GitHub Desktop.
| function adapter(name) { | |
| return adapter.instances[name] || (adapter.instances[name] = new AdapterClass(name)); | |
| } | |
| adapter.instances = {}; | |
| function AdapterClass(name) { | |
| this.name = name; | |
| this.types = {}; | |
| this.mixins = []; | |
| } | |
| var AdapterClassPrototype = AdapterClass.prototype; | |
| AdapterClassPrototype.type = function(key, value) { | |
| this.__lastKey__ = key; | |
| this.types[key] = value || {}; | |
| return this; | |
| } | |
| AdapterClassPrototype.to = function(block) { | |
| this.types[this.__lastKey__].to = block; | |
| return this; | |
| } | |
| AdapterClassPrototype.from = function(block) { | |
| this.types[this.__lastKey__].from = block; | |
| return this; | |
| } | |
| AdapterClassPrototype.mixin = function(mixin) { | |
| this.mixins.push(mixin); | |
| return this; | |
| } | |
| AdapterClassPrototype.create = function() { | |
| for (var key in mixin) | |
| this[key] = mixin[key]; | |
| return Object.create(this); | |
| } |
| var adapter = require('./adapter'); | |
| adapter(key) | |
| .type('string') | |
| .to(function(value) { return value; }) | |
| .from(function(value) { return value; }) | |
| .type('boolean') | |
| .type('date') | |
| .type('datetime') | |
| .type('time') | |
| .type('number') | |
| .type('integer') | |
| .type('float') | |
| .mixin({ | |
| /* | |
| * ['field', 'status', 'published'] // 3 values | |
| * ['field', 'title', 'match', /[aA]/] // 4 values | |
| * ['field', 'createdAt', 'lte', new Date] // 4 values | |
| */ | |
| find: function(criteria, callback) { | |
| var collections = this._compileCriteriaForFind(criteria); | |
| if (collections.length > 1) { | |
| // async iterate | |
| var i = 0 | |
| , recordsByType = {} | |
| , collection; | |
| function iterate() { | |
| // on second time around, you would append post ids to comments query, | |
| // stuff like that. | |
| collection = collections[i]; | |
| // collection[0] is the collection name | |
| // collection[1] are the query conditions | |
| this.db.collectionFor(collection[0]).find(collection[1]).toArray(function(error, records) { | |
| i++; | |
| if (i >= collections.length) { | |
| callback(null, records); | |
| } else { | |
| // save post records so you can use them in comments record query | |
| recordsByType[collection[0]] = records; | |
| process.nextTick(iterate); | |
| } | |
| }); | |
| } | |
| iterate(); | |
| } else { | |
| this.db.collectionFor(collections[0][0]).find(collections[0][1]).toArray(callback); | |
| } | |
| callback.call(this, undefined, collections); | |
| return this; | |
| }, | |
| create: function(criteria, callback) { | |
| }, | |
| update: function(criteria, callback) { | |
| }, | |
| destroy: function(criteria, callback) { | |
| }, | |
| save: function(criteria, callback) { | |
| }, | |
| findOrCreate: function(criteria, callback) { | |
| }, | |
| stream: function(criteria, callback) { | |
| }, | |
| connect: function(callback) { | |
| callback.call(this); | |
| return this; | |
| }, | |
| disconnect: function(callback) { | |
| }, | |
| _compileCriteriaForFind: function(criteria) { | |
| var i = 0 | |
| , n = criteria.length | |
| , criterion | |
| , collections = [] | |
| , collection | |
| , conditions; | |
| while (i < n) { | |
| criterion = criteria[i]; | |
| switch (criterion[0]) { | |
| case 'start': | |
| // this is hasMany, but is ready for graph queries. | |
| case 'incoming': | |
| conditions = {}; | |
| collection = [criterion[1], conditions]; | |
| collections.push(collection); | |
| break; | |
| case 'field': | |
| if (criterion.length === 3) { | |
| conditions[criterion[1]] = criterion[2]; | |
| } else { | |
| conditions[criterion[1]] || (conditions[criterion[1]] = {}); | |
| conditions[criterion[1]][criterion[2]] = criterion[3]; | |
| } | |
| break; | |
| } | |
| i++; | |
| } | |
| return collections; | |
| } | |
| }); |
| var adapter = require('./adapter'); | |
| function store(key) { | |
| return store.instances[key] || (store.instances[key] = new Store(key)); | |
| } | |
| store.instances = {}; | |
| function Store(key) { | |
| var adapterReference = adapter(key) | |
| , i = adapterReference.mixins.length | |
| , mixin | |
| , key; | |
| while (i--) { | |
| mixin = adapterReference.mixins[i]; | |
| for (key in mixin) | |
| this[key] = mixin[key]; | |
| } | |
| this.adapter = adapterReference; | |
| } |
| // this is how you'd use it | |
| controller('post') | |
| .where('status', 'published') | |
| .match('title', /[aA]/) | |
| .lte('createdAt', new Date) | |
| .incoming('comments') | |
| .match('message', 'node.js') | |
| .all(function(error, comments) { | |
| // comments is a controller, which is an array w/ extra functionality (Ember.ArrayProxy) | |
| }); | |
| // you could accomplish directly on a "store" | |
| // by manually passing a criteria array. | |
| store('mongodb').connect(function() { | |
| // the params in the routes could also be parsed into this, | |
| // so there is the property `route.criteria` | |
| var criteria = [ | |
| ['start', 'posts'] | |
| , ['field', 'status', 'published'] | |
| , ['field', 'title', 'match', '[aA]'] | |
| , ['field', 'createdAt', 'lte', new Date] | |
| , ['incoming', 'comments'] | |
| , ['field', 'message', 'match', 'node.js'] // where someone commented on post explicitly saying "node.js" | |
| ]; | |
| this.find(criteria, function(error, records) { | |
| console.log(JSON.stringify(records, null, 2)) | |
| }); | |
| }); | |
| // and here is how you would start doing graph queries: | |
| controller('user').outgoing('friends').outgoing('friends').all(function(error, fof) { | |
| fof // is a controller | |
| }); |
I learned from I think the gremlin code that, to do graph queries, all you need is the idea of "pipes", where the criteria array in the above gist is the same thing as the gremlin pipes.
I avoided the word "pipes" because, at least with gremlin (which is written in Java), every time you were to do something like users.outgoing('friends").outgoing('friends'), it would directly manipulate/query the database since it has a direct connection (at least it seems like that's what's happening). See https://github.com/tinkerpop/gremlin/blob/master/gremlin-java/src/main/java/com/tinkerpop/gremlin/java/GremlinPipeline.java and http://markorodriguez.com/2011/08/03/on-the-nature-of-pipes/ (also the wiki has a lot of good info: https://github.com/tinkerpop/gremlin/wiki)
The store would also be unique to the client connection, so the store could theoretically serve as an identity map for the current user making the request to the server.
How crazy would this be lol, if the criteria could be handled by multiple adapters (where you register models with specific adapters):
// users are stored in mongodb
// friendships are fetched from facebook via a FacebookFriendAdapter or something
// friends is defined on the friendship object
// gists are defined from github, via a GistAdapter or GitHubAdapter
controller('user').outgoing('friendships').incoming('friends').outgoing('gists').all(function(error, gists) {
});Wow, I really love that 3rd party adapter concept. Working with any other service won't become a pain anymore. I'm thinking of working with the criteria format for one of my projects, I really like the idea of conforming to a set of standards.
More stuff: https://gist.github.com/4689443#comment-764567
All this would be wrapped with the Ember class API, but this way it's extremely fast and flexible.