Feel free to skim it or not, just don't want it to get lost so that no one else 
can learn from that or contribute alternative solutions.

Cheers

Michael

1) All nodes are equal in Neo4j if I am correct, so if I have different types 
myself then I just create a node_type property with a text string to 
differentiate between f.ex. users and jobs. Is this the correct way to handle 
this?

2) Complex data structures, I need to store complex data structures in Neo4j, 
think about a whole CV like LinkedIn. When I create or update nodes can U 
pass-in this complex data structure as a whole? F.ex. below here could I add a 
parameter CV that contains a Hash where some values are Hashes themselves 
again? Or can  should I use JSON as a value?
Neography::Node.create("age" => 31, "name" => "Max")

3) I am currently creating my own models (Rails based web app) where the models 
themselves are not so much used like in Rails, they are only a nice way to 
collect logic in one place. F.ex. I could have Employee.create_new hash which 
internally will use neography to create a new employee and return the created 
one as a hash. Is this a good way to thank about the problem, or have other 
patterns emerged? 

1) There are several strategies, marking the nodes with certain properties is 
one, others are - linking the nodes to a "category" node so that they can be 
easily accessed from there, or putting them in a dedicated index for quick 
lookup.

2) Nodes only have primitive properties right now. To store hashes or more 
complex document structures you use other nodes and relationships between them. 
That should be quite easy to put in a helper method.
i.e. if the inner property is a hash take its property name and instead of 
making a property out of that one, create a relationship with this name to a 
node which has the hash keys + values as properties (and recursively so).

Fortunately it is easy with ruby to just reopen the Node class and add those 
convenience methods. I did that as well. (Code below)

3) I create my own Domain objects for the Neo4j-Hosting Domain and just have 
them get a Neography node as intitializer param, then you can delegate all 
store-methods to this node or its node.neo_server and have the other domain 
code directly in the object (code also below). I'm also in the process of 
exploring this whole remote-rest-store domain modelling, so we can certainly 
learn a lot from each other.

Mark you can still do  your uniqueness constraint of the email with the index. 
I would have written the same as Jim, put the E-Mail addresses in a string 
array field, but be sure to index them not as an array but individually.

Regarding your other questions of reading structures. Traversing is crazy fast 
in Neo4j. So what you do is not a manual navigation over the relationships but:
- you get your start node (i.e. the one being the person) from the index or 
from another traversal. (you can see that node like a root entity).

Then you specify a traversal description. I.e. which relationships to traverse, 
how deep etc. And then let the neo4j traverser do the work and have you return 
all the paths to the nodes that are included in your traversal - i.e. your 
whole document == aggregate structure.

Then in the client you pull those paths into your domain model and have it 
ready for processing/displaying whatever.

For hints for the traversal either look at the code of the previous email or 
that a quick look at the neo4j-imdb-app sourcecode at github: 
https://github.com/jexp/heroku-neo4j-example

Cheers

Michael

Ok one more question for modeling the data: Say a user can have multiple e-mail 
addresses what is the best way to model this? Have a single property containing 
a list with addresses or let
email_address be its own entity and create relations between the user and 
e-mail_address? The email address will be used to search for a user f.ex. so I 
rather have Neo4j being able to
determine if it already exists.

 -Mark


There's nothing in Neo4j that determines this one way or another. If email 
addresses are first class citizens in your domain, then they should be nodes. 
Otherwise make them properties.

For instane, if you want to be able to assert that two users are actually the 
same person because they share an email address, then make the email address a 
node. Otherwise it's a property.

Jim

I am using e-mail address as a way to select a user, but I didn't think of it 
as a first class citizen. A user can have multiple e-mail addresses and in 
cases non. The thing I am not sure
about is how to select a user by e-mail address. Can Neo4j have an Array as 
property type, using the ReST API and select a node if it the array contains 
the filter value?

I am starting to realize that how I think about my data needs to be a bit 
different from f.ex. MongoDB or CouchDB. In many ways I see great 
possibilities, but need to get my head around
these fundamental things :)

-Mark


It's possible to index node properties, that gives you effectively what Mongo 
does. Arrays are supported, you just pass in a JSON array:

{ "email_addresses" : ["j...@webber.name", "j...@jimwebber.org", 
"j...@neotechnology.com"]}

