This is an add-on library to https://github.com/andreasronge/neo4j
I was just using mongoid, and it was great because you could throw anything at it and have it work. This is an attempt to bring rails neo4j to the same accomplishment.
Read the source, its educational.
# This gem is alpha. The following doesn't work yet:
# for now, run a git pull and install in vendor/gems
gem install neo4j_helper
Methods are automatically added to Neo4j::Rails::Model. This means once you install the gem, you can't get away from them. If this causes issues, let me know, and maybe we can implement a mixin instead.
Before:
jruby-1.6.6 :003 > p.rels.map {|r| r.inspect }
=> ["#<Neo4j::Rails::Relationship:0x207bfdc3 @start_node=nil, @type=\"\", @properties_before_type_cast={}, @end_node=nil, @properties={}, @_java_rel=#<#<Class:0x23205829>:0x37608a6e>>", "#<Neo4j::Rails::Relationship:0x7689b9bf @start_node=nil, @type=\"\", @properties_before_type_cast={}, @end_node=nil, @properties={}, @_java_rel=#<#<Class:0x23205829>:0x17ff08d>>", "#<#<Class:0x23205829>:0x10febedf>"]
After:
=> ["Post#worse_post: Post 18 --> Post 19", "Post#worse_post: Post 18 --> Post 19", "#<#<Class:0x8ac3714>:0x7a0008e5>"]
# user.rb
property :facebook_credentials, :type => :serialize
Todo: allow indexing and searching on serialized info, like mongoid. (see: group post
Notices a passed-in hash to a relationship, and makes the right node. You can pass a block if it returns a model instance.
# user.rb
has_one(:location).to(Location)
accepts_hash_for :location do |attributes|
Location.new attributes
end
In fact, the above is done automatically with no block, so this is just the same effect:
# user.rb
has_one(:location).to(Location)
accepts_hash_for :location
Model.rels_to(other_node)
- shorthand for Model.rels.to_other
Model.rels2(:rel_type, options = nil)
- new version of #rels, allowing options such as to: and from: (see below)
Model.ensure_relation
- will make create a rel and set attribtues if no rel exists
- will update specified attributes on a rel if one is found
Model.insert_relation(:rel_type, to: new_node)
- Given a single directional linked list of nodes, this will add a node
Allows cypher queries without the all tha paperwork:
# Model#cypher symoblizes keys you
results = self.cypher %Q{
START self = node(#{self.neo_id})
MATCH (user)-[:member_of]-(self)
RETURN user
}
results.map{|row| row[:user]}
And provides good guesses at simple queries. Here's the one above, rewritten:
results = self.cypher.match(%Q{
MATCH (user)-[:member_of]-(self)
}).mapped(:user)
A Cypher has the methods initialize(string), results(), start(string), match(string), where(string), returning(string), limit(string), and mapped(*symbols)
Tuples are a useful tool for packaging the relationships going to a node with the node itself. The goal of the tuple is to allow extremely simple to/from json with rel data and node data combined.
For example:
t = Tuple.new(some_location, current_user.rels_to(some_location))
t.rels # the rels outgoing from user
t.end_node # the user
Note: Currently this relies on acts_as_api, that code should be extracted to an addon to the acts_as_api_gme
Todo: The second goal is to allow easy manipulation and understanding of multiple relationships between two nodes.
Tuples can be used as such:
# return an array of tuples between the user and their goal
User.first.goals.tuples
# update the tuple to the node w/ the given id
User.first.goals.update_tuple_attributes(params[:goal])
Beinning to implement mongoid query syntax
- any_in, also_in, are done
- where has been started
- fulltext takes options too
this comes with a new experimental query builder, for example:
Post.tuples(:from => current_user).order(:updated_at).limit(3).all
Post.tuples(:to => current_user).fulltext(:name => {:all => name.split}).limit(limit).page(page)
- lots of undocumented things (todo: document)
- allow #new as well as #build when setting up relationships
- created the model method: write_attr_in_transaction, which saves you from needing a blocks.
- aliases has_many for has_n
- symbolize keys by default in #props and #attributes
- when #last is not available, it gives a nicer method saying only # first is available.
- #find_by as synonym to #find
TODO: niceties to try and bring the syntax as close as possible to mongoid;
- allow :string to be passed as well as :string, or error
- figure out if both fulltext and exact indices can be made on a single field
- allow .as when setting up has_n and has_one
- allowing the :unique flag to be passed to indexes (like mongoid),
- making the difference between the Lucene query and neo4j traversal more evident, useful, or hidden
- command to automatically add missing indexes
- make it so that when invalid parameters get passed to rels, it pukes, rather than returning a Class
- make Class objects more debuggable
nodes and rels are held cached before persistence:
- can we do anything to make this more evident?
- i.e., inspection either saying post #, or post # cached rel
=> #<Post:0x128d1417 @_relationships={:"Post#posts"=>#<Neo4j::Rails::Relationships::Storage:0x1adb8a22 @rel_class=Neo4j::Rails::Relationship, @target_class=Neo4j::Rails::Model, @incoming_rels=[#<Neo4j::Rails::Relationship:0x21ed22af @start_node=Goal 5, @properties={}, @end_node=#<Post:0x128d1417 ...>, @properties_before_type_cast={}, @type="Post#posts">], @node=#<Post:0x128d1417 ...>, @rel_type=:"Post#posts", @persisted_related_nodes={}, @outgoing_rels=[], @persisted_relationships={}, @persisted_node_to_relationships={}>, :"User#notified_users"=>#<Neo4j::Rails::Relationships::Storage:0x57d437c @rel_class=Notification, @target_class=User, @incoming_rels=[], @node=#<Post:0x128d1417 ...>, @rel_type=:"User#notified_users", @persisted_related_nodes={}, @outgoing_rels=[Notification ], @persisted_relationships={}, @persisted_node_to_relationships={}>}, @changed_attributes={"body"=>nil}, @properties={"body"=>"123"}, @properties_before_type_cast={:body=>"123"}> jruby-1.6.6 :021 > p.save => true jruby-1.6.6 :022 > u.notifications.first => #<Post:0x26cffd3e @_relationships={}, @_java_node=#<#Class:0x63a80928:0x592d9d9a>, @properties={}, @properties_before_type_cast={}>
# before:
self.expand { |n| n._rels.find_all {|r| r[:latest] == true && r._end_node[:_classname] == :goal } }.to_a
# ideal query:
self.rels.in_java.where({:latest => true}).end_node(:classname => :goal)
# or maybe
rel_scope :goal_history, in_ruby.where({:latest => true}).targeting(:goal)
Once you've made your great commits
- Fork
- Pull
- Push
- Pull request
Peter Ehrlich @ehrlicp
(This rdoc written with Mou, a sweet markdown editor from @chenluois)