neo4j 1.0.0.beta.21-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/CHANGELOG +141 -0
  2. data/CONTRIBUTORS +17 -0
  3. data/Gemfile +16 -0
  4. data/README.rdoc +135 -0
  5. data/lib/generators/neo4j.rb +65 -0
  6. data/lib/generators/neo4j/model/model_generator.rb +39 -0
  7. data/lib/generators/neo4j/model/templates/model.erb +7 -0
  8. data/lib/neo4j.rb +77 -0
  9. data/lib/neo4j/config.rb +153 -0
  10. data/lib/neo4j/database.rb +56 -0
  11. data/lib/neo4j/equal.rb +21 -0
  12. data/lib/neo4j/event_handler.rb +116 -0
  13. data/lib/neo4j/index/class_methods.rb +62 -0
  14. data/lib/neo4j/index/index.rb +33 -0
  15. data/lib/neo4j/index/indexer.rb +312 -0
  16. data/lib/neo4j/index/indexer_registry.rb +68 -0
  17. data/lib/neo4j/index/lucene_query.rb +191 -0
  18. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  19. data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
  20. data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
  21. data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
  22. data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
  23. data/lib/neo4j/load.rb +21 -0
  24. data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
  25. data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
  26. data/lib/neo4j/mapping/class_methods/list.rb +13 -0
  27. data/lib/neo4j/mapping/class_methods/property.rb +82 -0
  28. data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
  29. data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
  30. data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
  31. data/lib/neo4j/mapping/has_list.rb +134 -0
  32. data/lib/neo4j/mapping/has_n.rb +83 -0
  33. data/lib/neo4j/mapping/node_mixin.rb +112 -0
  34. data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
  35. data/lib/neo4j/model.rb +4 -0
  36. data/lib/neo4j/neo4j.rb +95 -0
  37. data/lib/neo4j/node.rb +131 -0
  38. data/lib/neo4j/node_mixin.rb +4 -0
  39. data/lib/neo4j/node_relationship.rb +149 -0
  40. data/lib/neo4j/node_traverser.rb +157 -0
  41. data/lib/neo4j/property.rb +111 -0
  42. data/lib/neo4j/rails/attributes.rb +155 -0
  43. data/lib/neo4j/rails/callbacks.rb +34 -0
  44. data/lib/neo4j/rails/finders.rb +134 -0
  45. data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
  46. data/lib/neo4j/rails/mapping/property.rb +60 -0
  47. data/lib/neo4j/rails/model.rb +105 -0
  48. data/lib/neo4j/rails/persistence.rb +260 -0
  49. data/lib/neo4j/rails/railtie.rb +21 -0
  50. data/lib/neo4j/rails/relationships/mapper.rb +96 -0
  51. data/lib/neo4j/rails/relationships/relationship.rb +30 -0
  52. data/lib/neo4j/rails/relationships/relationships.rb +60 -0
  53. data/lib/neo4j/rails/serialization.rb +25 -0
  54. data/lib/neo4j/rails/timestamps.rb +65 -0
  55. data/lib/neo4j/rails/transaction.rb +67 -0
  56. data/lib/neo4j/rails/tx_methods.rb +15 -0
  57. data/lib/neo4j/rails/validations.rb +38 -0
  58. data/lib/neo4j/rails/validations/non_nil.rb +11 -0
  59. data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
  60. data/lib/neo4j/relationship.rb +169 -0
  61. data/lib/neo4j/relationship_mixin.rb +4 -0
  62. data/lib/neo4j/relationship_traverser.rb +92 -0
  63. data/lib/neo4j/to_java.rb +31 -0
  64. data/lib/neo4j/transaction.rb +68 -0
  65. data/lib/neo4j/type_converters.rb +117 -0
  66. data/lib/neo4j/version.rb +3 -0
  67. data/lib/orm_adapter/adapters/neo4j.rb +55 -0
  68. data/lib/tmp/neo4j/active_tx_log +1 -0
  69. data/lib/tmp/neo4j/index/lucene-store.db +0 -0
  70. data/lib/tmp/neo4j/index/lucene.log.active +0 -0
  71. data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
  72. data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
  73. data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
  74. data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
  75. data/lib/tmp/neo4j/messages.log +85 -0
  76. data/lib/tmp/neo4j/neostore +0 -0
  77. data/lib/tmp/neo4j/neostore.id +0 -0
  78. data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
  79. data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
  80. data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
  81. data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
  82. data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
  83. data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
  84. data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
  85. data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
  86. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
  87. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
  88. data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
  89. data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
  90. data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
  91. data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
  92. data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
  93. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
  94. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
  95. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
  96. data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
  97. data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
  98. data/neo4j.gemspec +31 -0
  99. metadata +216 -0
