From 4e849a9f7a4b709fe31f6001442a9c49a45c113c Mon Sep 17 00:00:00 2001 From: Anatoliy Chakkaev Date: Mon, 3 Oct 2011 17:37:33 +0400 Subject: [PATCH] Neo4j --- lib/neo4j.js | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 lib/neo4j.js diff --git a/lib/neo4j.js b/lib/neo4j.js new file mode 100644 index 00000000..276cd7bb --- /dev/null +++ b/lib/neo4j.js @@ -0,0 +1,177 @@ +/** + * Module dependencies + */ +var neo4j = require('neo4j'); + +exports.initialize = function initializeSchema(schema, callback) { + schema.client = new neo4j.GraphDatabase(schema.settings.url); + schema.adapter = new Neo4j(schema.client); +}; + +function Neo4j(client) { + this._models = {}; + this.client = client; + this.cache = {}; +} + +Neo4j.prototype.define = function defineModel(descr) { + this._models[descr.model.modelName] = descr; +}; + +Neo4j.prototype.node = function find(id, callback) { + if (this.cache[id]) { + callback(null, this.cache[id]); + } else { + this.client.getNodeById(id, function (err, node) { + this.cache[id] = node; + callback(err, node); + }.bind(this)); + } +}; + +Neo4j.prototype.create = function create(model, data, callback) { + data.nodeType = model; + var node = this.client.createNode(data); + node.save(function (err) { + if (err) { + return callback && callback(err); + } + this.cache[node.id] = node; + node.index(model, 'id', node.id, function (err) { + callback && callback(err, node.id); + }); + }.bind(this)); +}; + +Neo4j.prototype.save = function save(model, data, callback) { + this.node(data.id, function (err, node) { + if (err) return callback(err); + node.data = cleanup(data); + node.save(callback); + }); +}; + +Neo4j.prototype.exists = function exists(model, id, callback) { + delete this.cache[id]; + this.node(id, callback); +}; + +Neo4j.prototype.find = function find(model, id, callback) { + delete this.cache[id]; + this.node(id, function (err, node) { + if (node && node.data) { + node.data.id = id; + } + callback(err, this.readFromDb(model, node && node.data)); + }.bind(this)); +}; + +Neo4j.prototype.readFromDb = function readFromDb(model, data) { + if (!data) return data; + var res = {}; + var props = this._models[model].properties; + Object.keys(data).forEach(function (key) { + if (props[key] && props[key].type.name === 'Date') { + res[key] = new Date(data[key]); + } else { + res[key] = data[key]; + } + }); + return res; +}; + +Neo4j.prototype.destroy = function destroy(model, id, callback) { + this.node(id, function (err, node) { + if (err) return callback(err); + node.delete(function (err) { + if (err) return callback(err); + delete this.cache[id]; + }.bind(this), true); + }); +}; + +Neo4j.prototype.all = function all(model, filter, callback) { + this.client.queryNodeIndex(model, 'id:*', function (err, nodes) { + callback(err, filter && nodes ? nodes.filter(applyFilter(filter)) : nodes); + }); +}; + +function applyFilter(filter) { + if (typeof filter === 'function') { + return filter; + } + var keys = Object.keys(filter); + return function (obj) { + var pass = true; + keys.forEach(function (key) { + if (!test(filter[key], obj.data[key])) { + pass = false; + } + }); + return pass; + } + + function test(example, value) { + if (typeof value === 'string' && example && example.constructor.name === 'RegExp') { + return value.match(example); + } + // not strict equality + return example == value; + } +} + +Neo4j.prototype.destroyAll = function destroyAll(model, callback) { + var wait, error = null; + this.all(model, null, function (err, collection) { + if (err) return callback(err); + wait = collection.length; + collection && collection.forEach && collection.forEach(function (node) { + node.delete(done, true); + }); + }); + + function done(err) { + error = error || err; + if (--wait === 0) { + callback(error); + } + } +}; + +Neo4j.prototype.count = function count(model, callback) { + this.all(model, null, function (err, collection) { + callback(err, collection ? collection.length : 0); + }); +}; + +Neo4j.prototype.updateAttributes = function updateAttributes(model, id, data, cb) { + data.id = id; + this.node(id, function (err, node) { + this.save(model, merge(node.data, data), cb); + }.bind(this)); +}; + +function cleanup(data) { + if (!data) return {}; + var res = {}; + Object.keys(data).forEach(function (key) { + var v = data[key]; + if (v === null) { + // skip + // console.log('skip null', key); + } else if (v.constructor.name === 'Array' && v.length === 0) { + // skip + // console.log('skip blank array', key); + } else { + res[key] = v; + } + }); + return res; +} + +function merge(base, update) { + Object.keys(update).forEach(function (key) { + base[key] = update[key]; + }); + return base; +}