neo4j 1.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +141 -0
- data/CONTRIBUTORS +15 -0
- data/Gemfile +3 -0
- data/README.rdoc +2015 -0
- data/lib/neo4j.old/batch_inserter.rb +144 -0
- data/lib/neo4j.old/config.rb +138 -0
- data/lib/neo4j.old/event_handler.rb +73 -0
- data/lib/neo4j.old/extensions/activemodel.rb +158 -0
- data/lib/neo4j.old/extensions/aggregate.rb +12 -0
- data/lib/neo4j.old/extensions/aggregate/aggregate_enum.rb +40 -0
- data/lib/neo4j.old/extensions/aggregate/ext/node_mixin.rb +69 -0
- data/lib/neo4j.old/extensions/aggregate/node_aggregate.rb +8 -0
- data/lib/neo4j.old/extensions/aggregate/node_aggregate_mixin.rb +331 -0
- data/lib/neo4j.old/extensions/aggregate/node_aggregator.rb +216 -0
- data/lib/neo4j.old/extensions/aggregate/node_group.rb +43 -0
- data/lib/neo4j.old/extensions/aggregate/prop_group.rb +30 -0
- data/lib/neo4j.old/extensions/aggregate/property_enum.rb +24 -0
- data/lib/neo4j.old/extensions/aggregate/props_aggregate.rb +8 -0
- data/lib/neo4j.old/extensions/aggregate/props_aggregate_mixin.rb +31 -0
- data/lib/neo4j.old/extensions/aggregate/props_aggregator.rb +80 -0
- data/lib/neo4j.old/extensions/find_path.rb +117 -0
- data/lib/neo4j.old/extensions/graph_algo.rb +1 -0
- data/lib/neo4j.old/extensions/graph_algo/all_simple_paths.rb +133 -0
- data/lib/neo4j.old/extensions/graph_algo/neo4j-graph-algo-0.3.jar +0 -0
- data/lib/neo4j.old/extensions/reindexer.rb +104 -0
- data/lib/neo4j.old/extensions/rest.rb +21 -0
- data/lib/neo4j.old/extensions/rest/rest.rb +336 -0
- data/lib/neo4j.old/extensions/rest/rest_mixin.rb +193 -0
- data/lib/neo4j.old/extensions/rest/server.rb +50 -0
- data/lib/neo4j.old/extensions/rest/stubs.rb +141 -0
- data/lib/neo4j.old/extensions/rest_master.rb +34 -0
- data/lib/neo4j.old/extensions/rest_slave.rb +31 -0
- data/lib/neo4j.old/extensions/tx_tracker.rb +392 -0
- data/lib/neo4j.old/indexer.rb +187 -0
- data/lib/neo4j.old/jars.rb +6 -0
- data/lib/neo4j.old/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j.old/jars/neo4j-kernel-1.0.jar +0 -0
- data/lib/neo4j.old/mixins/java_list_mixin.rb +139 -0
- data/lib/neo4j.old/mixins/java_node_mixin.rb +205 -0
- data/lib/neo4j.old/mixins/java_property_mixin.rb +169 -0
- data/lib/neo4j.old/mixins/java_relationship_mixin.rb +60 -0
- data/lib/neo4j.old/mixins/migration_mixin.rb +157 -0
- data/lib/neo4j.old/mixins/node_mixin.rb +249 -0
- data/lib/neo4j.old/mixins/property_class_methods.rb +265 -0
- data/lib/neo4j.old/mixins/rel_class_methods.rb +167 -0
- data/lib/neo4j.old/mixins/relationship_mixin.rb +103 -0
- data/lib/neo4j.old/neo.rb +247 -0
- data/lib/neo4j.old/node.rb +49 -0
- data/lib/neo4j.old/reference_node.rb +15 -0
- data/lib/neo4j.old/relationship.rb +85 -0
- data/lib/neo4j.old/relationships/decl_relationship_dsl.rb +164 -0
- data/lib/neo4j.old/relationships/has_list.rb +101 -0
- data/lib/neo4j.old/relationships/has_n.rb +129 -0
- data/lib/neo4j.old/relationships/node_traverser.rb +138 -0
- data/lib/neo4j.old/relationships/relationship_dsl.rb +149 -0
- data/lib/neo4j.old/relationships/traversal_position.rb +50 -0
- data/lib/neo4j.old/relationships/wrappers.rb +51 -0
- data/lib/neo4j.old/search_result.rb +72 -0
- data/lib/neo4j.old/transaction.rb +254 -0
- data/lib/neo4j.old/version.rb +3 -0
- data/lib/neo4j.rb +50 -0
- data/lib/neo4j/config.rb +137 -0
- data/lib/neo4j/database.rb +43 -0
- data/lib/neo4j/equal.rb +22 -0
- data/lib/neo4j/event_handler.rb +91 -0
- data/lib/neo4j/index.rb +157 -0
- data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j/jars/lucene-core-2.9.2.jar +0 -0
- data/lib/neo4j/jars/lucene-core-3.0.1.jar +0 -0
- data/lib/neo4j/jars/neo4j-index-1.1.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-1.1.1.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-1.1.jar +0 -0
- data/lib/neo4j/jars/neo4j-lucene-index-0.1-20100916.085626-67.jar +0 -0
- data/lib/neo4j/mapping/class_methods/index.rb +21 -0
- data/lib/neo4j/mapping/class_methods/property.rb +139 -0
- data/lib/neo4j/mapping/class_methods/relationship.rb +96 -0
- data/lib/neo4j/mapping/class_methods/rule.rb +135 -0
- data/lib/neo4j/mapping/decl_relationship_dsl.rb +151 -0
- data/lib/neo4j/mapping/has_n.rb +117 -0
- data/lib/neo4j/mapping/node_mixin.rb +70 -0
- data/lib/neo4j/neo4j.rb +65 -0
- data/lib/neo4j/node.rb +82 -0
- data/lib/neo4j/node_mixin.rb +4 -0
- data/lib/neo4j/node_relationship.rb +60 -0
- data/lib/neo4j/node_traverser.rb +141 -0
- data/lib/neo4j/property.rb +72 -0
- data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
- data/lib/neo4j/rails/model.rb +210 -0
- data/lib/neo4j/rails/railtie.rb +16 -0
- data/lib/neo4j/rails/transaction.rb +29 -0
- data/lib/neo4j/rails/value.rb +43 -0
- data/lib/neo4j/relationship.rb +88 -0
- data/lib/neo4j/relationship_traverser.rb +57 -0
- data/lib/neo4j/to_java.rb +17 -0
- data/lib/neo4j/transaction.rb +69 -0
- data/lib/neo4j/version.rb +3 -0
- data/neo4j.gemspec +30 -0
- 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
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
|
+
|