data/lib/neo4j/load.rb ADDED
@@ -0,0 +1,21 @@
1
+ module Neo4j
2
+
3
+ # === Mixin responsible for loading Ruby wrappers for Neo4j Nodes and Relationship.
4
+ #
5
+ module Load
6
+ def wrapper(node) # :nodoc:
7
+ return node unless node.property?(:_classname)
8
+ to_class(node[:_classname]).load_wrapper(node)
9
+ end
10
+
11
+ def to_class(class_name) # :nodoc:
12
+ class_name.split("::").inject(Kernel) {|container, name| container.const_get(name.to_s) }
13
+ end
14
+
15
+ # Checks if the given entity (node/relationship) or entity id (#neo_id) exists in the database.
16
+ def exist?(node_or_node_id, db = Neo4j.started_db)
17
+ id = node_or_node_id.kind_of?(Fixnum) ? node_or_node_id : node_or_node_id.id
18
+ _load(id, db) != nil
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,50 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+ module InitNode
4
+
5
+ def load_wrapper(node)
6
+ wrapped_node = self.orig_new
7
+ wrapped_node.init_on_load(node)
8
+ wrapped_node
9
+ end
10
+
11
+
12
+ # Creates a new node or loads an already existing Neo4j node.
13
+ #
14
+ # You can use two callback method to initialize the node
15
+ # init_on_load:: this method is called when the node is loaded from the database
16
+ # init_on_create:: called when the node is created, will be provided with the same argument as the new method
17
+ #
18
+ #
19
+ # Does
20
+ # * sets the neo4j property '_classname' to self.class.to_s
21
+ # * creates a neo4j node java object (in @_java_node)
22
+ #
23
+ # If you want to provide your own initialize method you should instead implement the
24
+ # method init_on_create method.
25
+ #
26
+ # === Example
27
+ #
28
+ # class MyNode
29
+ # include Neo4j::NodeMixin
30
+ #
31
+ # def init_on_create(name, age)
32
+ # self[:name] = name
33
+ # self[:age] = age
34
+ # end
35
+ # end
36
+ #
37
+ # node = MyNode.new('jimmy', 23)
38
+ #
39
+ def new(*args)
40
+ node = Neo4j::Node.create
41
+ wrapped_node = super()
42
+ wrapped_node.init_on_load(node)
43
+ wrapped_node.init_on_create(*args)
44
+ wrapped_node
45
+ end
46
+
47
+ alias_method :create, :new
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,35 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+ module InitRel
4
+ def load_wrapper(rel)
5
+ wrapped_rel = self.orig_new
6
+ wrapped_rel.init_on_load(rel)
7
+ wrapped_rel
8
+ end
9
+
10
+
11
+ # Creates a relationship between given nodes.
12
+ #
13
+ # You can use two callback method to initialize the relationship
14
+ # init_on_load:: this method is called when the relationship is loaded from the database
15
+ # init_on_create:: called when the relationship is created, will be provided with the same argument as the new method
16
+ #
17
+ # ==== Parameters (when creating a new relationship in db)
18
+ # type:: the key and value to be set
19
+ # from_node:: create relationship from this node
20
+ # to_node:: create relationship to this node
21
+ # props:: optional hash of properties to initialize the create relationship with
22
+ #
23
+ def new(*args)
24
+ type, from_node, to_node, props = args
25
+ rel = Neo4j::Relationship.create(type, from_node, to_node)
26
+ wrapped_rel = super()
27
+ wrapped_rel.init_on_load(rel)
28
+ wrapped_rel.init_on_create(*args)
29
+ wrapped_rel
30
+ end
31
+
32
+ alias_method :create, :new
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,13 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+ module List
4
+ def has_list(name, params = {})
5
+ module_eval(%Q{
6
+ def #{name}
7
+ Neo4j::Mapping::HasList.new(self, '#{name}')
8
+ end}, __FILE__, __LINE__)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,82 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+ module Property
4
+
5
+ # Generates accessor method and sets configuration for Neo4j node properties.
6
+ # The generated accessor is a simple wrapper around the #[] and
7
+ # #[]= operators.
8
+ #
9
+ # ==== Types
10
+ # If a property is set to nil the property will be removed.
11
+ # A property can be of any primitive type (Boolean, String, Fixnum, Float) and does not
12
+ # even have to be the same. Arrays of primitive types is also supported. Array values must
13
+ # be of the same type and are mutable, e.g. you have to create a new array if you want to change one value.
14
+ #
15
+ # Example:
16
+ # class Foo
17
+ # include Neo4j::NodeMixin
18
+ # property :age
19
+ # end
20
+ #
21
+ # Example:
22
+ # foo = Foo.new
23
+ # foo.age = "hej" # first set it to string
24
+ # foo.age = 42 # change it to a Fixnum
25
+ #
26
+ # However, you can specify an type for the index, see Neo4j::Index::Indexer#index
27
+ #
28
+ # ==== Conversions
29
+ #
30
+ # It is possible to do conversions between types in order to support none primitive types
31
+ # Example:
32
+ #
33
+ # class Foo
34
+ # include Neo4j::NodeMixin
35
+ # property :since, :type => DateTime # will be converted into a fixnum
36
+ # end
37
+ #
38
+ # You can write your own converter and register it in the Neo4j::Config.
39
+ #
40
+ def property(*props)
41
+ if props.size == 2 and props[1].kind_of?(Hash)
42
+ props[1].each_pair do |key, value|
43
+ pname = props[0].to_sym
44
+ _decl_props[pname] ||= {}
45
+ _decl_props[pname][key] = value
46
+ end
47
+ props = props[0..0]
48
+ end
49
+
50
+ props.each do |prop|
51
+ pname = prop.to_sym
52
+ _decl_props[pname] ||= {}
53
+ _decl_props[pname][:defined] = true
54
+
55
+ define_method(pname) do
56
+ Neo4j::TypeConverters.to_ruby(self.class, pname, self[pname])
57
+ end
58
+
59
+ name = (pname.to_s() +"=").to_sym
60
+ define_method(name) do |value|
61
+ self[pname] = Neo4j::TypeConverters.to_java(self.class, pname, value)
62
+ end
63
+ end
64
+ end
65
+
66
+
67
+ # Returns true if the given property name has been defined with the class
68
+ # method property or properties.
69
+ #
70
+ # Notice that the node may have properties that has not been declared.
71
+ # It is always possible to set an undeclared property on a node.
72
+ #
73
+ # ==== Returns
74
+ # true or false
75
+ #
76
+ def property?(prop_name)
77
+ return false if _decl_props[prop_name.to_sym].nil?
78
+ _decl_props[prop_name.to_sym][:defined] == true
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,91 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+
4
+ module Relationship
5
+ include Neo4j::ToJava
6
+
7
+ # Specifies a relationship between two node classes.
8
+ # Generates assignment and accessor methods for the given relationship.
9
+ # Both incoming and outgoing relationships can be declared, see Neo4j::Mapping::DeclRelationshipDsl
10
+ #
11
+ # ==== Example
12
+ #
13
+ # class FolderNode
14
+ # include Ne4j::NodeMixin
15
+ # has_n(:files)
16
+ # end
17
+ #
18
+ # folder = FolderNode.new
19
+ # folder.files << Neo4j::Node.new << Neo4j::Node.new
20
+ # folder.files.inject {...}
21
+ #
22
+ # ==== Returns
23
+ #
24
+ # Neo4j::Mapping::DeclRelationshipDsl
25
+ #
26
+ def has_n(rel_type, params = {})
27
+ clazz = self
28
+ module_eval(%Q{
29
+ def #{rel_type}
30
+ dsl = _decl_rels_for('#{rel_type}'.to_sym)
31
+ Neo4j::Mapping::HasN.new(self, dsl)
32
+ end}, __FILE__, __LINE__)
33
+
34
+ module_eval(%Q{
35
+ def #{rel_type}_rels
36
+ dsl = #{clazz}._decl_rels[:'#{rel_type.to_s}']
37
+ dsl.all_relationships(self)
38
+ end}, __FILE__, __LINE__)
39
+
40
+ _decl_rels[rel_type.to_sym] = Neo4j::Mapping::DeclRelationshipDsl.new(rel_type, false, clazz, params)
41
+ end
42
+
43
+
44
+ # Specifies a relationship between two node classes.
45
+ # Generates assignment and accessor methods for the given relationship
46
+ # Old relationship is deleted when a new relationship is assigned.
47
+ # Both incoming and outgoing relationships can be declared, see Neo4j::Mapping::DeclRelationshipDsl
48
+ #
49
+ # ==== Example
50
+ #
51
+ # class FileNode
52
+ # include Ne4j::NodeMixin
53
+ # has_one(:folder)
54
+ # end
55
+ #
56
+ # file = FileNode.new
57
+ # file.folder = Neo4j::Node.new
58
+ # file.folder # => the node above
59
+ # file.folder_rel # => the relationship object between those nodes
60
+ #
61
+ # ==== Returns
62
+ #
63
+ # Neo4j::Mapping::DeclRelationshipDsl
64
+ #
65
+ def has_one(rel_type, params = {})
66
+ clazz = self
67
+ module_eval(%Q{def #{rel_type}=(value)
68
+ dsl = _decl_rels_for(:#{rel_type})
69
+ rel = dsl.single_relationship(self)
70
+ rel.del unless rel.nil?
71
+ dsl.create_relationship_to(self, value) if value
72
+ end}, __FILE__, __LINE__)
73
+
74
+ module_eval(%Q{def #{rel_type}
75
+ #dsl = #{clazz}._decl_rels[:#{rel_type}]
76
+ dsl = _decl_rels_for(:#{rel_type})
77
+ dsl.single_node(self)
78
+ end}, __FILE__, __LINE__)
79
+
80
+ module_eval(%Q{def #{rel_type}_rel
81
+ dsl = #{clazz}._decl_rels[:'#{rel_type.to_s}']
82
+ # dsl = _decl_rels_for(:#{rel_type})
83
+ dsl.single_relationship(self)
84
+ end}, __FILE__, __LINE__)
85
+
86
+ _decl_rels[rel_type.to_sym] = Neo4j::Mapping::DeclRelationshipDsl.new(rel_type, true, clazz, params)
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,295 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+ # Holds all defined rules and trigger them when an event is received.
4
+ #
5
+ # See Rule
6
+ #
7
+ class Rules
8
+ class << self
9
+ def add(clazz, field, props, &block)
10
+ clazz = clazz.to_s
11
+ @rules ||= {}
12
+ # was there no ruls for this class AND is neo4j running ?
13
+ if !@rules.include?(clazz) && Neo4j.running?
14
+ # maybe Neo4j was started first and the rules was added later. Create rule nodes now
15
+ create_rule_node_for(clazz)
16
+ end
17
+ @rules[clazz] ||= {}
18
+ filter = block.nil? ? Proc.new { |*| true } : block
19
+ @rules[clazz][field] = filter
20
+ @triggers ||= {}
21
+ @triggers[clazz] ||= {}
22
+ trigger = props[:trigger].nil? ? [] : props[:trigger]
23
+ @triggers[clazz][field] = trigger.respond_to?(:each) ? trigger : [trigger]
24
+ end
25
+
26
+ def inherit(parent_class, subclass)
27
+ # copy all the rules
28
+ @rules[parent_class.to_s].each_pair do |field, filter|
29
+ subclass.rule field, &filter
30
+ end if @rules[parent_class.to_s]
31
+ end
32
+
33
+ def trigger_other_rules(node)
34
+ clazz = node[:_classname]
35
+ @rules[clazz].keys.each do |field|
36
+ rel_types = @triggers[clazz][field]
37
+ rel_types.each do |rel_type|
38
+ node.incoming(rel_type).each { |n| n.trigger_rules }
39
+ end
40
+ end
41
+ end
42
+
43
+ def fields_for(clazz)
44
+ clazz = clazz.to_s
45
+ return [] if @rules.nil? || @rules[clazz].nil?
46
+ @rules[clazz].keys
47
+ end
48
+
49
+ def delete(clazz)
50
+ clazz = clazz.to_s
51
+ # delete the rule node if found
52
+ if Neo4j.ref_node.rel?(clazz)
53
+ Neo4j.ref_node.outgoing(clazz).each { |n| n.del }
54
+ end
55
+ @rules.delete(clazz) if @rules
56
+ end
57
+
58
+ def on_neo4j_started(*)
59
+ @rules.each_key { |clazz| create_rule_node_for(clazz) } if @rules
60
+ end
61
+
62
+ def create_rule_node_for(clazz)
63
+ if !Neo4j.ref_node.rel?(clazz)
64
+ Neo4j::Transaction.run do
65
+ node = Neo4j::Node.new
66
+ Neo4j.ref_node.outgoing(clazz) << node
67
+ node
68
+ end
69
+ end
70
+ end
71
+
72
+ def trigger?(node)
73
+ @rules && node.property?(:_classname) && @rules.include?(node[:_classname])
74
+ end
75
+
76
+ def rule_for(clazz)
77
+ if Neo4j.ref_node.rel?(clazz)
78
+ Neo4j.ref_node._rel(:outgoing, clazz)._end_node
79
+ else
80
+ # this should be called if the rule node gets deleted
81
+ create_rule_node_for(clazz)
82
+ end
83
+ end
84
+
85
+
86
+ def on_relationship_created(rel, *)
87
+ trigger_start_node = trigger?(rel._start_node)
88
+ trigger_end_node = trigger?(rel._end_node)
89
+ # end or start node must be triggered by this event
90
+ return unless trigger_start_node || trigger_end_node
91
+ on_property_changed(trigger_start_node ? rel._start_node : rel._end_node)
92
+ end
93
+
94
+
95
+ def on_property_changed(node, *)
96
+ trigger_rules(node) if trigger?(node)
97
+ end
98
+
99
+ def trigger_rules(node)
100
+ trigger_rules_for_class(node, node[:_classname])
101
+ trigger_other_rules(node)
102
+ end
103
+
104
+ def trigger_rules_for_class(node, clazz)
105
+ return if @rules[clazz].nil?
106
+
107
+ agg_node = rule_for(clazz)
108
+ @rules[clazz].each_pair do |field, rule|
109
+ if run_rule(rule, node)
110
+ # is this node already included ?
111
+ unless connected?(field, agg_node, node)
112
+ agg_node.outgoing(field) << node
113
+ end
114
+ else
115
+ # remove old ?
116
+ break_connection(field, agg_node, node)
117
+ end
118
+ end
119
+
120
+ # recursively add relationships for all the parent classes with rules that also pass for this node
121
+ if clazz = eval("#{clazz}.superclass")
122
+ trigger_rules_for_class(node, clazz.to_s)
123
+ end
124
+ end
125
+
126
+ # work out if two nodes are connected by a particular relationship
127
+ # uses the end_node to start with because it's more likely to have less relationships to go through
128
+ # (just the number of superclasses it has really)
129
+ def connected?(relationship, start_node, end_node)
130
+ end_node.incoming(relationship).each do |n|
131
+ return true if n == start_node
132
+ end
133
+ false
134
+ end
135
+
136
+ # sever a direct one-to-one relationship if it exists
137
+ def break_connection(relationship, start_node, end_node)
138
+ end_node.rels(relationship).incoming.each do |r|
139
+ return r.del if r.start_node == start_node
140
+ end
141
+ end
142
+
143
+ def run_rule(rule, node)
144
+ if rule.arity != 1
145
+ node.wrapper.instance_eval(&rule)
146
+ else
147
+ rule.call(node)
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+
154
+ # Allows you to group nodes by providing a rule.
155
+ #
156
+ # === Example, finding all nodes of a certain class
157
+ # Just add a rule without a code block, then all nodes of that class will be grouped under the given key (<tt>all</tt>
158
+ # for the example below).
159
+ #
160
+ # class Person
161
+ # include Neo4j::NodeMixin
162
+ # rule :all
163
+ # end
164
+ #
165
+ # Then you can get all the nodes of type Person (and siblings) by
166
+ # Person.all.each {|x| ...}
167
+ #
168
+ # === Example, finding all nodes with a given condition on a property
169
+ #
170
+ # class Person
171
+ # include Neo4j::NodeMixin
172
+ # property :age
173
+ # rule(:old) { age > 10 }
174
+ # end
175
+ #
176
+ # Now we can find all nodes with a property <tt>age</tt> above 10.
177
+ #
178
+ # === Chain Rules
179
+ #
180
+ # class NewsStory
181
+ # include Neo4j::NodeMixin
182
+ # has_n :readers
183
+ # rule(:featured) { |node| node[:featured] == true }
184
+ # rule(:young_readers) { !readers.find{|user| !user.young?}}
185
+ # end
186
+ #
187
+ # You can combine two rules. Let say you want to find all stories which are featured and has young readers:
188
+ # NewsStory.featured.young_readers.each {...}
189
+ #
190
+ # === Trigger Other Rules
191
+ # You can let one rule trigger another rule.
192
+ # Let say you have readers of some magazine and want to know if the magazine has old or young readers.
193
+ # So when a reader change from young to old you want to trigger all the magazine that he reads (a but stupid example)
194
+ #
195
+ # Example
196
+ # class Reader
197
+ # include Neo4j::NodeMixin
198
+ # property :age
199
+ # rule(:young, :trigger => :readers) { age < 15 }
200
+ # end
201
+ #
202
+ # class NewsStory
203
+ # include Neo4j::NodeMixin
204
+ # has_n :readers
205
+ # rule(:young_readers) { !readers.find{|user| !user.young?}}
206
+ # end
207
+ #
208
+ # === Performance Considerations
209
+ # If you have many rules and many updates this can be a bit slow.
210
+ # In order to speed it up somewhat you can use the raw java node object instead by providing an argument in your block.
211
+ #
212
+ # Example:
213
+ #
214
+ # class Person
215
+ # include Neo4j::NodeMixin
216
+ # property :age
217
+ # rule(:old) {|node| node[:age] > 10 }
218
+ # end
219
+ #
220
+ # === Thread Safe ?
221
+ # Not sure...
222
+ #
223
+ module Rule
224
+
225
+ # Creates an rule node attached to the Neo4j.ref_node
226
+ # Can be used to rule all instances of a specific Ruby class.
227
+ #
228
+ # Example of usage:
229
+ # class Person
230
+ # include Neo4j
231
+ # rule :all
232
+ # rule :young { self[:age] < 10 }
233
+ # end
234
+ #
235
+ # p1 = Person.new :age => 5
236
+ # p2 = Person.new :age => 7
237
+ # p3 = Person.new :age => 12
238
+ # Neo4j::Transaction.finish
239
+ # Person.all # => [p1,p2,p3]
240
+ # Person.young # => [p1,p2]
241
+ # p1.young? # => true
242
+ #
243
+ def rule(name, props = {}, &block)
244
+ singelton = class << self;
245
+ self;
246
+ end
247
+
248
+ # define class methods
249
+ singelton.send(:define_method, name) do
250
+ agg_node = Rules.rule_for(self)
251
+ raise "no rule node for #{name} on #{self}" if agg_node.nil?
252
+ traversal = agg_node.outgoing(name) # TODO possible to cache this object
253
+ Rules.fields_for(self).each do |filter_name|
254
+ traversal.filter_method(filter_name) do |path|
255
+ path.end_node.rel?(filter_name, :incoming)
256
+ end
257
+ end
258
+ traversal
259
+ end unless respond_to?(name)
260
+
261
+ # define instance methods
262
+ self.send(:define_method, "#{name}?") do
263
+ instance_eval &block
264
+ end
265
+
266
+ Rules.add(self, name, props, &block)
267
+ end
268
+
269
+ def inherit_rules_from(clazz)
270
+ Rules.inherit(clazz, self)
271
+ end
272
+
273
+ # This is typically used for RSpecs to clean up rule nodes created by the #rule method.
274
+ # It also remove the given class method.
275
+ def delete_rules
276
+ singelton = class << self;
277
+ self;
278
+ end
279
+ Rules.fields_for(self).each do |name|
280
+ singelton.send(:remove_method, name)
281
+ end
282
+ Rules.delete(self)
283
+ end
284
+
285
+ # Force to trigger the rules.
286
+ # You don't normally need that since it will be done automatically.
287
+ def trigger_rules(node)
288
+ Rules.trigger_rules(node)
289
+ end
290
+
291
+ end
292
+
293
+ Neo4j.unstarted_db.event_handler.add(Rules)
294
+ end
295
+ end