The question you need to answer is: is there any value in the relationship 
between a user and their email address(es)?

Jim

Ah cool, that is what I was wondering about how easy Neo could index properties 
like an array. Because this is a border case (I might actually promote e-mail 
address to a first class citizen for uniqueness check), but there are other 
cases where I do not see the need to do this. Then it is good to know that Neo 
will handle that easily.

How much is the ReST API behind the direct Java way?

-Mark

Regarding the second - information about those concepts is distributed over 
blog posts, wiki entries and mailing list posts. We certainly need more docs 
from the users/developers point of
view, e.g. on how to model your domain in a graph.

What I've just found (but that is by far not enough: 
http://wiki.neo4j.org/content/Domain_Modeling_Gallery)

http://wiki.neo4j.org/content/Design_Guide
http://stackoverflow.com/questions/1000162/has-anyone-used-graph-based-databases-http-neo4j-org

http://www.build47.com/posts/neo4j-presentation-at-twitter-headquaters/
http://www.slideshare.net/directi/neo4j-and-the-benefits-of-graph-dbs-3-3325734

I also would like to discuss how one could map the DDD terms onto a graph 
database and where it is fitting and where not.

Two last points: don't underestimate relationships, they are full first level 
citizens and go for traversals rather than manually navigating along the 
relationships.

node_ext.rb

require 'rubygems'
require 'neography'

module Neography
  class Node 
    class << self
      def create_and_index(data, to_index)
        node = Neography::Node.create(data)
        return node unless to_index
        to_index.each do |index,names| 
          names.each do | prop |
            
node.neo_server.add_node_to_index(index,prop,data[prop],node.neo_id) 
          end
        end
        node
      end

      def find(index, prop, value)
        res = Neography::Rest.new.get_node_index(index,prop,value)
        return nil unless res
        Neography::Node.load(res.first)
      end
    end
  end
  class Rest
    def get_type(type)
        case type
          when :node, "nodes", :nodes, "nodes"
            "node"
          when :relationship, "relationship", :relationships, "relationships"
            "relationship"
          when :path, "path", :paths, "paths"
            "path"
          when :fullpath, "fullpath", :fullpaths, "fullpaths"
            "fullpath"
          else
            "node"
        end
      end
  end
end


domain.rb
require 'node_ext'    

# todo I want to be able to filter for properties with the REST api w/o 
reverting to JS
module Gateway
  class Provider
  def initialize(node) 
    @node = node
  end

  def to_s 
    "#{@node.name} (#{@node.neo_id})"
  end
  
  class << self
    def get(name)
      node = Neography::Node.find(:providers,:name,name)
      return nil unless node
      Provider.new(node)
    end
  
    def add(name) 
       provider = Provider.get(name)
       return provider if provider
       node = Neography::Node.create_and_index({:name => name}, :providers => 
[:name])
       return nil unless node
       Provider.new(node)
    end
  end

  def plans
    @node.outgoing(:active_plan).collect
  end
  def plan(name)
    plans.find { |plan| plan.name == name }
  end

  def users
    rels.incoming(:account).collect
  end
  def userFor(account)
    users = @node.incoming(:account).filter(" position.lastRelationship() != 
null && position.lastRelationship().getProperty('account') == '#{account}';")
    return User.new(users.first) if users && !users.empty?
    user = Neography::Node.create_and_index({:name => account, :email => 
account}, :users => [:email, :name ])
    @node.neo_server.create_relationship(:account, user, @node, {:account => 
account })
    User.new(user)
  end

  def new_hash(size = 8)
    Digest::SHA1.hexdigest(rand.to_s)[0..size]
  end

  def databaseFor(user, plan)
    begin 
      id = new_hash
    end while !Node.find(:databases, :id, id)
    db = Neography::Node.create_and_index({
        :id => id, :login => new_hash, :password => new_hash,
        :state => :creating
    }, :databases => [:id])
    
    user.outgoing(:active_database) << db
    db.outgoin(:has_plan) << plan

    Database.new(db)
  end
end
end




_______________________________________________
Neo4j mailing list
User@lists.neo4j.org
https://lists.neo4j.org/mailman/listinfo/user

Reply via email to