neo4j 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/CHANGELOG +141 -0
  2. data/CONTRIBUTORS +15 -0
  3. data/Gemfile +3 -0
  4. data/README.rdoc +2015 -0
  5. data/lib/neo4j.old/batch_inserter.rb +144 -0
  6. data/lib/neo4j.old/config.rb +138 -0
  7. data/lib/neo4j.old/event_handler.rb +73 -0
  8. data/lib/neo4j.old/extensions/activemodel.rb +158 -0
  9. data/lib/neo4j.old/extensions/aggregate.rb +12 -0
  10. data/lib/neo4j.old/extensions/aggregate/aggregate_enum.rb +40 -0
  11. data/lib/neo4j.old/extensions/aggregate/ext/node_mixin.rb +69 -0
  12. data/lib/neo4j.old/extensions/aggregate/node_aggregate.rb +8 -0
  13. data/lib/neo4j.old/extensions/aggregate/node_aggregate_mixin.rb +331 -0
  14. data/lib/neo4j.old/extensions/aggregate/node_aggregator.rb +216 -0
  15. data/lib/neo4j.old/extensions/aggregate/node_group.rb +43 -0
  16. data/lib/neo4j.old/extensions/aggregate/prop_group.rb +30 -0
  17. data/lib/neo4j.old/extensions/aggregate/property_enum.rb +24 -0
  18. data/lib/neo4j.old/extensions/aggregate/props_aggregate.rb +8 -0
  19. data/lib/neo4j.old/extensions/aggregate/props_aggregate_mixin.rb +31 -0
  20. data/lib/neo4j.old/extensions/aggregate/props_aggregator.rb +80 -0
  21. data/lib/neo4j.old/extensions/find_path.rb +117 -0
  22. data/lib/neo4j.old/extensions/graph_algo.rb +1 -0
  23. data/lib/neo4j.old/extensions/graph_algo/all_simple_paths.rb +133 -0
  24. data/lib/neo4j.old/extensions/graph_algo/neo4j-graph-algo-0.3.jar +0 -0
  25. data/lib/neo4j.old/extensions/reindexer.rb +104 -0
  26. data/lib/neo4j.old/extensions/rest.rb +21 -0
  27. data/lib/neo4j.old/extensions/rest/rest.rb +336 -0
  28. data/lib/neo4j.old/extensions/rest/rest_mixin.rb +193 -0
  29. data/lib/neo4j.old/extensions/rest/server.rb +50 -0
  30. data/lib/neo4j.old/extensions/rest/stubs.rb +141 -0
  31. data/lib/neo4j.old/extensions/rest_master.rb +34 -0
  32. data/lib/neo4j.old/extensions/rest_slave.rb +31 -0
  33. data/lib/neo4j.old/extensions/tx_tracker.rb +392 -0
  34. data/lib/neo4j.old/indexer.rb +187 -0
  35. data/lib/neo4j.old/jars.rb +6 -0
  36. data/lib/neo4j.old/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  37. data/lib/neo4j.old/jars/neo4j-kernel-1.0.jar +0 -0
  38. data/lib/neo4j.old/mixins/java_list_mixin.rb +139 -0
  39. data/lib/neo4j.old/mixins/java_node_mixin.rb +205 -0
  40. data/lib/neo4j.old/mixins/java_property_mixin.rb +169 -0
  41. data/lib/neo4j.old/mixins/java_relationship_mixin.rb +60 -0
  42. data/lib/neo4j.old/mixins/migration_mixin.rb +157 -0
  43. data/lib/neo4j.old/mixins/node_mixin.rb +249 -0
  44. data/lib/neo4j.old/mixins/property_class_methods.rb +265 -0
  45. data/lib/neo4j.old/mixins/rel_class_methods.rb +167 -0
  46. data/lib/neo4j.old/mixins/relationship_mixin.rb +103 -0
  47. data/lib/neo4j.old/neo.rb +247 -0
  48. data/lib/neo4j.old/node.rb +49 -0
  49. data/lib/neo4j.old/reference_node.rb +15 -0
  50. data/lib/neo4j.old/relationship.rb +85 -0
  51. data/lib/neo4j.old/relationships/decl_relationship_dsl.rb +164 -0
  52. data/lib/neo4j.old/relationships/has_list.rb +101 -0
  53. data/lib/neo4j.old/relationships/has_n.rb +129 -0
  54. data/lib/neo4j.old/relationships/node_traverser.rb +138 -0
  55. data/lib/neo4j.old/relationships/relationship_dsl.rb +149 -0
  56. data/lib/neo4j.old/relationships/traversal_position.rb +50 -0
  57. data/lib/neo4j.old/relationships/wrappers.rb +51 -0
  58. data/lib/neo4j.old/search_result.rb +72 -0
  59. data/lib/neo4j.old/transaction.rb +254 -0
  60. data/lib/neo4j.old/version.rb +3 -0
  61. data/lib/neo4j.rb +50 -0
  62. data/lib/neo4j/config.rb +137 -0
  63. data/lib/neo4j/database.rb +43 -0
  64. data/lib/neo4j/equal.rb +22 -0
  65. data/lib/neo4j/event_handler.rb +91 -0
  66. data/lib/neo4j/index.rb +157 -0
  67. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  68. data/lib/neo4j/jars/lucene-core-2.9.2.jar +0 -0
  69. data/lib/neo4j/jars/lucene-core-3.0.1.jar +0 -0
  70. data/lib/neo4j/jars/neo4j-index-1.1.jar +0 -0
  71. data/lib/neo4j/jars/neo4j-kernel-1.1.1.jar +0 -0
  72. data/lib/neo4j/jars/neo4j-kernel-1.1.jar +0 -0
  73. data/lib/neo4j/jars/neo4j-lucene-index-0.1-20100916.085626-67.jar +0 -0
  74. data/lib/neo4j/mapping/class_methods/index.rb +21 -0
  75. data/lib/neo4j/mapping/class_methods/property.rb +139 -0
  76. data/lib/neo4j/mapping/class_methods/relationship.rb +96 -0
  77. data/lib/neo4j/mapping/class_methods/rule.rb +135 -0
  78. data/lib/neo4j/mapping/decl_relationship_dsl.rb +151 -0
  79. data/lib/neo4j/mapping/has_n.rb +117 -0
  80. data/lib/neo4j/mapping/node_mixin.rb +70 -0
  81. data/lib/neo4j/neo4j.rb +65 -0
  82. data/lib/neo4j/node.rb +82 -0
  83. data/lib/neo4j/node_mixin.rb +4 -0
  84. data/lib/neo4j/node_relationship.rb +60 -0
  85. data/lib/neo4j/node_traverser.rb +141 -0
  86. data/lib/neo4j/property.rb +72 -0
  87. data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
  88. data/lib/neo4j/rails/model.rb +210 -0
  89. data/lib/neo4j/rails/railtie.rb +16 -0
  90. data/lib/neo4j/rails/transaction.rb +29 -0
  91. data/lib/neo4j/rails/value.rb +43 -0
  92. data/lib/neo4j/relationship.rb +88 -0
  93. data/lib/neo4j/relationship_traverser.rb +57 -0
  94. data/lib/neo4j/to_java.rb +17 -0
  95. data/lib/neo4j/transaction.rb +69 -0
  96. data/lib/neo4j/version.rb +3 -0
  97. data/neo4j.gemspec +30 -0
  98. metadata +243 -0
