Skip to content

Commit

Permalink
Port Cypher queries to Neo4j 2.0 labels!
Browse files Browse the repository at this point in the history
  • Loading branch information
aseemk committed Mar 30, 2014
1 parent e23455f commit 58e6d02
Showing 1 changed file with 63 additions and 74 deletions.
137 changes: 63 additions & 74 deletions models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,6 @@ var db = new neo4j.GraphDatabase(
'http://localhost:7474'
);

// constants:

var INDEX_NAME = 'nodes';
var INDEX_KEY = 'type';
var INDEX_VAL = 'user';

var FOLLOWS_REL = 'follows';

// private constructor:

var User = module.exports = function User(_node) {
Expand All @@ -30,10 +22,6 @@ Object.defineProperty(User.prototype, 'id', {
get: function () { return this._node.id; }
});

Object.defineProperty(User.prototype, 'exists', {
get: function () { return this._node.exists; }
});

Object.defineProperty(User.prototype, 'name', {
get: function () {
return this._node.data['name'];
Expand All @@ -43,28 +31,6 @@ Object.defineProperty(User.prototype, 'name', {
}
});

// private instance methods:

User.prototype._getFollowingRel = function (other, callback) {
var query = [
'START user=node({userId}), other=node({otherId})',
'OPTIONAL MATCH (user) -[rel:FOLLOWS_REL]-> (other)',
'RETURN rel'
].join('\n')
.replace('FOLLOWS_REL', FOLLOWS_REL);

var params = {
userId: this.id,
otherId: other.id,
};

db.query(query, params, function (err, results) {
if (err) return callback(err);
var rel = results[0] && results[0]['rel'];
callback(null, rel);
});
};

// public instance methods:

User.prototype.save = function (callback) {
Expand All @@ -74,9 +40,26 @@ User.prototype.save = function (callback) {
};

User.prototype.del = function (callback) {
this._node.del(function (err) {
// use a Cypher query to delete both this user and his/her following
// relationships in one transaction and one network request:
// (note that this'll still fail if there are any relationships attached
// of any other types, which is good because we don't expect any.)
var query = [
'MATCH (user:User)',
'WHERE ID(user) = {userId}',
'DELETE user',
'WITH user',
'MATCH (user) -[rel:follows]- (other)',
'DELETE rel',
].join('\n')

var params = {
userId: this.id
};

db.query(query, params, function (err) {
callback(err);
}, true); // true = yes, force it (delete all relationships)
});
};

User.prototype.follow = function (other, callback) {
Expand All @@ -86,12 +69,19 @@ User.prototype.follow = function (other, callback) {
};

User.prototype.unfollow = function (other, callback) {
this._getFollowingRel(other, function (err, rel) {
if (err) return callback(err);
if (!rel) return callback(null);
rel.del(function (err) {
callback(err);
});
var query = [
'MATCH (user:User) -[rel:follows]-> (other:User)',
'WHERE ID(user) = {userId} AND ID(other) = {otherId}',
'DELETE rel',
].join('\n')

var params = {
userId: this.id,
otherId: other.id,
};

db.query(query, params, function (err) {
callback(err);
});
};

Expand All @@ -100,14 +90,11 @@ User.prototype.unfollow = function (other, callback) {
User.prototype.getFollowingAndOthers = function (callback) {
// query all users and whether we follow each one or not:
var query = [
'START user=node({userId}), other=node:INDEX_NAME(INDEX_KEY="INDEX_VAL")',
'OPTIONAL MATCH (user) -[rel:FOLLOWS_REL]-> (other)',
'RETURN other, COUNT(rel)' // COUNT(rel) is a hack for 1 or 0
'MATCH (user:User), (other:User)',
'OPTIONAL MATCH (user) -[rel:follows]-> (other)',
'WHERE ID(user) = {userId}',
'RETURN other, COUNT(rel)', // COUNT(rel) is a hack for 1 or 0
].join('\n')
.replace('INDEX_NAME', INDEX_NAME)
.replace('INDEX_KEY', INDEX_KEY)
.replace('INDEX_VAL', INDEX_VAL)
.replace('FOLLOWS_REL', FOLLOWS_REL);

var params = {
userId: this.id,
Expand Down Expand Up @@ -147,40 +134,42 @@ User.get = function (id, callback) {
};

User.getAll = function (callback) {
db.getIndexedNodes(INDEX_NAME, INDEX_KEY, INDEX_VAL, function (err, nodes) {
if (err) {
// HACK our node index doesn't exist by default on fresh dbs, so
// check to see if that's the reason for this error.
// it'd be better to have an explicit way to create this index
// before running the app, e.g. an "initialize db" script.
//
// HACK it's also brittle to be relying on the error's message
// property. it'd be better if node-neo4j added more semantic
// properties to errors, e.g. neo4jException or statusCode.
// https://github.com/thingdom/node-neo4j/issues/73
//
if (err.message.match(/Neo4j NotFoundException/i)) {
return callback(null, []);
} else {
return callback(err);
}
}
var users = nodes.map(function (node) {
return new User(node);
var query = [
'MATCH (user:User)',
'RETURN user',
].join('\n');

db.query(query, null, function (err, results) {
if (err) return callback(err);
var users = results.map(function (result) {
return new User(result['user']);
});
callback(null, users);
});
};

// creates the user and persists (saves) it to the db, incl. indexing it:
User.create = function (data, callback) {
// construct a new instance of our class with the data, so it can
// validate and extend it, etc., if we choose to do that in the future:
var node = db.createNode(data);
var user = new User(node);
node.save(function (err) {

// but we do the actual persisting with a Cypher query, so we can also
// apply a label at the same time. (the save() method doesn't support
// that, since it uses Neo4j's REST API, which doesn't support that.)
var query = [
'CREATE (user:User {data})',
'RETURN user',
].join('\n');

var params = {
data: data
};

db.query(query, params, function (err, results) {
if (err) return callback(err);
node.index(INDEX_NAME, INDEX_KEY, INDEX_VAL, function (err) {
if (err) return callback(err);
callback(null, user);
});
var user = new User(results[0]['user']);
callback(null, user);
});
};

0 comments on commit 58e6d02

Please sign in to comment.