brikteknologier / seraph-model

thin model layer for seraph/neo4j (node.js)
MIT License
111 stars 28 forks source link

cast cypher query results to model instance for computed fields #52

Closed alank64 closed 10 years ago

alank64 commented 10 years ago

I might just be missing something but after I perform a cypher query in my model, which returns neo4j results from my profile node, I'm just not sure how to cast these into my Profile seraph-model object. So in my model I have...

Profile.forUser = function(id, callback){

  var cypher = "START u=node({user_id}) "
                + "MATCH u-[r2:has_profile]->p "
                + "RETURN p";
  db.query(cypher, {user_id: id}, function(err, results){

    // ???   profile = Profile(results);
    if (err)
      return callback(err, null);
    else
      return callback(null, results);

  });
};

but obviously, simply returning results will not give me a Profile object with my computed fields Profile.addComputedField().

jonpacker commented 10 years ago

If it's just computed fields you're after, you could use Profile.compute(profileObject, callback). In your example it would be beneficial to use a module such as async to do something like this:

var async = require('async');
Profile.forUser = function(id, callback){

  var cypher = "START u=node({user_id}) "
                + "MATCH u-[r2:has_profile]->p "
                + "RETURN p";
  db.query(cypher, {user_id: id}, function(err, results){
    if (err)
      return callback(err, null);
    else
      return async.map(results, function(profile, callback) {
        Profile.compute(profile, callback);
      }, callback);
  });
};

However, if you're after compositions as well, we don't have any support for that yet :( You'd have to do a Profile.read for each object in results.

Does this answer your question? I know model.compute isn't documented yet, and I'll fix that. If you have a suggestion of a better API for this case, I'm totally open to it.

alank64 commented 10 years ago

This works fine for me!

One suggestion, and once I get a little more experienced with node.js, would be to have...

db.query(cypher, {user_id: id}, {p: Profile}, function(err, results){...

where the 'p' would map to the RETURN values in the query. Another way would be to have a property 'class_type' in every node. Then when evaluating Cypher results, the node results could be cast into the class. Just ideas

Again, thanks for the quick response! Tested your solution above and works great.

jonpacker commented 10 years ago

I definitely see where you're coming from, but that API would mean tightly coupling seraph to seraph-model, and I'm not comfortable with doing that just yet. As it stands, seraph has no concept of seraph-model. And if we did do it that way, would it end up querying for compositions as well? It might be quite an overhead to add.

Something that might be a little more abstract, but a long the same lines, could be like this:

db.queryMap(cypher, {user_id: id}, {p: Profile.compute}, function(err, results) {...

But we're approaching the point here where it's simpler to just make the async.map call in the callback and do it yourself. Unfortunately it's not as simple as casting to another kind of object - objects that you save and read with the Profile model are semantically no different to any other javascript object. There's no way to differentiate them, and there's no way to check if an object is a Profile or not.

I'll have a think about it and see if I can come up with any better API for this use case, I can definitely see how it would be useful. It's also difficult because we need to differentiate between cases when you want to add compositions, and when you only want the computed fields.

For now, I'd suggest sticking with using Profile.compute, as that part of the API will not be changing.