data/CHANGELOG ADDED
@@ -0,0 +1,141 @@
1
+ == 0.4.4 / 2010-08-01
2
+ * Fixed bug on traversing when using the RelationshipMixin (#121)
3
+ * BatchInserter and JRuby 1.6 - Fix iteration error with trying to modify in-place hash
4
+
5
+ == 0.4.3 / 2010-04-10
6
+ * Fixed .gitignore - make sure that we do not include unnecessarily files like neo4j databases. Release 0.4.2 contained test data.
7
+ * Added synchronize around Index.new so that two thread can't modify the same index at the same time.
8
+
9
+ == 0.4.2 / 2010-04-08
10
+ * No index on properties for the initialize method bug (#116)
11
+ * Tidy up Thread Synchronization in Lucene wrapper - lucene indexing performance improvement (#117)
12
+ * Permission bug loading neo4j jar file (#118)
13
+ * Spike: Make NodeMixin ActiveModel complient - experimental (#115)
14
+
15
+ == 0.4.1 / 2010-03-11
16
+ * Migrations (#108)
17
+ * BatchInserter (#111)
18
+ * Neo4j::Relationship.new should take a hash of properties (#110)
19
+ * Upgrade to neo4j-1.0 (#114)
20
+ * Bigfix: has_one should replace old relationship (#106)
21
+ * Bugfix: custom accessors for NodeMixin#update (#113)
22
+ * Bugfix: Indexed properties problem on extented ruby classes critical "properties indexer" (#112)
23
+
24
+ == 0.4.0 / 2010-02-06
25
+ * Performance improvements and Refactoring: Use and Extend Neo4j Java Classes (#97)
26
+ * Support for Index and Declaration of Properties on Relationships (#91)
27
+ * Upgrade to neo4j-1.0 rc (#100)
28
+ * All internal properties should be prefix with a '_',0.4.0 (#105)
29
+ * Generate relationship accessor methods for declared has_n and has_one relationships (#104)
30
+ * New way of creating relationship - Neo4j::Relationship.new (#103)
31
+ * Neo4j#init_node method should take one or more args (#98)
32
+ * Namespaced relationships: has_one...from using the wrong has_n...to(#92)
33
+ * Neo4j::NodeMixin and Neo4j::Node should allow a hash for initialization (#99)
34
+
35
+ == 0.3.3 / 2009-11-25
36
+ * Support for a counter property on has_lists (#75)
37
+ * Support for Cascade delete. On has_n, had_one and has_list (#81)
38
+ * NodeMixin#all should work with inheritance - Child classes should have a relationship of their own. (#64)
39
+ * Support for other lucene analyzer then StandardAnalyzer (#87)
40
+ * NodeMixin initialize should accept block like docs (#82)
41
+ * Add incoming relationship should work as expected: n1.relationships.incoming(:foo) << n2 (#80)
42
+ * Delete node from a has_list relationship should work as expected (#79)
43
+ * Improve stacktraces (#94)
44
+ * Removed sideeffect of rspecs (#90)
45
+ * Add debug method on NodeMixin to print it self (#88)
46
+ * Removed to_a method (#73)
47
+ * Upgrade to neo4j-1.0b10 (#95)
48
+ * Upgrade to lucene 2.9.0 (#83)
49
+ * Refactoring: RSpecs (#74)
50
+ * Refactoring: aggregate each, renamed to property aggregator (#72)
51
+ * BugFix: neo4j gem cannot be built from the source (#86)
52
+ * BugFix: Neo4j::relationship should not raise Exception if there are no relationships (#78)
53
+
54
+ == 0.3.2 / 2009-09-17
55
+ * Added support for aggregating nodes (#65)
56
+ * Wrapped Neo4j GraphAlgo AllSimplePath (#70)
57
+ * Added traversal with traversal position (#71)
58
+ * Removed DynamicAccessors mixin, replaced by [] operator (#67)
59
+ * Impl Neo4j.all_nodes (#69)
60
+ * Upgrated Neo4j jar file to 1.0-b9
61
+ * The Neo4j#relationship method now allows a filter parameter (#66)
62
+ * Neo4j.rb now can read database not created by Neo4j.rb - does not require classname property (#63)
63
+ * REST - added an "all" value for the depth traversal query parameter (#62)
64
+ * REST - Performance improvments using the Rest Mixin (#60)
65
+
66
+ == 0.3.1 / 2009-07-25
67
+ * Feature, extension - find path between given pair of nodes (#58)
68
+ * Fix a messy exception on GET /nodes/UnknownClassName (#57)
69
+ * Bug - exception on GET /nodes/classname/rel if rel is a has_one relationship (#56)
70
+ * Bug: GET /nodes/classname missing out nodes with no properties (#55)
71
+ * Bug: Lucene sorting caused exception if there were no documents (#54)
72
+ * Bug: reindexer fails to connect nodes to the IndexNode (#53)
73
+
74
+ == 0.3.0 / 2009-06-25
75
+ * Neo4j should track node changes
76
+ * RESTful support for lucene queries, sorting and paging
77
+ * RESTful support for Relationships
78
+ * RESTful support for Node and properties
79
+ * Experimental support for Master-Slave Replication via REST
80
+ * RESTful Node representation should contain hyperlinks to relationships
81
+ * Added some handy method like first and empty? on relationships
82
+ * Use new neo4j: neo-1.0-b8
83
+ * Add an event handler for create/delete nodes start/stop neo, update property/relationship
84
+ * The NodeMixin should behave like a hash, added [] and []= methods
85
+ * Support list topology - has_list and belongs_to_list Neo4j::NodeMixin Classmethods
86
+ * Should be possible to add relationships without declaring them (Neo4j#relationships.outgoing(:friends) << node)
87
+ * Neo4j extensions file structure, should be easy to create your own extensions
88
+ * Rename relation to relationship (Neo4j::Relations => Neo4j::Relationships, DynamicRelation => Relationship) [data incompatible change]
89
+ * Auto Transaction is now optional
90
+ * Setting Float properties fails under JRuby1.2.0
91
+ * Bug: Indexing relationships does not work
92
+ * Make the ReferenceNode include Neo4j::NodeMixin
93
+ * Added handy Neo4j class that simply includes the Neo4j::NodeMixin
94
+ * Neo4j::IndexNode now holds references to all nodes (Neo4j.ref_node -> Neo4j::IndexNode -> ...)
95
+
96
+
97
+ == 0.2.1 / 2009-03-15
98
+ * Refactoring of lucene indexing of the node space (28)
99
+ * Fixed bug on Neo4j::Nodemixin#property? (#22)
100
+
101
+
102
+ == 0.2.0 / 2009-01-20
103
+ * Impl. Neo4j::Node#traverse - enables traversal and filtering using TraversalPosition info (#17,#19)
104
+ * Impl. traversal to any depth (#15)
105
+ * Impl. traversal several relationships type at the same time (#16)
106
+ * Fixed a Lucene timezone bug (#20)
107
+ * Lots of refactoring of the neo4j.rb traversal code and RSpecs
108
+
109
+ == 0.1.0 / 2008-12-18
110
+ * Property can now be of any type (and not only String, Fixnum, Float)
111
+ * Indexing and Query with Date and DateTime
112
+ * YARD documentation
113
+ * Properties can be removed
114
+ * A property can be set to nil (it will then be removed).
115
+
116
+ == 0.0.7 / 2008-12-10
117
+ * Added method to_param and methods on the value object needed for Ruby on Rails
118
+ * Impl. update from a value object/hash for a node
119
+ * Impl. generation of value object classes/instances from a node.
120
+ * Refactoring the Transaction handling (reuse PlaceboTransaction instances if possible)
121
+ * Removed the need to start and stop neo. It will be done automatically when needed.
122
+
123
+
124
+ == 0.0.6 / 2008-12-03
125
+ * Removed the configuration from the Neo4j.start method. Now exist in Neo4j::Config and Lucene::Config.
126
+ * Implemented sort_by method.
127
+ * Lazy loading of search result. Execute the query and load the nodes only if needed.
128
+ * Added support to use lucene query language, example: Person.find("name:foo AND age:42")
129
+ * All test now uses RAM based lucene indexes.
130
+
131
+ == 0.0.5 / 2008-11-17
132
+ * Supports keeping lucene index in memory instead of on disk
133
+ * Added support for lucene full text search
134
+ * Fixed so neo4j runs on JRuby 1.1.5
135
+ * Implemented support for reindex all instances of a node class. This is needed if the lucene index is kept in memory or if the index is changed.
136
+ * Added ReferenceNode. All nodes now have a relationship from this reference node.
137
+ * Lots of refactoring
138
+ * Added the IMDB example. It shows how to create a neo database, lucene queries and node traversals.
139
+
140
+ == 0.0.4 / 2008-10-23
141
+ * First release to rubyforge
data/CONTRIBUTORS ADDED
@@ -0,0 +1,15 @@
1
+ Maintainer:
2
+ Andreas Ronge <andreas dot ronge at gmail dot com>
3
+
4
+ Contributors:
5
+ * Martin Kleppmann
6
+ * Peter Neubauer
7
+ * Jan-Felix Wittmann
8
+ * Marius Mårnes Mathiesen
9
+ * Bert Fitié
10
+ * Jan Berkel
11
+ * David Beckwith
12
+ * Johny Ho
13
+ * Carlo Cabanilla
14
+ * Anders Janmyr
15
+ * Nick Sieger
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :gemcutter
2
+
3
+ gemspec
data/README.rdoc ADDED
@@ -0,0 +1,2015 @@
1
+ = Neo4j.rb
2
+
3
+ Neo4j.rb is a graph database for JRuby.
4
+
5
+ It provides:
6
+ * Mapping of ruby objects to nodes in networks rather than in tables.
7
+ * Dynamic, and schema-free - no need to declare nodes/properties/relationships in advance.
8
+ * Storage of ruby object to a file system.
9
+ * Fast traversal of relationships between nodes in a huge node space.
10
+ * Transaction with rollbacks support.
11
+ * Indexing and querying of ruby objects.
12
+ * Migration and BatchInserter support
13
+ * Can be used instead of ActiveRecord in Ruby on Rails or Merb
14
+ * Can be accessible as REST resources.
15
+
16
+ It uses two powerful and mature Java libraries:
17
+ * Neo4J (http://www.neo4j.org/) - for persistence and traversal of the graph
18
+ * Lucene (http://lucene.apache.org/java/docs/index.html) for querying and indexing.
19
+
20
+ === Status
21
+ * There are over 500 RSpecs.
22
+ * Has been tested with rails applications, used Neo4j.rb instead of ActiveRecord
23
+
24
+ === Project information
25
+ * GitHub - http://github.com/andreasronge/neo4j/tree/master
26
+ * Issue Tracking - http://neo4j.lighthouseapp.com
27
+ * Twitter - http://twitter.com/ronge
28
+ * IRC - #neo4j @ irc.freenode.net
29
+ * API Documentation - http://neo4j.rubyforge.org/ (of the released version)
30
+ * Source repo - git://github.com/andreasronge/neo4j.git
31
+ * Mailing list - http://groups.google.com/group/neo4jrb (neo4jrb@googlegroups.com)
32
+
33
+ === Presentation Materials and other URLs
34
+ * Ruby Manor 2008 - Jonathan Conway: http://jaikoo.com/assets/presentations/neo4j.pdf
35
+ * Nordic Ruby 2010 (upcoming May 21-23) http://nordicruby.org/speakers#user_29
36
+ * Neo4j wiki - http://wiki.neo4j.org/content/Main_Page (check the guidelines and domain modeling gallery pages)
37
+
38
+ === Contributing
39
+
40
+ Have you found a bug, need help or have a patch ?
41
+ Just clone neo4j.rb and send me a pull request or email me.
42
+ Do you need help - send me an email (andreas.ronge at gmail dot com).
43
+ Please also check/add issues at lighthouse, http://neo4j.lighthouseapp.com
44
+
45
+ === License
46
+ * Neo4j.rb - MIT, see the LICENSE file http://github.com/andreasronge/neo4j/tree/master/LICENSE.
47
+ * Lucene - Apache, see http://lucene.apache.org/java/docs/features.html
48
+ * Neo4j - Dual free software/commercial license, see http://neo4j.org/
49
+
50
+ === Content
51
+ This page contains the following information:
52
+ * Installation guide
53
+ * Three Minute Tutorial
54
+ * Ten Minute Tutorial
55
+ * Neo4j API Documentation
56
+ * Extensions: REST (see Neo4j::RestMixin) and find_path (Neo4j::GraphAlgo::AllSimplePaths)
57
+ * Performance issues
58
+ * Ruby on Rails with Neo4j.rb
59
+ * Lucene API Documentation
60
+
61
+ There are also some complete examples in the example folder
62
+ * admin - an incomplete admin web gui for the Neo4j.rb/REST interface
63
+ * railway - an example of a railway network application
64
+ * imdb - an example of a Neo4j database consisting of movies, role and actors nodes/relationships (over 18000 nodes).
65
+ * rest - an example how to expose Neo4j nodes as REST resources
66
+ * Ruby on Rails - see http://github.com/andreasronge/neo4j-rails-example/tree/master or http://github.com/sashaagafonoff/peoplemap
67
+ * Rails3/ActiveModel integration see: http://github.com/nicksieger/neo4j-rails
68
+
69
+ For most of the examples below there are RSpecs available, check the test/neo4j/readme_spec.rb file.
70
+
71
+ == Installation
72
+
73
+ To install it:
74
+
75
+ jruby -S gem install neo4j
76
+
77
+ To install from the latest source:
78
+ git clone git://github.com/andreasronge/neo4j.git
79
+ cd neo4j
80
+ gem install bundler # only needed for development
81
+ bundle install # to install all test dependencies needed for development
82
+ gem build rspec-apigen.gemspec
83
+ gem install neo4j-x.y.z.gem
84
+
85
+ This has been verified to work on JRuby 1.5.2
86
+
87
+ ==== Running all RSpecs
88
+
89
+ To check that neo4j.rb is working:
90
+
91
+ cd neo4j # the folder containing the Rakefile
92
+ rake # you may have to type jruby -S rake depending how you installed JRuby
93
+
94
+
95
+ = Three Minute Tutorial
96
+
97
+ Neo node space consists of three basic elements: nodes, relationships that connect nodes and properties attached
98
+ to both nodes and relationships. All relationships have a type, for example if the node space represents
99
+ a social network, a relationship type could be KNOWS. If a relationship of the type KNOWS connects two nodes,
100
+ that probably represents two people that know each other. A lot of the semantics, the meaning, of a node space
101
+ is encoded in the relationship types of the application.
102
+
103
+ === Creating Nodes
104
+
105
+ Example of creating a Neo4j::Node
106
+
107
+ require "rubygems"
108
+ require 'neo4j'
109
+
110
+ Neo4j::Transaction.run do
111
+ node = Neo4j::Node.new
112
+ end
113
+
114
+ === Transactions
115
+
116
+ Almost all Neo4j operation must be wrapped in a transaction as shown above.
117
+ In all the following examples we assume that the operations are inside an Neo4j transaction.
118
+ There are two ways of creating transaction - in a block or the Transaction.new method
119
+
120
+ Using a block:
121
+
122
+ Neo4j::Transaction.run do
123
+ # neo4j operations goes here
124
+ end
125
+
126
+ Using the Neo4j::Transaction#new and Neo4j::Transaction#finish methods:
127
+
128
+ Neo4j::Transaction.new
129
+ # neo4j operations goes here
130
+ Neo4j::Transaction.finish
131
+
132
+ === Properties
133
+
134
+ Example of setting properties
135
+
136
+ Neo4j::Node.new :name=>'foo', :age=>123, :hungry => false, 4 => 3.14
137
+ # which is same as the following:
138
+ node = Neo4j::Node.new
139
+ node[:name] = 'foo'
140
+ node[:age] = 123
141
+ node[:hungry] = false
142
+ node[4] = 3.14
143
+ node[:age] # => 123
144
+
145
+ === Creating Relationships
146
+ Example of creating an outgoing Neo4j::Relationship from node1 to node2 of type friends
147
+
148
+ node1 = Neo4j::Node.new
149
+ node2 = Neo4j::Node.new
150
+ Neo4j::Relationship.new(:friends, node1, node2)
151
+
152
+ # which is same as
153
+ node1.rels.outgoing(:friends) << node2
154
+
155
+ === Accessing Relationships
156
+ Example of getting relationships
157
+
158
+ node1.rels.empty? # => false
159
+
160
+ # The rels method returns an enumeration of relationship objects.
161
+ # The nodes method on the relationships returns the nodes instead.
162
+ node1.rels.nodes.include?(node2) # => true
163
+
164
+ node1.rels.first # => the first relationship this node1 has which is between node1 and node2 of any type
165
+ node1.rels.nodes.first # => node2 first node of any relationship type
166
+ node2.rels.incoming(:friends).nodes.first # => node1 first node of relationship type 'friends'
167
+ node2.rels.incoming(:friends).first # => a relationship object between node1 and node2
168
+
169
+ === Properties on Relationships
170
+ Example of setting properties on relationships
171
+
172
+ rel = node1.rels.outgoing(:friends).first
173
+ rel[:since] = 1982
174
+ node1.rels.first[:since] # => 1982 (there is only one relationship defined on node1 in this example)
175
+
176
+ = Ten Minute Tutorial
177
+
178
+ === Creating a Model
179
+
180
+ The following example specifies how to map a Neo4j node to a Ruby Person instance.
181
+
182
+ require "rubygems"
183
+ require "neo4j"
184
+
185
+ class Person
186
+ include Neo4j::NodeMixin
187
+
188
+ # define Neo4j properties
189
+ property :name, :salary, :age, :country
190
+
191
+ # define an one way relationship to any other node
192
+ has_n :friends
193
+
194
+ # adds a Lucene index on the following properties
195
+ index :name, :salary, :age, :country
196
+ end
197
+
198
+ Neo properties and relationships are declared using the 'property' and 'has_n'/'has_one' NodeMixin class method.
199
+ Adding new types of properties and relationships can also be done without
200
+ declaring those properties/relationships by using the operator '[]' on Neo4j::NodeMixin and the
201
+ '<<' on the Neo4j::Relationships::RelationshipTraverser.
202
+
203
+ By using the NodeMixin and by declaring properties and indices, all instances of the Person class can now be stored in
204
+ the Neo4j node space and be retrieved/queried by traversing the node space or performing Lucene queries.
205
+
206
+ A Lucene index will be updated when the name or salary property changes.
207
+
208
+ === Creating a node
209
+
210
+ Creating a Person node instance
211
+
212
+ person = Person.new
213
+
214
+ === Setting properties
215
+
216
+ Setting a property:
217
+
218
+ person.name = 'kalle'
219
+ person.salary = 10000
220
+
221
+ You can also set this (or any property) when you create the node:
222
+
223
+ person = Person.new :name => 'kalle', :salary => 10000, :foo => 'bar'
224
+
225
+
226
+ === Properties and the [] operator
227
+ Notice that it is not required to specify which attributes should be available on a node. Any attributes can be
228
+ set using the [] operator. Declared properties set an expectation, not an requirement. It can be used for documenting your model objects and
229
+ catching typos.
230
+
231
+ Example:
232
+
233
+ person['an_undefined_property'] = 'hello'
234
+
235
+ So, why declare properties in the class at all? By declaring a property in the class, you get the sexy dot notation.
236
+ But also, if you declare a Lucene index on the declared property and update the value, then the Lucene index will
237
+ automatically be updated. The property declaration is required before declaring an index on the property.
238
+
239
+ === Relationships
240
+ Like properties, relationships do not have to be defined using has_n or has_one for a class.
241
+ A relationship can be added at any time on any node.
242
+
243
+ Example:
244
+
245
+ person.rels.outgoing(:best_friends) << other_node
246
+ person.rels.outgoing(:best_friends).first.end_node # => other_node (if there is only one relationship of type 'best_friends' on person)
247
+ # the line above can also be written as below - take the first outgoing relationship:
248
+ person.rel(:best_friends).end_node
249
+
250
+
251
+ === Finding Nodes and Queries
252
+
253
+ There are three ways of finding/querying nodes in Neo4j:
254
+ 1. by traversing the graph
255
+ 2. by using Lucene queries
256
+ 3. using the unique neo4j id (Neo4j::NodeMixin#neo_id).
257
+
258
+ When doing a traversal one starts from a node and traverses one or more relationships (one or more levels deep).
259
+ This start node can be either the reference node which is always found (Neo4j#ref_node) or by finding a start
260
+ node from a Lucene query.
261
+
262
+ === Lucene Queries
263
+
264
+ There are different ways to write Lucene queries.
265
+ Using a hash:
266
+ Person.find (:name => 'kalle', :salary => 20000..30000) # find people with name kalle and age between 20 and 30
267
+
268
+ or using the Lucene query language:
269
+
270
+ Person.find("name:kalle AND salary:[10000 TO 30000]")
271
+
272
+ The Lucene query language supports wildcard, grouping, boolean, fuzzy queries, etc...
273
+ For more information see: http://lucene.apache.org/java/2_4_0/queryparsersyntax.html
274
+
275
+ === Sorting, example
276
+
277
+ Person.find(:age => 25).sort_by(:salary)
278
+ Person.find(:age => 25).sort_by(Lucene::Desc[:salary], Lucene::Asc[:country])
279
+ Person.find(:age => 25).sort_by(Lucene::Desc[:salary, :country])
280
+
281
+ === Search Results
282
+
283
+ The query is not performed until the search result is requested.
284
+ Example of using the search result.
285
+
286
+ res = Person.find(:name => 'kalle')
287
+ res.size # => 10
288
+ res.each {|x| puts x.name}
289
+ res[0].name = 'sune'
290
+
291
+ === Creating a Relationships
292
+
293
+ Since we declared a relationship in the example above with <tt>has_n :friends</tt> (see Neo4j::RelClassMethods#has_n) we
294
+ can use the generated methods <tt>Person#friends</tt> and <tt>Person#friends_rels</tt>
295
+ The <tt>friends_rels</tt> method is used to access relationships and the <tt>Person#friends</tt>
296
+ method for accessing nodes.
297
+
298
+ Adding a relationship between two nodes:
299
+
300
+ person2 = Person.new
301
+ person.friends << person2
302
+
303
+ The person.friends returns an object that has a number of useful methods (it also includes the Enumerable mixin).
304
+ Example
305
+
306
+ person.friends.empty? # => false
307
+ person.friends.first # => person2
308
+ person.friends.include?(person2) # => true
309
+
310
+
311
+ === Deleting a Relationship
312
+
313
+ To delete the relationship between person and person2:
314
+
315
+ person.friends_rels.first.del
316
+
317
+ If a node is deleted then all its relationship will also be deleted
318
+ Deleting a node is performed by using the delete method:
319
+
320
+ person.del
321
+
322
+ === Node Traversals
323
+
324
+ The has_one and has_many methods create a convenient method for traversals and
325
+ managing relationships to other nodes.
326
+ Example:
327
+
328
+ Person.has_n :friends # generates the friends instance method
329
+ # all instances of Person now has a friends method so that we can do the following
330
+ person.friends.each {|n| ... }
331
+
332
+ Traversing using a filter
333
+
334
+ person.friends{ salary == 10000 }.each {|n| ...}
335
+
336
+ Traversing with a specific depth (depth 1 is default)
337
+
338
+ person.friends{ salary == 10000}.depth(3).each { ... }
339
+
340
+ There is also a more powerful method for traversing several relationships at
341
+ the same time - Neo4j::NodeMixin#traverse, Neo4j::JavaNodeMixin#outgoing and Neo4j::JavaNodeMixin:incoming see below.
342
+
343
+ === Example on Relationships
344
+
345
+ In the first example the friends relationship can have relationships to any
346
+ other node of any class.
347
+ In the next example we specify that the 'acted_in' relationship should use
348
+ the Ruby classes Actor, Role and Movie.
349
+ This is done by using the has_n class method:
350
+
351
+ class Role
352
+ include Neo4j::RelationshipMixin
353
+ # notice that neo4j relationships can also have properties
354
+ property :name
355
+ end
356
+
357
+ class Actor
358
+ include Neo4j::NodeMixin
359
+
360
+ # The following line defines the acted_in relationship
361
+ # using the following classes:
362
+ # Actor[Node] --(Role[Relationship])--> Movie[Node]
363
+ #
364
+ has_n(:acted_in).to(Movie).relationship(Role)
365
+ end
366
+
367
+ class Movie
368
+ include Neo4j::NodeMixin
369
+ property :title
370
+ property :year
371
+
372
+ # defines a method for traversing incoming acted_in relationships from Actor
373
+ has_n(:actors).from(Actor, :acted_in)
374
+ end
375
+
376
+ Creating a new Actor-Role-Movie relationship can be done like this:
377
+
378
+ keanu_reeves = Actor.new
379
+ matrix = Movie.new
380
+ keanu_reeves.acted_in << matrix
381
+
382
+ or you can also specify this relationship on the incoming node
383
+ (since we provided that information in the has_n methods).
384
+
385
+ keanu_reeves = Actor.new
386
+ matrix = Movie.new
387
+ matrix.actors << keanu_reeves
388
+
389
+ More information about neo4j can be found after the Lucene section below.
390
+
391
+ = Neo4j API Documentation
392
+
393
+ === Start and Stop of the Neo4j
394
+
395
+ Unlike the Java Neo4j implementation it is not necessarily to start Neo4j.
396
+ It will automatically be started when needed.
397
+ It also uses a hook to automatically shutdown Neo4j.
398
+ Shutdown of Neo4j can also be done using the stop method, example:
399
+
400
+ Neo4j.stop
401
+
402
+ ==== Neo4j Configuration
403
+
404
+ Before using Neo4j the location where the database is stored on disk should be configured.
405
+ The Neo4j configuration is kept in the Neo4j::Config class:
406
+
407
+ Neo4j::Config[:storage_path] = '/home/neo/neodb'
408
+
409
+ ==== Accessing the Java Neo4j API
410
+
411
+ You can access the org.neo4j.kernel.EmbeddedGraphDatabase class by
412
+
413
+ Neo4j.instance
414
+
415
+ You can create an org.neo4j.graphdb.Node object by using the Neo4j::Node.new method (!)
416
+
417
+ node = Neo4j::Node.new # => an instance of org.neo4j.graphdb.Node
418
+
419
+ To load a specific node by its ID (see javadoc org.neo4j.graphdb.Node.getId()), do
420
+
421
+ node_id = node.neo_id
422
+ ref_node = Neo4j.load_node(node_id)
423
+
424
+ The neo_id method works both for the Neo4j::NodeMixin and for org.neo4j.graphdb.Node java objects.
425
+
426
+ You can create a relationship of type org.neo4j.graphdb.Relationship by
427
+
428
+ a = Neo4j::Node.new
429
+ b = Neo4j::Node.new
430
+ r = a.add_rel(:friends, b)
431
+ r.java_class # => class org.neo4j.kernel.impl.core.RelationshipProxy
432
+
433
+ === Lucene Integration
434
+
435
+ Neo4j.rb uses the Lucene module. That means that the Neo4j::NodeMixin has methods for
436
+ both traversal and Lucene queries/indexing.
437
+
438
+ ==== Lucene Configuration
439
+
440
+ By default Lucene indexes are kept in memory.
441
+ Keeping index in memory will increase the performance of Lucene operations (such as updating the index).
442
+
443
+ Example to configure Lucene to store indexes on disk instead
444
+
445
+ Lucene::Config[:store_on_file] = true
446
+ Lucene::Config[:storage_path] = '/home/neo/lucene-db'
447
+
448
+ ==== Lucene Index in Memory
449
+
450
+ If index is stored in memory then one needs to reindex all nodes when the application starts up again.
451
+
452
+ MyNode.update_index # will traverse all MyNode instances and (re)create the Lucene index in memory.
453
+
454
+ === Neo4j::NodeMixin
455
+
456
+ Neo4j::NodeMixin is a mixin that lets instances to be stored as a node in the Neo node space on disk.
457
+ A node can have properties and relationships to other nodes.
458
+
459
+ Example of how declare a class that has this behaviour:
460
+
461
+ class MyNode
462
+ include Neo4j::NodeMixin
463
+ end
464
+
465
+
466
+ === Neo4j::Node
467
+
468
+ If you do not need to map a node to a ruby instance you can simply use the Neo4j::Node object.
469
+
470
+ Example:
471
+
472
+ node = Neo4j::Node.new
473
+ node[:name] = 'foo'
474
+
475
+ The Neo4j::Node.new method actually returns a Java object that implements the org.neo4j.graphdb.Node interface.
476
+ That Java interface is extended with methods so it behaviors almost like using the your own Ruby Neo4j::NodeMixin class.
477
+
478
+ === Create a Node
479
+
480
+ node = MyNode.new
481
+
482
+ === Delete a Node
483
+
484
+ The Neo4j::NodeMixin mixin defines a delete method that will delete the node and all its relationships.
485
+
486
+ Example:
487
+
488
+ node = MyNode.new
489
+ node.del
490
+
491
+ The node in the example above will be removed from the Neo database on the filesystem and the Lucene index
492
+
493
+ ==== Neo4j::Node#del method
494
+
495
+ Since one can both use the org.neo4j.graphdb.Node directly or using the Neo4j::NodeMixin there might be a clash
496
+ in method names.
497
+ For example the method Neo4j::NodeMixin#del deletes the node and all its relationships.
498
+ The org.neo4j.graphdb.Node#delete (which is created by Neo4j::Node.new) will raise an exception if not all relationships are already deleted.
499
+
500
+
501
+ === Node and Relationship Identity
502
+
503
+ Each node has an unique identity (neo_id) which can be used for loading the node:
504
+
505
+ id = Neo4j::Node.new.neo_id
506
+ Neo4j.load_node(id) # will return the node that was created above
507
+
508
+ And for relationships:
509
+
510
+ rel = Neo4j::Node.new.add_rel(:some_relationship_type, Neo4j::Node.new)
511
+ id = rel.neo_id
512
+ # Load the node
513
+ Neo4j.load_rel(id) # will return the relationship (rel) that was created above
514
+
515
+ === Node Properties
516
+
517
+ In order to use properties they have to be declared first
518
+
519
+ class MyNode
520
+ include Neo4j::NodeMixin
521
+ property :foo, :bar
522
+ end
523
+
524
+ These properties (foo and bar) will be stored in the Neo database.
525
+ You can set those properties:
526
+
527
+ # create a node with two properties in one transaction
528
+ node = MyNode.new { |n|
529
+ n.foo = 123
530
+ n.bar = 3.14
531
+ }
532
+
533
+ # access those properties
534
+ puts node.foo
535
+
536
+
537
+ You can also set a property like this:
538
+
539
+ f = SomeNode.new
540
+ f.foo = 123
541
+
542
+ Neo4j.rb supports properties to by of type String, Fixnum, Float and true/false
543
+
544
+ === Property Types and Marshalling
545
+
546
+ If you want to set a property of a different type then String, Fixnum, Float or true/false
547
+ you have to specify its type.
548
+
549
+ Example, to set a property to any type
550
+
551
+ class MyNode
552
+ include Neo4j::NodeMixin
553
+ property :foo, :type => Object
554
+ end
555
+
556
+
557
+ node = MyNode.new
558
+ node.foo = [1,"3", 3.14]
559
+
560
+ Neo4j.load_node(node.neo_id).foo.class # => Array
561
+
562
+
563
+ === Property of type Date and DateTime
564
+
565
+ Example of using Date queries:
566
+
567
+ class MyNode
568
+ include Neo4j::NodeMixin
569
+ property :born, :type => Date
570
+ index :born, :type => Date
571
+ end
572
+
573
+ Neo4j::Transaction.run do
574
+ node = MyNode.new
575
+ node.born = Date.new 2008, 05, 06
576
+ end
577
+
578
+ Neo4j::Transaction.run do
579
+ MyNode.find("born:[20080427 TO 20100203]")[0].born # => Date
580
+ end
581
+
582
+ Example of using DateTime queries:
583
+
584
+ class MyNode
585
+ include Neo4j::NodeMixin
586
+ property :since, :type => DateTime
587
+ index :since, :type => DateTime
588
+ end
589
+
590
+ Neo4j::Transaction.run do
591
+ node = MyNode.new
592
+ node.since = DateTime.civil 2008, 04, 27, 15, 25, 59
593
+ end
594
+
595
+ Neo4j::Transaction.run do
596
+ MyNode.find("since:[200804271504 TO 201002031534]")[0].since # => DateTime
597
+ end
598
+
599
+ Only UTC timezone is allowed.
600
+ Notice that the query must be performed in a new transaction.
601
+
602
+ === Finding all nodes
603
+
604
+ To find all nodes use the Neo4j#all_nodes method.
605
+
606
+ Example
607
+
608
+ Neo4j.all_nodes{|node| puts node}
609
+
610
+ === Declared Relationships
611
+
612
+ Neo relationships are asymmetrical. That means that if A has a relationship to B
613
+ then it may not be true that B has a relationship to A.
614
+
615
+ Relationships can be declared by using 'has_n', 'has_one' or 'has_list' Neo4j::RelClassMethods methods (included in the Neo4j::NodeMixin).
616
+
617
+ This methods generates accessor methods for relationships.
618
+ By using those accessor methods we do no longer need to know which direction to navigate in the relationship.
619
+ There are accessor methods for both relationships and nodes.
620
+
621
+ The 'has_n', 'has_one' or 'has_list' Neo4j::RelClassMethods methods returns a Neo4j::Relationships::DeclRelationshipDsl.
622
+
623
+ === has_n
624
+
625
+ The Neo4j::NodeMixin#has_n class method (see Neo4j::RelClassMethods#has_n) creates a new instance method that can
626
+ be used for both traversing and adding new objects to a specific relationship type.
627
+ The has_n method returns a DSL object Neo4j::Relationships::DeclRelationshipDsl
628
+
629
+ For example, let say that Person can have a relationship to any other node class with the type 'friends':
630
+
631
+ class Person
632
+ include Neo4j::NodeMixin
633
+ has_n :knows # will generate a knows method for outgoing relationships
634
+ end
635
+
636
+ The generated knows method will allow you to add new relationships, example:
637
+
638
+ me = Person.new
639
+ neo = Person.new
640
+ me.knows << neo # me knows neo but neo does not know me
641
+
642
+ You can add any object to the 'knows' relationship as long as it
643
+ includes the Neo4j::NodeMixin or is an org.neo4j.core.api.Node object, example:
644
+
645
+ person = Person.new
646
+ another_node = Neo4j::Node.new
647
+ person.knows << another_node
648
+
649
+ person.knows.include?(another_node) # => true
650
+
651
+ ==== has_n to an outgoing class
652
+
653
+ If you want to express that the relationship should point to a specific class
654
+ use the 'to' method on the has_n method.
655
+ (It's still possible to add any nodes to the relationship - no validation is performed)
656
+
657
+ class Person
658
+ include Neo4j::NodeMixin
659
+ has_n(:knows).to(Person)
660
+ end
661
+
662
+ The difference between specifying a 'to' class and not doing that is that Neo4j.rb will create relationships of type
663
+ 'Person#knows'.
664
+
665
+ Example:
666
+ a = Person.new
667
+ b = Neo4j::Node.new
668
+ a.knows << b
669
+
670
+ # is the same as
671
+ a.add_rel('Person#knows', b)
672
+
673
+ The given class 'Person' will act like a name space for the relationship 'knows'.
674
+
675
+ ==== has_n from an incoming class
676
+
677
+ It's also possible to generate methods for incoming relationships by using the
678
+ 'from' method on the has_n method.
679
+
680
+ Example:
681
+ class Person
682
+ include Neo4j::NodeMixin
683
+ has_n :knows # will generate a knows method for outgoing relationships
684
+ has_n(:known_by).from(Person, :knows) # will generate a known_by method for incoming knows relationship
685
+ end
686
+
687
+ In the example above we can find outgoing nodes using the 'knows' method and incoming relationships using the 'known_by' method.
688
+
689
+ Example:
690
+
691
+ person = Person.new
692
+ other_person = Person.new
693
+ person.knows << other_person
694
+ other_person.known_by.include?(person) # => true
695
+
696
+ You can also add a relationships on either the incoming or outgoing node.
697
+ The from method can also take an additional class parameter if it has incoming nodes
698
+ from a different node class (see the Actor-Role-Movie example at the top of this document).
699
+
700
+ Example of adding a 'knows' relationship from the other node:
701
+
702
+ me = Person.new
703
+ neo = Person.new
704
+ neo.known_by << me
705
+
706
+ # me knows neo but neo does not know me
707
+ me.knows.include?(neo) # => true
708
+ neo.knows.include?(me) # => false
709
+
710
+ The known_by method creates a 'knows' relationship between the me and neo nodes.
711
+ This is the same as doing:
712
+
713
+ me.knows << neo # me knows neo but neo does not know me
714
+
715
+ ==== has_n from an incoming class with 'namespace'
716
+
717
+ In the example above we only provided the parameter :knows for the from method. That means that incoming relationship of type 'knows' will
718
+ be accessible with the known_by method.
719
+ The following many-to-many example demonstrates how to specify a from class.
720
+
721
+ class Product
722
+ include Neo4j::NodeMixin
723
+ has_n(:orders).to(Order)
724
+ end
725
+
726
+ class Order
727
+ include Neo4j::NodeMixin
728
+ has_n(:products).from(Product, :orders)
729
+ end
730
+
731
+ Then you can add an order on the Product object or add an Product on the Order object.
732
+
733
+ p = Product.new
734
+ o = Order.new
735
+ o.products << p
736
+
737
+ Which is the same as
738
+
739
+ p = Product.new
740
+ o = Order.new
741
+ p.orders << o
742
+
743
+ Notice that we must provide the class name of the from method since we use the 'namespace' Order for the outgoing orders relationship.
744
+
745
+ ==== Accessing Declared Relationships
746
+
747
+ Neo4j.rb generates methods for accessing declared relationship.
748
+ Example, let say that class Product declares a relationship 'order' to class Order.
749
+
750
+ class Product
751
+ include Neo4j::NodeMixin
752
+ has_n(:orders).to(Order)
753
+ end
754
+
755
+ To access the relationships between Product and Order without specifying the 'Order#' namespace
756
+ one can use the '<rel type>_rels' method.
757
+
758
+ Example:
759
+
760
+ product = Product.new
761
+ order = Order.new
762
+ product.orders << order
763
+ prod_order_relationship = product.orders_rels.first
764
+ prod_order_relationship.start_node # => product
765
+ prod_order_relationship.end_node # => order
766
+
767
+ For a has_one relationship the '<rel type>_rel' method will be generated instead.
768
+ This work for both incoming and outgoing nodes.
769
+
770
+ === Relationship has_one
771
+
772
+ Example: A person can have at most one Address
773
+
774
+ class Address; end
775
+
776
+ class Person
777
+ include Neo4j::NodeMixin
778
+ has_one(:address).to(Address)
779
+ end
780
+
781
+ class Address
782
+ include Neo4j::NodeMixin
783
+ property :city, :road
784
+ has_n(:people).from(Person, :address)
785
+ end
786
+
787
+ In the example above we have Neo4j.rb will generate the following methods
788
+ * in Person, the method ''address='' and ''address''
789
+ * in Address, the traversal method ''people'' for traversing incoming relationships from the Person node.
790
+
791
+ Example of usage:
792
+
793
+ p = Person.new
794
+ p.address = Address.new
795
+ p.address.city = 'malmoe'
796
+ p.address.people.include?(p) # => true
797
+
798
+ Or from the incoming ''address'' relationship
799
+
800
+ a = Address.new {|n| n.city = 'malmoe'}
801
+ a.people << Person.new
802
+ a.people.first.address # => a
803
+
804
+ For more documentation see the Neo4j::RelClassMethods#has_one.
805
+
806
+ === Relationship has_list
807
+ The has_n relationship will not maintain the order of when items are inserted to the relationship.
808
+ If order should be preserved then use the has_list class method instead.
809
+
810
+ Example
811
+
812
+ class Company
813
+ include Neo4j::NodeMixin
814
+ has_list :employees
815
+ end
816
+
817
+ company = Company.new
818
+ company.employees << employee1 << employee2
819
+
820
+ # prints first employee2 and then employee1
821
+ company.employees.each {|employee| puts employee.name}
822
+
823
+ If the optional parameter :size is given then the list will contain a size counter.
824
+
825
+ Example
826
+
827
+ class Company
828
+ has_list :employees, :counter => true
829
+ end
830
+
831
+ company = Company.new
832
+ company.employees << employee1 << employee2
833
+ company.employees.size # => 2
834
+
835
+ For more documentation see the Neo4j::RelClassMethods#has_list.
836
+
837
+ ==== Deleted List Items
838
+
839
+ The list will be updated if an item is deleted in a list.
840
+ Example:
841
+
842
+ company = Company.new
843
+ company.employees << employee1 << employee2 << employee3
844
+ company.employees.size # => 3
845
+
846
+ employee2.del
847
+
848
+ company.employees.to_a # => [employee1, employee3]
849
+ company.employees.size # => 2
850
+
851
+ ==== Memberships in lists
852
+
853
+ Each node in a list knows which lists it belongs to, and the next and previous item in the list
854
+ Example:
855
+
856
+ employee1.list(:employees).prev # => employee2
857
+ employee2.list(:employees).next # => employee1
858
+ employee1.list(:employees).size # => 3 # the size counter is available if the :counter parameter is given as shown above
859
+
860
+
861
+ (The list method takes an optional extra parameter - the list node. Needed if one node is member of more then one list with the same name).
862
+
863
+ === Cascade delete
864
+
865
+ The has_one, has_n and has_list all support cascade delete.
866
+ There are two types of cascade delete - incoming and outgoing.
867
+ For an outgoing cascade delete the members (of the has_one/has_n/has_list) will all be deleted when the
868
+ 'root' node is deleted. For incoming cascade the 'root' node will be deleted when all its members are deleted.
869
+
870
+ Example, outgoing
871
+
872
+ class Person
873
+ include Neo4j::NodeMixin
874
+ has_list :phone_nbr, :cascade_delete => :outgoing
875
+ end
876
+
877
+ p = Person.new
878
+ phone1 = Neo4j::Node.new
879
+ phone1[:number] = '+46123456789'
880
+ p.phone_nbr << phone1
881
+ p.phone_nbr << phone2
882
+
883
+ p.del
884
+
885
+ # then phone1 and phone2 node will also be deleted.
886
+
887
+ Example, incoming
888
+
889
+ class Phone
890
+ include Neo4j::NodeMixin
891
+ has_list :people, :cascade_delete => :incoming # a list of people having this phone number
892
+ end
893
+
894
+ phone1 = Phone.new
895
+ p1 = Person.new
896
+ p2 = person.new
897
+ phone1.people << p1
898
+ phone1.people << p2
899
+
900
+ p1.del
901
+ p2.del
902
+
903
+ # then phone1 will be deleted
904
+
905
+
906
+ === Finding all nodes
907
+
908
+ To find all nodes of a specific type use the all method.
909
+
910
+ Example
911
+
912
+ require 'neo4j/extensions/reindexer'
913
+
914
+ class Car
915
+ include Neo4j::NodeMixin
916
+ property :wheels
917
+ end
918
+
919
+ class Volvo < Car
920
+ end
921
+
922
+ v = Volvo.new
923
+ c = Car.new
924
+
925
+ Car.all # will return all relationships from the reference node to car objects
926
+ Volvo.all # will return the same as Car.all
927
+
928
+ To return nodes (just like the relationships method)
929
+
930
+ Car.all.nodes # => [c,v]
931
+ Volvo.all.nodes # => [v]
932
+
933
+
934
+ The reindexer extension that is used in the example above will for each created node create a relationship
935
+ from the index node (Neo4j#ref_node.relationships.outgoing(:index_node)) to that new node.
936
+ The all method use these relationships in order to return nodes of a certain class.
937
+ The update_index method also uses this all method in order to update index for all nodes of a specific class.
938
+
939
+ === Traversing Relationships
940
+
941
+ Each type of relationship has a method that returns an Enumerable object that enables you
942
+ to traverse that type of relationship.
943
+
944
+ For example the Person example above declares one relationship of type friends.
945
+ You can traverse all Person's friends (depth 1 is default)
946
+
947
+ f.friends.each { |n| puts n }
948
+
949
+ It is also possible to traverse a relationship of an arbitrary depth.
950
+ Example finding all friends and friends friends.
951
+
952
+ f.friends.depth(2).each { ...}
953
+
954
+ Traversing to the end of the graph
955
+
956
+ f.friends.depth(:all).each { ...}
957
+
958
+ ==== Filtering Nodes
959
+
960
+ If you want to find one node in a relationship you can use a filter.
961
+ Example, let say we want to find a friend with name 'andreas'
962
+
963
+ n1 = Person.new
964
+ n2 = Person.new :name => 'andreas'
965
+ n3 = Person.new
966
+ n1.friends << n2 << n3
967
+ n1.friends{ name == 'andreas' }.to_a # => [n2]
968
+
969
+ The block { name == 'andreas' } will be evaluated on each node in the relationship.
970
+ If the evaluation returns true the node will be included in the filter search result.
971
+
972
+ === Traversing Nodes
973
+
974
+ The Neo4j::NodeMixin#incoming and Neo4j::NodeMixin#outgoing method are a more powerful methods compared to the
975
+ generated has_n and has_one methods. Unlike the generated methods it can
976
+ traverse several relationship types at the same time. The types of relationships
977
+ being traversed must therefore always be specified in the incoming, outgoing or both method.
978
+ The three methods can take one or more relationship types parameters
979
+ if more than one type of relationship should be traversed.
980
+
981
+ ==== Traversing Nodes of Arbitrary Depth
982
+
983
+ The depth method allows you to specify how deep the traversal should be.
984
+ If not specified, only one level is traversed.
985
+
986
+ Example:
987
+
988
+ me.incoming(:friends).depth(4).each {} # => people with a friend relationship to me
989
+
990
+ ==== Traversing Nodes With Several Relationship Types
991
+
992
+ It is possible to traverse several relationship types at the same type.
993
+ The incoming, both and outgoing methods takes a list of arguments.
994
+
995
+ Example, given the following holiday trip domain:
996
+
997
+ # A location contains a hierarchy of other locations
998
+ # Example region (asia) contains countries which contains cities etc...
999
+ class Location
1000
+ include Neo4j::NodeMixin
1001
+ has_n :contains
1002
+ has_n :trips
1003
+ property :name
1004
+ index :name
1005
+
1006
+ # A Trip can be specific for one global area, such as "see all of sweden" or
1007
+ # local such as a 'city tour of malmoe'
1008
+ class Trip
1009
+ include Neo4j::NodeMixin
1010
+ property :name
1011
+ end
1012
+
1013
+ # create all nodes
1014
+ # ...
1015
+
1016
+ # setup the relationship between all nodes
1017
+ @europe.contains << @sweden << @denmark
1018
+ @sweden.contains << @malmoe << @stockholm
1019
+
1020
+ @sweden.trips << @sweden_trip
1021
+ @malmoe.trips << @malmoe_trip
1022
+ @malmoe.trips << @city_tour
1023
+ @stockholm.trips << @city_tour # the same city tour is available both in malmoe and stockholm
1024
+
1025
+ Then we can traverse both the contains and the trips relationship types.
1026
+ Example:
1027
+ @sweden.outgoing(:contains, :trips).to_a # => [@malmoe, @stockholm, @sweden_trip]
1028
+
1029
+ It is also possible to traverse both incoming and outgoing relationships, example:
1030
+
1031
+ @sweden.outgoing(:contains, :trips).incoming(:contains).to_a # => [@malmoe, @stockholm, @sweden_trip, @europe]
1032
+
1033
+ ==== Traversing Nodes With a Filter
1034
+
1035
+ It's possible to filter which nodes should be returned from the traverser by
1036
+ using the filter function. This filter function will be evaluated differently
1037
+ depending the number of arguments it takes, see below.
1038
+
1039
+ ==== Filtering: Using Evaluation in the Context of the Current Node
1040
+ If the provided filter function does not take any parameter it will be evaluated in the context
1041
+ of the current node being traversed.
1042
+ That means that one can writer filter functions like this:
1043
+
1044
+ @sweden.outgoing(:contains, :trips).filter { name == 'sweden' }
1045
+
1046
+ ==== Filtering: Using the TraversalPostion
1047
+ If the filter method takes one parameter then it will be given an object of type
1048
+ TraversalPosition which contains information about current node, how many nodes
1049
+ has been returned, depth etc.
1050
+
1051
+ The information contained in the TraversalPostion can be used in order to decide if the node should be included in the traversal search result.
1052
+ If the provided block returns true then the node will be included in the search result.
1053
+
1054
+ The filter function will not be evaluated in the context of the current node when this parameter is provided.
1055
+
1056
+ The TraversalPosition is a thin wrapper around the java interface TraversalPosition, see
1057
+ http://api.neo4j.org/current/org/neo4j/api/core/TraversalPosition.html
1058
+
1059
+ For example if we only want to return the Trip objects in the example above:
1060
+
1061
+ # notice how the tp (TraversalPosition) parameter is used in order to only
1062
+ # return nodes included in a 'trips' relationship.
1063
+ traverser = @sweden.outgoing(:contains, :trips).filter do |tp|
1064
+ tp.last_relationship_traversed.relationship_type == :trips
1065
+ end
1066
+
1067
+ traverser.to_a # => [@sweden_trip]
1068
+
1069
+ === Relationships
1070
+
1071
+ A relationship between two nodes can have properties just like a node.
1072
+
1073
+ Example:
1074
+
1075
+ p1 = Person.new
1076
+ p2 = Person.new
1077
+
1078
+ relationship = p1.friends.new(p2)
1079
+
1080
+ # set a property 'since' on this relationship between p1 and p2
1081
+ relationship.since = 1992
1082
+
1083
+ If a Relationship class has not been specified for a relationship then any properties
1084
+ can be set on the relationship. It has a default relationship class: Neo4j::Relationships::Relationship
1085
+
1086
+ If you instead want to use your own class for a relationship use the
1087
+ Neo4j::NodeMixin#has_n.relationship method, example:
1088
+
1089
+ class Role
1090
+ # This class can be used as the relationship between two nodes
1091
+ # since it includes the following mixin
1092
+ include Neo4j::RelationMixin
1093
+ property :name
1094
+ end
1095
+
1096
+ class Actor
1097
+ include Neo4j::NodeMixin
1098
+ # use the Role class above in the relationship between Actor and Movie
1099
+ has_n(:acted_in).to(Movie).relationship(Role)
1100
+ end
1101
+
1102
+
1103
+ === Finding Relationships
1104
+
1105
+ The Neo4j::NodeMixin#relationships method can be used to find incoming or outgoing relationship objects.
1106
+ Example of listing all types of outgoing (default) relationship objects (of depth one) from the me node.
1107
+
1108
+ me.relationships.each {|rel| ... }
1109
+
1110
+ If we instead want to list the nodes that those relationships points to then the nodes method can be used.
1111
+
1112
+ me.rels.nodes.each {|rel| ... }
1113
+
1114
+ Listing all incoming relationship objects of any relationship type:
1115
+
1116
+ me.rels.incoming.each { ... }
1117
+
1118
+ Listing both incoming and outgoing relationship object of a specific type:
1119
+
1120
+ me.rels.both(:friends) { }
1121
+
1122
+ Finding one outgoing relationship of a specific type and node (you)
1123
+
1124
+ me.rels.outgoing(:friends)[you] # => [#<Neo4j::RelationshipMixin:0x134ae32]
1125
+
1126
+
1127
+ ==== Finding Relationships Example
1128
+
1129
+ Example, given we have the two nodes with a relationship between them:
1130
+
1131
+ n1 = Person.new
1132
+ n2 = Person.new
1133
+
1134
+ n1.friends << n2
1135
+
1136
+ Then we can find all incoming and outgoing relationships like this:
1137
+
1138
+ n1.rels.to_a # => [#<Neo4j::RelationshipMixin:0x134ae32]
1139
+
1140
+ A Neo4j::RelationshipMixin object represents a relationship between two nodes.
1141
+
1142
+ n1.rels[0].start_node # => n1
1143
+ n1.rels[0].end_node # => n2
1144
+
1145
+ A RelationshipMixin contains the relationship type which connects the two nodes
1146
+
1147
+ n1.rels[0].relationship_type # => :friends
1148
+
1149
+ Relationships can also have properties just like a node (NodeMixin).
1150
+
1151
+ === Finding outgoing and incoming relationships
1152
+
1153
+ If we are only interested in all incoming nodes, we can do
1154
+
1155
+ n2.rels.incoming # => [#<Neo4j::RelationshipMixin:0x134aea2]
1156
+
1157
+ Or outgoing:
1158
+
1159
+ n1.rels.outgoing # => [#<Neo4j::RelationshipMixin:0x134aea2]
1160
+
1161
+ To find a specific relationship use the [] operator:
1162
+
1163
+ n1.rels.outgoing[n2] = #<Neo4j::RelationshipMixin:0x134aea2
1164
+
1165
+ Or which is better performance wise (since only friends relationships are being traversed):
1166
+
1167
+ n1.rels.outgoing(:friends)[n2] = #<Neo4j::RelationshipMixin:0x134aea2
1168
+
1169
+ === Deleting a relationship
1170
+
1171
+ Use the Neo4j::RelationshipMixin#delete method.
1172
+ For example, to delete the relationship between n1 and n2 from the example above:
1173
+
1174
+ n1.rels.outgoing(:friends)[n2].delete
1175
+
1176
+ === Finding nodes in a relationship
1177
+
1178
+ If you do not want the relationship object, but just the nodes you can use the 'nodes' method
1179
+ in the Neo4j::RelationshipMixin object.
1180
+
1181
+ For example:
1182
+
1183
+ n2.rels.incoming.nodes # => [n1]
1184
+
1185
+ === Finding outgoing/incoming nodes of a specific relationship type
1186
+
1187
+ Let say we want to find who has my phone number and who consider me as a friend
1188
+
1189
+ # who has my phone numbers
1190
+ me.rels.incoming(:phone_numbers).nodes # => people with my phone numbers
1191
+
1192
+ # who consider me as a friend
1193
+ me.rels.incoming(:friends).nodes # => people with a friend relationship to me
1194
+
1195
+ Remember that relationships are not symmetrical.
1196
+ Notice that, there is also another way of finding nodes, see the Neo4j::NodeMixin#traverse method below.
1197
+
1198
+ === Transactions
1199
+
1200
+ All operations that work with the node space (even read operations) must be wrapped in a transaction.
1201
+ For example all get, set and find operations will start a new transaction if none is already not running (for that thread).
1202
+
1203
+ If you want to perform a set of operation in a single transaction, use the Neo4j::Transaction.run method:
1204
+
1205
+ Example
1206
+
1207
+ Neo4j::Transaction.run {
1208
+ node1.foo = "value"
1209
+ node2.bar = "hi"
1210
+ }
1211
+
1212
+ There is also a auto commit feature available which is enabled by requiring 'neo4j/auto_tx' instead of 'neo4j',
1213
+ see the three minutes tutorial above.
1214
+
1215
+ You can also run it without a block, like this:
1216
+
1217
+ transaction = Neo4j::Transaction.new
1218
+ transaction.start
1219
+ # do something
1220
+ transaction.finish
1221
+
1222
+ ==== Rollback
1223
+
1224
+ Neo4j support rollbacks on transaction. Example:
1225
+ Example:
1226
+
1227
+ include 'neo4j'
1228
+
1229
+ node = MyNode.new
1230
+
1231
+ Neo4j::Transaction.run { |t|
1232
+ node.foo = "hej"
1233
+ # something failed so we signal for a failure
1234
+ t.failure # will cause a rollback, node.foo will not be updated
1235
+ }
1236
+
1237
+
1238
+ === Indexing
1239
+
1240
+ Properties and relationships which should be indexed by Lucene can be specified by the index class method.
1241
+ For example to index the properties foo and bar
1242
+
1243
+ class SomeNode
1244
+ include Neo4j::NodeMixin
1245
+ property :foo, :bar
1246
+ index :foo, :bar
1247
+ end
1248
+
1249
+ Every time a node of type SomeNode (or a subclass) is created, deleted or updated the Lucene index will be updated.
1250
+
1251
+ === Reindexing
1252
+
1253
+ Sometimes it's necessarily to change the index of a class after a lot of node instances already have been created.
1254
+ To delete an index use the class method 'remove_index'
1255
+ To update an index use the class method 'update_index' which will update all already created nodes in the Neo database.
1256
+
1257
+ Example:
1258
+
1259
+ require 'neo4j'
1260
+ require 'neo4j/extensions/reindexer' # needed for the update_index method
1261
+ class Person
1262
+ include Neo4j
1263
+ property :name, :age, :phone
1264
+ index :name, :age
1265
+ end
1266
+
1267
+ p1 = Person.new :name => 'andreas', :phone => 123
1268
+ Person.find (:name => 'andreas') # => [p1]
1269
+ Person.find (:phone => 123) # => []
1270
+
1271
+ # change index and reindex all person nodes already created in the Neo database.
1272
+ Person.remove_index :name
1273
+ Person.index :phone # add an index on phone
1274
+ Person.update_index
1275
+
1276
+ Person.find (:name => 'andreas') # => []
1277
+ Person.find (:phone => 123) # => [p1]
1278
+
1279
+ In order to use the update_index method you must include the reindexer neo4j.rb extension.
1280
+ This extension will keep a relationship to each created node so that it later can recreate
1281
+ the index by traversing those relationships.
1282
+
1283
+ === Updating Lucene Index
1284
+
1285
+ The Lucene index will be updated after the transaction commits. It is not possible to
1286
+ query for something that has been created inside the same transaction as where the query is performed.
1287
+
1288
+ === Querying (using Lucene)
1289
+
1290
+ You can declare properties to be indexed by Lucene by the index method:
1291
+
1292
+ Example
1293
+
1294
+ class Person
1295
+ include Neo4j::NodeMixin
1296
+ property :name, :age
1297
+ index :name, :age
1298
+ end
1299
+
1300
+ node = Person.new
1301
+ node.name = 'foo'
1302
+ node.age = 42
1303
+
1304
+
1305
+ Person.find(:name => 'foo', :age => 42) # => [node]
1306
+
1307
+ The query parameter (like property on a Neo4j::NodeMixin) can be of type String, Fixnum, Float, boolean or Range.
1308
+ The query above can also be written in a Lucene query DSL:
1309
+
1310
+ Person.find{(name =='foo') & (age => 42)} # => [node]
1311
+
1312
+ Or Lucene query language:
1313
+
1314
+ Person.find("name:foo AND age:42")
1315
+
1316
+ For more information see: http://lucene.apache.org/java/2_4_0/queryparsersyntax.html or the Lucene module above.
1317
+
1318
+
1319
+ === Indexing and Property Types
1320
+
1321
+ In order to use range query on numbers the property types must be converted.
1322
+ This is done by using the :type optional parameter:
1323
+
1324
+ class Person
1325
+ include Neo4j::NodeMixin
1326
+ property :name, :age
1327
+ index :age, :type => Fixnum
1328
+ end
1329
+
1330
+ By using :type => Fixnum the age will be padded with '0's (Lucene only support string comparison).
1331
+
1332
+ Example, if the :type => Fixnum was not specified then
1333
+
1334
+ p = Person.new {|n| n.age = 100 }
1335
+ Person.find(:age => 0..8) # => [p]
1336
+
1337
+ === Indexing and Querying Relationships
1338
+
1339
+ The Neo4j::NodeMixin#index method can be used to index relationships to other classes.
1340
+
1341
+ Example, let say we have to classes, Customer and Orders:
1342
+
1343
+ class Customer
1344
+ include Neo4j::NodeMixin
1345
+
1346
+ property :name
1347
+
1348
+ # specifies outgoing relationships to Order
1349
+ has_n(:orders).to(Order)
1350
+
1351
+ # create an index on customer-->order#total_cost
1352
+ index "orders.total_cost"
1353
+ end
1354
+
1355
+
1356
+ class Order
1357
+ include Neo4j::NodeMixin
1358
+
1359
+ property :total_cost
1360
+
1361
+ # specifies one incoming relationship from Customer
1362
+ has_one(:customer).from(Customer, :orders)
1363
+
1364
+ # create an index on the order<--customer#name relationship
1365
+ index "customer.name"
1366
+ end
1367
+
1368
+ Notice that we can index both incoming and outgoing relationships.
1369
+
1370
+ Let's create a customer and one order for that customer
1371
+
1372
+ Neo4j::Transaction.run do
1373
+ cust = Customer.new
1374
+ order = Order.new
1375
+ cust.name = "kalle"
1376
+ order.total_cost = "1000"
1377
+
1378
+ cust.orders << order
1379
+ end
1380
+
1381
+ Now we can find both Orders with a total cost between 500 and 2000 and Customers with name 'kalle' using Lucene
1382
+
1383
+ Example:
1384
+
1385
+ customers = Customer.find('orders.total_cost' => 500..2000, 'name' => 'kalle')
1386
+
1387
+ Or also possible from the other way:
1388
+
1389
+ orders = Order.find('total_cost' => 500..2000, 'customer.name' => 'kalle')
1390
+
1391
+ === Full text search
1392
+
1393
+ Neo4j supports full text search by setting the tokenized property to true on an index.
1394
+ (see JavaDoc for org.apache.lucene.document.Field.Index.ANALYZED).
1395
+
1396
+ class Comment
1397
+ include Neo4j::NodeMixin
1398
+
1399
+ property :comment
1400
+ index comment, :tokenized => true
1401
+ end
1402
+
1403
+ === Keyword searches
1404
+
1405
+ If we want to search for exact matches, for example language codes like 'se', 'it' we must make sure
1406
+ that the Lucene does not filters away stop words like 'it'
1407
+
1408
+ class LangCodes
1409
+ include Neo4j::NodeMixin
1410
+ property :code
1411
+ index :code, :analyzer => :keyword
1412
+ end
1413
+
1414
+ By using the keyword analyzer (instead of the default StandardAnalyzer) we make sure that Lucene indexes everything.
1415
+ For more info, see the Lucene chapter below.
1416
+
1417
+ === Unmarshalling
1418
+
1419
+ The Neo module will automatically unmarshal nodes to the correct ruby class.
1420
+ It does this by reading the classname property and loading that ruby class with that node.
1421
+ If this classname property does not exist it will use the default Neo4j::Node for nodes and
1422
+ Neo4j::Relationships::Relationship for relationship.
1423
+
1424
+ class Person
1425
+ include Neo4j::Node
1426
+
1427
+ def hello
1428
+ end
1429
+ end
1430
+
1431
+ f1 = Person.new {}
1432
+
1433
+ # load the class again
1434
+ f2 = Neo4j.load_node(foo.neo_id)
1435
+
1436
+ # f2 will now be new instance of Person, but will be == f1
1437
+ f1 == f2 # => true
1438
+
1439
+ === Reference node
1440
+
1441
+ There is one node that can always be found - the reference node, Neo4j::ReferenceNode.
1442
+ Example:
1443
+
1444
+ Neo4j.ref_node
1445
+
1446
+ This node can have a relationship to the index node (Neo4j::IndexNode), which has relationships to all created nodes.
1447
+ You can add relationships from this node to your nodes.
1448
+
1449
+ == Performance Issues
1450
+
1451
+ It is recommended to wrap several Neo4j operations including read operations
1452
+ in a singe transaction if possible for better performance.
1453
+ Updating a Lucene index can be slow. A solution to this is to keep the index in memory instead of on disk.
1454
+
1455
+ Using raw java nodes (Neo4j::Node) and relationship (Neo4j::Relationship) will also increase performance.
1456
+ Here is an example how to traverse only using Java objects (instead of Ruby wrappers):
1457
+
1458
+ iter = folder.outgoing(:child_folders).raw(true).depth(:all).iterator
1459
+ iter.hasNext()
1460
+
1461
+ The example above gives you access to the raw Java iterator class.
1462
+ Another way to improve performance is to rewrite the performance critical part of your application in Java and access it from neo4j.rb in JRuby.
1463
+ Traversing in pure Java is of orders of magnitude faster then doing it in JRuby.
1464
+
1465
+ == Migrations
1466
+
1467
+ By using migrations you can keep the code and the database in sync. There are two types of migrations : none lazy and lazy.
1468
+ In a none lazy migration the database is upgraded/downgraded all at once, while in lazy migrations the node/relationship is only upgraded/downgraded
1469
+ when the node or relationship is loaded.
1470
+
1471
+ === None Lazy Migration
1472
+
1473
+ Here is an example of a use case for this feature.
1474
+ Let say that we already have a database with nodes that have one property 'name'.
1475
+ Now we want to split that property into two properties: 'surname' and 'given_name'.
1476
+ We want to upgrade the database when it starts so we don't use the lazy migration feature.
1477
+ The neo database starts at version 0 by default.
1478
+
1479
+ Neo4j.migrate 1, "split name" do
1480
+ up do
1481
+ # find all people and change
1482
+ Person.all.each {|p|
1483
+ surname = self[:name].split[0]
1484
+ given_name = self[:name].split[1]
1485
+ delete_property(:name)
1486
+ end
1487
+
1488
+ down do
1489
+ Person.all.each {|p|
1490
+ name = "#{self[:surname]} {self[:given_name]}"
1491
+ delete_property(:surname)
1492
+ delete_property(:given_name)
1493
+ end
1494
+ end
1495
+ end
1496
+
1497
+ If the code above has been loaded before the neo database starts it will automatically upgrade to version 1 (running all the migrations to the higest migration available).
1498
+ You can force the neo to go to a specific version by using Neo4j#migrate! method.
1499
+ For more information see the example/imdb application or the RSpecs.
1500
+
1501
+ === Lazy Migration
1502
+
1503
+ The example above can also be run as lazy migration. i.e. perform the upgrade/downgrade when the node is loaded instead of all at once.
1504
+ The following example demonstrates this feature:
1505
+
1506
+ class Person
1507
+ include Neo4j::NodeMixin
1508
+ include Neo4j::MigrationMixin # you need to include this in order to use lazy migrations
1509
+ ...
1510
+ end
1511
+
1512
+ Person.migration 1, :split_name do
1513
+ up do
1514
+ surname = self[:name].split[0]
1515
+ given_name = self[:name].split[1]
1516
+ delete_property(:name)
1517
+ end
1518
+
1519
+ down do
1520
+ name = "self[:given_name] #{self[:surname]}"
1521
+ delete_property(:surname)
1522
+ delete_property(:given_name)
1523
+ end
1524
+ end
1525
+
1526
+ == Batch Insert
1527
+
1528
+ Sometimes you need a fast way to insert a lot of data into the database without any transactional support.
1529
+ Neo4j.rb wrapps the Java BatchInserter API.
1530
+
1531
+ Neo4j::BatchInserter.new do |b|
1532
+ a = Neo4j::Node.new :name => 'a'
1533
+ b = Neo4j::Node.new :name => 'b'
1534
+ c = Foo.new :key1 => 'val1', :key2 => 'val2'
1535
+ Neo4j::Relationship.new(:friend, a, b, :since => '2001-01-01')
1536
+ end
1537
+
1538
+ Creating nodes and relationships inside the code block uses the batch inserter API. Only a limited set of the API for nodes and relationships are available
1539
+ inside the code block (e.g. traversing is not possible).
1540
+
1541
+ If you need lucene indexing you have to wrap your code inside a transaction, since only when the transaction is finished the lucene database will be updated
1542
+ (the neo4j transaction is disabled). Example:
1543
+
1544
+ Neo4j::BatchInserter.new do
1545
+ Neo4j::Transaction.new
1546
+ foo = Foo98.new
1547
+ foo.name = 'hej'
1548
+ Neo4j::Transaction.finish # update the lucene index, neo4j transaction is disabled here.
1549
+ end
1550
+
1551
+ To get even better insertion speed one can use the raw java Batch Inserter API: http://wiki.neo4j.org/content/Batch_Insert.
1552
+
1553
+ Example:
1554
+
1555
+ Neo4j::BatchInserter.new do |b|
1556
+ b.createNode({'name' => 'me'})
1557
+ end
1558
+
1559
+ Notice that the BatchInserter can be used together with Migrations.
1560
+
1561
+ == Extensions: Replication
1562
+
1563
+ There is an experimental extension that makes it possible to replicate a Neo4j database to another machine.
1564
+ For example how to use it see the test/replication/test_master.rb and test_slave.rb
1565
+ It has only been tested to work with a very simple node space.
1566
+
1567
+ == Extension: REST
1568
+
1569
+ There is a REST extension to Neo4j.rb.
1570
+ It requires the following gems
1571
+ * Sinatra >= 0.9.4
1572
+ * Rack >= 1.0
1573
+ * json-jruby >= 1.1.6
1574
+
1575
+ For RSpec testing it also needs:
1576
+ * rack-test
1577
+
1578
+ For more information see the examples/rest/example.rb or the examples/admin or Neo4j::RestMixin.
1579
+
1580
+
1581
+ == Extension: find_path
1582
+
1583
+ Extension which finds the shortest path (in terms of number of links) between
1584
+ two nodes. Use something like this:
1585
+
1586
+ require 'neo4j/extensions/find_path'
1587
+ node1.traverse.both(:knows).depth(:all).path_to(node2)
1588
+ # => [node1, node42, node1234, node256, node2]
1589
+
1590
+ This extension is still rather experimental. The algorithm is based on the one
1591
+ used in the Neo4j Java IMDB example. For more information see Neo4j::Relationships::NodeTraverser#path_to
1592
+ or the RSpec find_path_spec.rb.
1593
+
1594
+ == Extension: graph_algo
1595
+
1596
+ This extension uses the Java Neo4j Graph Algo package - http://components.neo4j.org/graph-algo/
1597
+ Currently only the AllSimplePaths algorithm supported. If you want the
1598
+ other algorithms you either access the Java methods directly or write a new wrapper (like my AllSimplePath wrapper).
1599
+
1600
+ == Ruby on Rails with Neo4j.rb
1601
+
1602
+ Neo4j.rb does work nicely with R&R.
1603
+ There are two ways to use neo4j.rb with rails - embedded or accessing it via REST.
1604
+
1605
+ === Embedded Rails
1606
+ A complete example of embedding Neo4j with rails can be found http://github.com/andreasronge/neo4j-rails-example/tree/master
1607
+ (please fork and improve it).
1608
+
1609
+ ==== Config rails
1610
+ Config rails to use Neo4j.rb instead of ActiveRecord, edit movies/config/environment.rb
1611
+ environment.rb:
1612
+
1613
+ config.frameworks -= [ :active_record ] #, :active_resource, :action_mailer ]
1614
+ config.gem "neo4j", :version => "0.3.1" # or the latest one
1615
+
1616
+ If you need to reindex all nodes or use the Neo4j::NodeMixin#all method you must require the
1617
+ reindexer neo4j.rb extension. Add a require in the environment.rb file:
1618
+
1619
+ require 'neo4j/extensions/reindexer'
1620
+
1621
+
1622
+ ==== Models
1623
+ Create a new file for each Neo4j node or relationship class
1624
+ Example for an Actor class create the file: app/models/actor.rb
1625
+
1626
+ # filename app/models/actor.rb
1627
+ class Actor
1628
+ include Neo4j::NodeMixin
1629
+ property :name, :phone, :salary
1630
+ has_n(:acted_in).to(Movie).relationship(Role)
1631
+ index :name
1632
+ end
1633
+
1634
+ ==== Create RESTful routes
1635
+ Edit the config/routes.rb file
1636
+ Example:
1637
+
1638
+ ActionController::Routing::Routes.draw do |map|
1639
+ map.resources :actors do |actor|
1640
+ actor.resources :acted_in
1641
+ actor.resource :movies, :controller => 'acted_in'
1642
+ end
1643
+
1644
+ ==== Create Controllers
1645
+
1646
+ Since all Neo4j operations must be wrapped in a transaction, add an around filter for all operations
1647
+ Example:
1648
+
1649
+ acted_in_controller.rb:
1650
+
1651
+ class ActedInController < ApplicationController
1652
+ around_filter :neo_tx
1653
+
1654
+ def index
1655
+ @actor = Neo4j.load_node(params[:actor_id])
1656
+ @movies = @actor.acted_in.nodes
1657
+ end
1658
+
1659
+ def create
1660
+ @actor = Neo4j.load_node(params[:actor_id])
1661
+ @movie = Movie.new
1662
+ @movie.update(params[:movie])
1663
+ @actor.acted_in << @movie
1664
+ flash[:notice] = 'Movie was successfully created.'
1665
+ redirect_to(@actor)
1666
+ end
1667
+
1668
+ def update
1669
+ @actor = Neo4j.load_node(params[:actor_id])
1670
+ @movie = Movie.new
1671
+ @movie.update(params[:movie])
1672
+ @actor.acted_in.new @movie
1673
+ @movie.update(params[:movie])
1674
+ flash[:notice] = 'Movie was successfully updated.'
1675
+ redirect_to(@movie)
1676
+ end
1677
+
1678
+ def show
1679
+ @movie = Neo4j.load_node(params[:id])
1680
+ end
1681
+
1682
+ def new
1683
+ @actor = Neo4j.load_node(params[:actor_id])
1684
+ @movie = Movie.value_object.new
1685
+ end
1686
+
1687
+ def edit
1688
+ @movie = Neo4j.load_node(params[:id])
1689
+ end
1690
+
1691
+ private
1692
+
1693
+ def neo_tx
1694
+ Neo4j::Transaction.new
1695
+ yield
1696
+ Neo4j::Transaction.finish
1697
+ end
1698
+ end
1699
+
1700
+ ==== Add views
1701
+
1702
+ Add the following views in app/views/actors
1703
+ index.html.erb:
1704
+
1705
+ <h1>Listing actors</h1>
1706
+
1707
+ <table>
1708
+ <tr>
1709
+ <th>Name</th>
1710
+ </tr>
1711
+
1712
+ <% for actor in @actors %>
1713
+ <tr>
1714
+ <td><%=h actor.name %></td>
1715
+ <td><%= link_to 'Edit', edit_actor_path(actor) %></td>
1716
+ <td><%= link_to 'Show', actor %></td>
1717
+ <td><%= link_to 'Destroy', actor, :confirm => 'Are you sure?', :method => :delete %></td>
1718
+ </tr>
1719
+ <% end %>
1720
+ </table>
1721
+
1722
+ <br />
1723
+
1724
+ <%= link_to 'New actor', new_actor_path %>
1725
+
1726
+ new.html.erb:
1727
+
1728
+ <h1>New Actor</h1>
1729
+
1730
+ <% form_for(@actor) do |f| %>
1731
+ <p>
1732
+ <%= f.label :name %><br />
1733
+ <%= f.text_field :name %>
1734
+ </p>
1735
+ <p>
1736
+ <%= f.label :phone %><br />
1737
+ <%= f.text_field :phone %>
1738
+ </p>
1739
+ <p>
1740
+ <%= f.label :salary%><br />
1741
+ <%= f.text_field :salary %>
1742
+ </p>
1743
+ <p>
1744
+ <%= f.submit "Update" %>
1745
+ </p>
1746
+
1747
+ <% end %>
1748
+
1749
+
1750
+
1751
+ <%= link_to 'Back', actors_path %>
1752
+
1753
+
1754
+ == The Lucene Module
1755
+
1756
+ You can use this module without using the Neo4j module.
1757
+
1758
+ Lucene provides:
1759
+ * Flexible Queries - Phrases, Wildcards, Compound boolean expressions etc...
1760
+ * Field-specific Queries eg. title, artist, album
1761
+ * Sorting
1762
+ * Ranked Searching
1763
+
1764
+ === Lucene Document
1765
+
1766
+ In Lucene everything is a Document. A document can represent anything textual:
1767
+ A Word Document, a DVD (the textual metadata only), or a Neo4j.rb node.
1768
+ A document is like a record or row in a relationship database.
1769
+
1770
+ The following example shows how a document can be created by using the ''<<'' operator
1771
+ on the Lucene::Index class and found using the Lucene::Index#find method.
1772
+
1773
+ Example of how to write a document and find it:
1774
+
1775
+ require 'lucene'
1776
+
1777
+ include Lucene
1778
+
1779
+ # the var/myindex parameter is either a path where to store the index or
1780
+ # just a key if index is kept in memory (see below)
1781
+ index = Index.new('var/myindex')
1782
+
1783
+ # add one document (a document is like a record or row in a relationship database)
1784
+ index << {:id=>'1', :name=>'foo'}
1785
+
1786
+ # write to the index file
1787
+ index.commit
1788
+
1789
+ # find a document with name foo
1790
+ # hits is a ruby Enumeration of documents
1791
+ hits = index.find{name == 'foo'}
1792
+
1793
+ # show the id of the first document (document 0) found
1794
+ # (the document contains all stored fields - see below)
1795
+ hits[0][:id] # => '1'
1796
+
1797
+ Notice that you have to call the commit method in order to update the index (both disk and in memory indexes).
1798
+ Performing several update and delete operations before a commit will give much
1799
+ better performance than committing after each operation.
1800
+
1801
+ === Keep indexing on disk
1802
+
1803
+ By default Neo4j::Lucene keeps indexes in memory. That means that when the application restarts
1804
+ the index will be gone and you have to reindex everything again.
1805
+
1806
+ To store indexes on file:
1807
+
1808
+ Lucene::Config[:store_on_file] = true
1809
+ Lucene::Config[:storage_path] => '/home/neo/lucene-db'
1810
+
1811
+ When creating a new index the location of the index will be the Lucene::Config[:storage_path] + index path
1812
+ Example:
1813
+
1814
+ Lucene::Config[:store_on_file] = true
1815
+ Lucene::Config[:storage_path] => '/home/neo/lucene-db'
1816
+ index = Index.new('/foo/lucene')
1817
+
1818
+ The example above will store the index at /home/neo/lucene-db/foo/lucene
1819
+
1820
+ === Indexing several values with the same key
1821
+
1822
+ Let say a person can have several phone numbers. How do we index that?
1823
+
1824
+ index << {:id=>'1', :name=>'adam', :phone => ['987-654', '1234-5678']}
1825
+
1826
+
1827
+ === Id field
1828
+
1829
+ All Documents must have one id field. If an id is not specified, the default will be: :id of type String.
1830
+ A different id can be specified using the field_infos id_field property on the index:
1831
+
1832
+ index = Index.new('some/path/to/the/index')
1833
+ index.field_infos.id_field = :my_id
1834
+
1835
+ To change the type of the my_id from String to a different type see below.
1836
+
1837
+ === Conversion of types
1838
+
1839
+ Lucene.rb can handle type conversion for you. (The Java Lucene library stores all
1840
+ the fields as Strings)
1841
+ For example if you want the id field to be a Fixnum
1842
+
1843
+ require 'lucene'
1844
+ include Lucene
1845
+
1846
+ index = Index.new('var/myindex') # store the index at dir: var/myindex
1847
+ index.field_infos[:id][:type] = Fixnum
1848
+
1849
+ index << {:id=>1, :name=>'foo'} # notice 1 is not a string now
1850
+
1851
+ index.commit
1852
+
1853
+ # find that document, hits is a ruby Enumeration of documents
1854
+ hits = index.find(:name => 'foo')
1855
+
1856
+ # show the id of the first document (document 0) found
1857
+ # (the document contains all stored fields - see below)
1858
+ doc[0][:id] # => 1
1859
+
1860
+ If the field_info type parameter is not set then it has a default value of String.
1861
+
1862
+ === Storage of fields
1863
+
1864
+ By default only the id field will be stored.
1865
+ That means that in the example above the :name field will not be included in the document.
1866
+
1867
+ Example
1868
+ doc = index.find('name' => 'foo')
1869
+ doc[:id] # => 1
1870
+ doc[:name] # => nil
1871
+
1872
+ Use the field info :store=true if you want a field to be stored in the index
1873
+ (otherwise it will only be searchable).
1874
+
1875
+ Example
1876
+
1877
+ require 'lucene'
1878
+ include Lucene
1879
+
1880
+ index = Index.new('var/myindex') # store the index at dir: var/myindex
1881
+ index.field_infos[:id][:type] = Fixnum
1882
+ index.field_infos[:name][:store] = true # store this field
1883
+
1884
+ index << {:id=>1, :name=>'foo'} # notice 1 is not a string now
1885
+
1886
+ index.commit
1887
+
1888
+ # find that document, hits is a ruby Enumeration of documents
1889
+ hits = index.find('name' => 'foo')
1890
+
1891
+ # let say hits only contains one document so we can use doc[0] for that one
1892
+ # that document contains all stored fields (see below)
1893
+ doc[0][:id] # => 1
1894
+ doc[0][:name] # => 'foo'
1895
+
1896
+ === Setting field infos
1897
+
1898
+ As shown above you can set field infos like this
1899
+
1900
+ index.field_infos[:id][:type] = Fixnum
1901
+
1902
+ Or you can set several properties like this:
1903
+
1904
+ index.field_infos[:id] = {:type => Fixnum, :store => true}
1905
+
1906
+ ==== Tokenized
1907
+
1908
+ Field infos can be used to specify if the should be tokenized.
1909
+ If this value is not set then the entire content of the field will be considered as a single term.
1910
+
1911
+ Example
1912
+
1913
+ index.field_infos[:text][:tokenized] = true
1914
+
1915
+ If not specified, the default is 'false'
1916
+
1917
+ ==== Analyzer
1918
+
1919
+ Field infos can also be used to set which analyzer should be used.
1920
+ If none is specified, the default analyzer - org.apache.lucene.analysis.standard.StandardAnalyzer (:standard) will be used.
1921
+
1922
+
1923
+ index.field_infos[:code][:tokenized] = false
1924
+ index.field_infos[:code][:analyzer] = :standard
1925
+
1926
+ The following analyzer is supported
1927
+ * :standard (default) - org.apache.lucene.analysis.standard.StandardAnalyzer
1928
+ * :keyword - org.apache.lucene.analysis.KeywordAnalyzer
1929
+ * :simple - org.apache.lucene.analysis.SimpleAnalyzer
1930
+ * :whitespace - org.apache.lucene.analysis.WhitespaceAnalyzer
1931
+ * :stop - org.apache.lucene.analysis.StopAnalyzer
1932
+
1933
+ For more info, check the Lucene documentation, http://lucene.apache.org/java/docs/
1934
+
1935
+
1936
+ === Simple Queries
1937
+
1938
+ Lucene.rb support search in several fields:
1939
+ Example:
1940
+
1941
+ # finds all document having both name 'foo' and age 42
1942
+ hits = index.find('name' => 'foo', :age=>42)
1943
+
1944
+ Range queries:
1945
+
1946
+ # finds all document having both name 'foo' and age between 3 and 30
1947
+ hits = index.find('name' => 'foo', :age=>3..30)
1948
+
1949
+ === Lucene Queries
1950
+
1951
+ If the query is string then the string is a Lucene query.
1952
+
1953
+ hits = index.find('name:foo')
1954
+
1955
+ For more information see:
1956
+ http://lucene.apache.org/java/2_4_0/queryparsersyntax.html
1957
+
1958
+ === Advanced Queries (DSL)
1959
+
1960
+ The queries above can also be written in a lucene.rb DSL:
1961
+
1962
+ hits = index.find { (name == 'andreas') & (foo == 'bar')}
1963
+
1964
+ Expression with OR (|) is supported, example
1965
+
1966
+ # find all documents with name 'andreas' or age between 30 and 40
1967
+ hits = index.find { (name == 'andreas') | (age == 30..40)}
1968
+
1969
+ === Sorting
1970
+
1971
+ Sorting is specified by the 'sort_by' parameter
1972
+ Example:
1973
+
1974
+ hits = index.find(:name => 'foo', :sort_by=>:category)
1975
+
1976
+ To sort by several fields:
1977
+
1978
+ hits = index.find(:name => 'foo', :sort_by=>[:category, :country])
1979
+
1980
+ Example sort order:
1981
+
1982
+ hits = index.find(:name => 'foo', :sort_by=>[Desc[:category, :country], Asc[:city]])
1983
+
1984
+ === Thread-safety
1985
+
1986
+ The Lucene::Index is thread safe.
1987
+ It guarantees that an index is not updated from two threads at the same time.
1988
+
1989
+
1990
+ === Lucene Transactions
1991
+
1992
+ Use the Lucene::Transaction in order to do atomic commits.
1993
+ By using a transaction you do not need to call the Index.commit method.
1994
+
1995
+ Example:
1996
+
1997
+ Transaction.run do |t|
1998
+ index = Index.new('var/index/foo')
1999
+ index << { id=>42, :name=>'andreas'}
2000
+ t.failure # rollback
2001
+ end
2002
+
2003
+ result = index.find('name' => 'andreas')
2004
+ result.size.should == 0
2005
+
2006
+ You can find uncommitted documents with the uncommitted index property.
2007
+
2008
+ Example:
2009
+
2010
+ index = Index.new('var/index/foo')
2011
+ index.uncommited #=> [document1, document2]
2012
+
2013
+ Notice that even if it looks like a new Index instance object was created the index.uncommitted
2014
+ may return a non-empty array. This is because Index.new is a singleton - a new instance object is not created.
2015
+