neo4j-wrapper 0.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/Gemfile +10 -0
  2. data/README.rdoc +64 -0
  3. data/lib/db/active_tx_log +1 -0
  4. data/lib/db/index/lucene/node/Person_exact/_0.fdt +0 -0
  5. data/lib/db/index/lucene/node/Person_exact/_0.fdx +0 -0
  6. data/lib/db/index/lucene/node/Person_exact/_0.fnm +1 -0
  7. data/lib/db/index/lucene/node/Person_exact/_0.frq +0 -0
  8. data/lib/db/index/lucene/node/Person_exact/_0.nrm +1 -0
  9. data/lib/db/index/lucene/node/Person_exact/_0.prx +0 -0
  10. data/lib/db/index/lucene/node/Person_exact/_0.tii +0 -0
  11. data/lib/db/index/lucene/node/Person_exact/_0.tis +0 -0
  12. data/lib/db/index/lucene/node/Person_exact/_1.fdt +0 -0
  13. data/lib/db/index/lucene/node/Person_exact/_1.fdx +0 -0
  14. data/lib/db/index/lucene/node/Person_exact/_1.fnm +1 -0
  15. data/lib/db/index/lucene/node/Person_exact/_1.frq +0 -0
  16. data/lib/db/index/lucene/node/Person_exact/_1.nrm +1 -0
  17. data/lib/db/index/lucene/node/Person_exact/_1.prx +0 -0
  18. data/lib/db/index/lucene/node/Person_exact/_1.tii +0 -0
  19. data/lib/db/index/lucene/node/Person_exact/_1.tis +0 -0
  20. data/lib/db/index/lucene/node/Person_exact/_2.fdt +0 -0
  21. data/lib/db/index/lucene/node/Person_exact/_2.fdx +0 -0
  22. data/lib/db/index/lucene/node/Person_exact/_2.fnm +1 -0
  23. data/lib/db/index/lucene/node/Person_exact/_2.frq +0 -0
  24. data/lib/db/index/lucene/node/Person_exact/_2.nrm +1 -0
  25. data/lib/db/index/lucene/node/Person_exact/_2.prx +0 -0
  26. data/lib/db/index/lucene/node/Person_exact/_2.tii +0 -0
  27. data/lib/db/index/lucene/node/Person_exact/_2.tis +0 -0
  28. data/lib/db/index/lucene/node/Person_exact/segments.gen +0 -0
  29. data/lib/db/index/lucene/node/Person_exact/segments_3 +0 -0
  30. data/lib/db/index/lucene-store.db +0 -0
  31. data/lib/db/index/lucene.log.active +0 -0
  32. data/lib/db/index.db +0 -0
  33. data/lib/db/messages.log +826 -0
  34. data/lib/db/neostore +0 -0
  35. data/lib/db/neostore.id +0 -0
  36. data/lib/db/neostore.nodestore.db +0 -0
  37. data/lib/db/neostore.nodestore.db.id +0 -0
  38. data/lib/db/neostore.propertystore.db +0 -0
  39. data/lib/db/neostore.propertystore.db.arrays +0 -0
  40. data/lib/db/neostore.propertystore.db.arrays.id +0 -0
  41. data/lib/db/neostore.propertystore.db.id +0 -0
  42. data/lib/db/neostore.propertystore.db.index +0 -0
  43. data/lib/db/neostore.propertystore.db.index.id +0 -0
  44. data/lib/db/neostore.propertystore.db.index.keys +0 -0
  45. data/lib/db/neostore.propertystore.db.index.keys.id +0 -0
  46. data/lib/db/neostore.propertystore.db.strings +0 -0
  47. data/lib/db/neostore.propertystore.db.strings.id +0 -0
  48. data/lib/db/neostore.relationshipstore.db +0 -0
  49. data/lib/db/neostore.relationshipstore.db.id +0 -0
  50. data/lib/db/neostore.relationshiptypestore.db +0 -0
  51. data/lib/db/neostore.relationshiptypestore.db.id +0 -0
  52. data/lib/db/neostore.relationshiptypestore.db.names +0 -0
  53. data/lib/db/neostore.relationshiptypestore.db.names.id +0 -0
  54. data/lib/db/nioneo_logical.log.active +0 -0
  55. data/lib/db/tm_tx_log.1 +0 -0
  56. data/lib/example.rb +34 -0
  57. data/lib/neo4j/identity_map.rb +109 -0
  58. data/lib/neo4j/node_mixin.rb +74 -0
  59. data/lib/neo4j/relationship_mixin.rb +62 -0
  60. data/lib/neo4j/type_converters/type_converters.rb +333 -0
  61. data/lib/neo4j-wrapper/class_methods.rb +27 -0
  62. data/lib/neo4j-wrapper/find.rb +65 -0
  63. data/lib/neo4j-wrapper/has_n/class_methods.rb +131 -0
  64. data/lib/neo4j-wrapper/has_n/decl_rel.rb +261 -0
  65. data/lib/neo4j-wrapper/has_n/instance_methods.rb +12 -0
  66. data/lib/neo4j-wrapper/has_n/nodes.rb +83 -0
  67. data/lib/neo4j-wrapper/node_mixin/class_methods.rb +53 -0
  68. data/lib/neo4j-wrapper/node_mixin/delegates.rb +140 -0
  69. data/lib/neo4j-wrapper/node_mixin/initialize.rb +43 -0
  70. data/lib/neo4j-wrapper/properties/class_methods.rb +150 -0
  71. data/lib/neo4j-wrapper/relationship_mixin/class_methods.rb +30 -0
  72. data/lib/neo4j-wrapper/relationship_mixin/delegates.rb +110 -0
  73. data/lib/neo4j-wrapper/relationship_mixin/initialize.rb +35 -0
  74. data/lib/neo4j-wrapper/rule/class_methods.rb +204 -0
  75. data/lib/neo4j-wrapper/rule/event_listener.rb +66 -0
  76. data/lib/neo4j-wrapper/rule/functions/count.rb +45 -0
  77. data/lib/neo4j-wrapper/rule/functions/function.rb +76 -0
  78. data/lib/neo4j-wrapper/rule/functions/sum.rb +31 -0
  79. data/lib/neo4j-wrapper/rule/instance_methods.rb +16 -0
  80. data/lib/neo4j-wrapper/rule/neo4j_core_ext/traverser.rb +29 -0
  81. data/lib/neo4j-wrapper/rule/rule.rb +134 -0
  82. data/lib/neo4j-wrapper/rule/rule_node.rb +205 -0
  83. data/lib/neo4j-wrapper/version.rb +5 -0
  84. data/lib/neo4j-wrapper/wrapper.rb +38 -0
  85. data/lib/neo4j-wrapper.rb +35 -0
  86. data/lib/test.rb +61 -0
  87. data/neo4j-wrapper.gemspec +31 -0
  88. metadata +162 -0
@@ -0,0 +1,204 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+
5
+
6
+ # Allows you to group nodes by providing a rule.
7
+ #
8
+ # === Example, finding all nodes of a certain class
9
+ # Just add a rule without a code block, then all nodes of that class will be grouped under the given key (<tt>all</tt>
10
+ # for the example below).
11
+ #
12
+ # class Person
13
+ # include Neo4j::NodeMixin
14
+ # rule :all
15
+ # end
16
+ #
17
+ # Then you can get all the nodes of type Person (and siblings) by
18
+ # Person.all.each {|x| ...}
19
+ #
20
+ # === Example, finding all nodes with a given condition on a property
21
+ #
22
+ # class Person
23
+ # include Neo4j::NodeMixin
24
+ # property :age
25
+ # rule(:old) { age > 10 }
26
+ # end
27
+ #
28
+ # Now we can find all nodes with a property <tt>age</tt> above 10.
29
+ #
30
+ # === Chain Rules
31
+ #
32
+ # class NewsStory
33
+ # include Neo4j::NodeMixin
34
+ # has_n :readers
35
+ # rule(:featured) { |node| node[:featured] == true }
36
+ # rule(:young_readers) { !readers.find{|user| !user.young?}}
37
+ # end
38
+ #
39
+ # You can combine two rules. Let say you want to find all stories which are featured and has young readers:
40
+ # NewsStory.featured.young_readers.each {...}
41
+ #
42
+ # === Trigger Other Rules
43
+ # You can let one rule trigger another rule.
44
+ # Let say you have readers of some magazine and want to know if the magazine has old or young readers.
45
+ # So when a reader change from young to old you want to trigger all the magazine that he reads (a but stupid example)
46
+ #
47
+ # Example
48
+ # class Reader
49
+ # include Neo4j::NodeMixin
50
+ # property :age
51
+ # rule(:young, :triggers => :readers) { age < 15 }
52
+ # end
53
+ #
54
+ # class NewsStory
55
+ # include Neo4j::NodeMixin
56
+ # has_n :readers
57
+ # rule(:young_readers) { !readers.find{|user| !user.young?}}
58
+ # end
59
+ #
60
+ # === Performance Considerations
61
+ # If you have many rules and many updates this can be a bit slow.
62
+ # In order to speed it up somewhat you can use the raw java node object instead by providing an argument in your block.
63
+ #
64
+ # Example:
65
+ #
66
+ # class Person
67
+ # include Neo4j::NodeMixin
68
+ # property :age
69
+ # rule(:old) {|node| node[:age] > 10 }
70
+ # end
71
+ #
72
+ # === Thread Safe ?
73
+ # Yes, since operations are performed in an transaction. However you may get a deadlock exception:
74
+ # @see http://docs.neo4j.org/chunked/stable/transactions-deadlocks.html Transaction Deadlock
75
+ #
76
+ module ClassMethods
77
+
78
+ # Creates an rule node attached to the Neo4j.ref_node
79
+ # Can be used to rule all instances of a specific Ruby class.
80
+ #
81
+ # Example of usage:
82
+ # class Person
83
+ # include Neo4j::NodeMixin
84
+ # property :age
85
+ # rule :all
86
+ # rule :young { self[:age] < 10 }
87
+ # rule(:old, :functions => [Sum.new[:age]) { age > 20 }
88
+ # end
89
+ #
90
+ # p1 = Person.new :age => 5
91
+ # p2 = Person.new :age => 7
92
+ # p3 = Person.new :age => 12
93
+ # Neo4j::Transaction.finish
94
+ # Person.all # => [p1,p2,p3]
95
+ # Person.young # => [p1,p2]
96
+ # p1.young? # => true
97
+ # p1.sum(old, :age) # the some of the old people's age
98
+ #
99
+ def rule(rule_name, props = {}, &block)
100
+ singleton = class << self;
101
+ self;
102
+ end
103
+
104
+ # define class methods
105
+ singleton.send(:define_method, rule_name) do
106
+ rule_node = Rule.rule_node_for(self)
107
+ rule_node.traversal(rule_name)
108
+ end unless respond_to?(rule_name)
109
+
110
+ # define instance methods
111
+ self.send(:define_method, "#{rule_name}?") do
112
+ instance_eval &block
113
+ end
114
+
115
+ rule = Rule.add(self, rule_name, props, &block)
116
+
117
+ rule.functions && rule.functions.each do |func|
118
+ singleton.send(:define_method, func.class.function_name) do |r_name, *args|
119
+ rule_node = Rule.rule_node_for(self)
120
+ function_id = args.empty? ? "_classname" : args[0]
121
+ function = rule_node.find_function(r_name, func.class.function_name, function_id)
122
+ function.value(rule_node.rule_node, r_name)
123
+ end unless respond_to?(func.class.function_name)
124
+ end
125
+ end
126
+
127
+ def ref_node_for_class
128
+ Neo4j.ref_node #The reference node for a type falls back to the threadlocal ref node by default.
129
+ end
130
+
131
+ # Assigns the reference node for a class via a supplied block.
132
+ # Example of usage:
133
+ # class Person
134
+ # include Neo4j::NodeMixin
135
+ # ref_node { Neo4j.default_ref_node }
136
+ # end
137
+ #
138
+ def ref_node(&block)
139
+ singleton = class << self;
140
+ self;
141
+ end
142
+ singleton.send(:define_method, :ref_node_for_class) { block.call }
143
+ end
144
+
145
+ def inherit_rules_from(clazz)
146
+ Rule.inherit(clazz, self)
147
+ end
148
+
149
+ # This is typically used for RSpecs to clean up rule nodes created by the #rule method.
150
+ # It also remove all the rule class methods.
151
+ def delete_rules
152
+ singelton = class << self;
153
+ self;
154
+ end
155
+ rule_node = Rule.rule_node_for(self)
156
+
157
+ rule_node.rule_names.each { |rule_name| singelton.send(:remove_method, rule_name) }
158
+ rule_node.rules.clear
159
+ end
160
+
161
+ # Force to trigger the rules.
162
+ # You don't normally need that since it will be done automatically.
163
+ # This can be useful if you need to trigger rules on already existing nodes in the database.
164
+ # Can also be called from an migration.
165
+ #
166
+ def trigger_rules(node, *changes)
167
+ Rule.trigger_rules(node, *changes)
168
+ end
169
+
170
+ # Returns a proc that will call add method on the given function
171
+ # Can be used in migration to trigger rules on already existing nodes.
172
+ # Parameter function_id is default to '_classname' which means that
173
+ # the function 'reacts' on changes on the property '_classname'.
174
+ # That property changes only when a node is created or deleted.
175
+ # Function using function_id '_classname' is typically used for counting number of nodes of a class.
176
+ def add_function_for(rule_name, function_name_or_class, function_id = '_classname')
177
+ function_for(:add, rule_name, function_name_or_class, function_id)
178
+ end
179
+
180
+ # See #add_function_for
181
+ # Calls the delete method on the function.
182
+ def delete_function_for(rule_name, function_name_or_class, function_id = '_classname')
183
+ function_for(:delete, rule_name, function_name_or_class, function_id)
184
+ end
185
+
186
+ # Returns a proc that calls the given method on the given function.
187
+ def function_for(method, rule_name, function_name_or_class, function_id = '_classname')
188
+ function_name = function_name_or_class.is_a?(Symbol) ? function_name_or_class : function_name_or_class.function_name
189
+ rule_node = Rule.rule_node_for(self)
190
+ rule = rule_node.find_rule(rule_name)
191
+ rule_node_raw = rule_node.rule_node
192
+
193
+ function = rule_node.find_function(rule_name, function_name, function_id)
194
+ lambda do |node|
195
+ new_value = node[function_id]
196
+ function.send(method, rule.rule_name, rule_node_raw, new_value)
197
+ end
198
+ end
199
+ end
200
+
201
+ end
202
+ end
203
+
204
+ end
@@ -0,0 +1,66 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+ class EventListener
5
+ class << self
6
+ # ----------------------------------------------------------------------------------------------------------------
7
+ # Event handling methods
8
+ # ----------------------------------------------------------------------------------------------------------------
9
+
10
+ def on_relationship_created(rel, *)
11
+ Rule.trigger_rules(rel._start_node) if Rule.trigger?(rel._start_node)
12
+ Rule.trigger_rules(rel._end_node) if Rule.trigger?(rel._end_node)
13
+ end
14
+
15
+ def on_property_changed(node, *changes)
16
+ Rule.trigger_rules(node, *changes) if Rule.trigger?(node)
17
+ end
18
+
19
+ def on_node_deleted(node, old_properties, deleted_relationship_set, deleted_identity_map)
20
+ # have we deleted a rule node ?
21
+ del_rule_node = Rule.find_rule_node(node)
22
+ del_rule_node && del_rule_node.clear_rule_node
23
+ return if del_rule_node
24
+
25
+ # do we have prop_aggregations for this
26
+ clazz = old_properties['_classname']
27
+ rule_node = Rule.rule_node_for(clazz)
28
+ return if rule_node.nil?
29
+
30
+ id = node.neo_id
31
+ rule_node.rules.each do |rule|
32
+ next if rule.functions.nil? || rule.bulk_update?
33
+ rule_name = rule.rule_name.to_s
34
+
35
+ # is the rule node deleted ?
36
+ deleted_rule_node = deleted_identity_map.get(rule_node.rule_node.neo_id)
37
+ next if deleted_rule_node
38
+
39
+ rule.functions.each do |function|
40
+ next unless deleted_relationship_set.contains?(id, rule_name)
41
+ previous_value = old_properties[function.function_id]
42
+ function.delete(rule_name, rule_node.rule_node, previous_value) if previous_value
43
+ end if rule.functions
44
+ end
45
+ end
46
+
47
+ def classes_changed(changed_class_map)
48
+ changed_class_map.each_pair do |clazz, class_change|
49
+ Rule.bulk_trigger_rules(clazz, class_change, changed_class_map)
50
+ end
51
+ end
52
+
53
+ def on_neo4j_started(db)
54
+ if not Neo4j::Config[:enable_rules]
55
+ db.event_handler.remove(self)
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ end
62
+ Neo4j.unstarted_db.event_handler.add(EventListener) unless Neo4j.read_only?
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,45 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+ module Functions
5
+
6
+ # A function for counting number of nodes of a given class.
7
+ class Count < Function
8
+ def initialize
9
+ @property = '_classname'
10
+ end
11
+
12
+ def calculate?(changed_property)
13
+ true
14
+ end
15
+
16
+ def delete(rule_name, rule_node, _)
17
+ key = rule_node_property(rule_name)
18
+ rule_node[key] ||= 0
19
+ rule_node[key] -= 1
20
+ end
21
+
22
+ def add(rule_name, rule_node, _)
23
+ key = rule_node_property(rule_name)
24
+ rule_node[key] ||= 0
25
+ rule_node[key] += 1
26
+ end
27
+
28
+ def update(*)
29
+ # we are only counting, not interested in property changes
30
+ end
31
+
32
+ def classes_changed(rule_name, rule_node, class_change)
33
+ key = rule_node_property(rule_name)
34
+ rule_node[key] ||= 0
35
+ rule_node[key] += class_change.net_change
36
+ end
37
+
38
+ def self.function_name
39
+ :count
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,76 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+ module Functions
5
+
6
+ # The base class of rule functions.
7
+ #
8
+ # You are expected to at least implement two methods:
9
+ # * update :: update the rule node value of this function
10
+ # * function_name :: the name of this function, the name of the generated method - A class method !
11
+ #
12
+ class Function
13
+
14
+ # Initialize the the function with a property which is usually the same as the function identity.
15
+ # See the #calculate? method how this property is used.
16
+ #
17
+ def initialize(property)
18
+ @property = property.to_s
19
+ end
20
+
21
+ def to_s
22
+ "Function #{self.class.function_name} function_id: #{function_id}"
23
+ end
24
+
25
+ # Decides if the function should be called are not
26
+ #
27
+ def calculate?(changed_property)
28
+ @property == changed_property
29
+ end
30
+
31
+
32
+ # The identity of the function.
33
+ # Used to identify function.
34
+ #
35
+ # ==== Example
36
+ # Person.sum(:young, :age)
37
+ #
38
+ # In the example above the property :age is the used to identify which function will be called
39
+ # since there could be several sum method. In the example we want use the sum method that uses the :age property.
40
+ #
41
+ def function_id
42
+ @property
43
+ end
44
+
45
+ # The value of the rule
46
+ def value(rule_node, rule_name)
47
+ key = rule_node_property(rule_name)
48
+ rule_node[key] || 0
49
+ end
50
+
51
+ # Called when a node is removed from a rule group
52
+ # Default is calling update method which is expected to be implemented in a subclass
53
+ def delete(rule_name, rule_node, old_value)
54
+ update(rule_name, rule_node, old_value, nil)
55
+ end
56
+
57
+ # Called when a node is added to a rule group
58
+ # Default is calling update method which is expected to be implemented in a subclass
59
+ def add(rule_name, rule_node, new_value)
60
+ update(rule_name, rule_node, nil, new_value)
61
+ end
62
+
63
+ # the name of the property that holds the value of the function
64
+ def rule_node_property(rule_name)
65
+ self.class.rule_node_property(self.class.function_name, rule_name, @property)
66
+ end
67
+
68
+ def self.rule_node_property(function_name, rule_name, prop)
69
+ "_#{function_name}_#{rule_name}_#{prop}"
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+ module Functions
5
+
6
+ class Sum < Function
7
+ # Updates the function's value.
8
+ # Called after the transactions commits and a property has been changed on a node.
9
+ #
10
+ # ==== Arguments
11
+ # * rule_name :: the name of the rule group
12
+ # * rule_node :: the node which contains the value of this function
13
+ # * old_value new value :: the changed value of the property (when the transaction commits)
14
+ def update(rule_name, rule_node, old_value, new_value)
15
+ key = rule_node_property(rule_name)
16
+ rule_node[key] ||= 0
17
+ old_value ||= 0
18
+ new_value ||= 0
19
+ rule_node[key] += new_value - old_value
20
+ end
21
+
22
+ def self.function_name
23
+ :sum
24
+ end
25
+ end
26
+
27
+
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+ module InstanceMethods
5
+ # Trigger rules.
6
+ # You don't normally need to call this method (except in Migration) since
7
+ # it will be triggered automatically by the Neo4j::Wrapper::Rule::Rule
8
+ # @see Neo4j::Wrapper::Rule::ClassMethods
9
+ def trigger_rules
10
+ self.class.trigger_rules(self)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,29 @@
1
+ module Neo4j
2
+ module Core
3
+ module Traversal
4
+ # Extends the Neo4j::Core Traverser in order to add rule traversal methods.
5
+ class Traverser
6
+ def filter_method(name, &proc)
7
+ # add method name
8
+ singelton = class << self;
9
+ self;
10
+ end
11
+ singelton.send(:define_method, name) { filter &proc }
12
+ self
13
+ end
14
+
15
+ def functions_method(func, rule_node, rule_name)
16
+ singelton = class << self;
17
+ self;
18
+ end
19
+ singelton.send(:define_method, func.class.function_name) do |*args|
20
+ function_id = args.empty? ? "_classname" : args[0]
21
+ function = rule_node.find_function(rule_name, func.class.function_name, function_id)
22
+ function.value(rule_node.rule_node, rule_name)
23
+ end
24
+ self
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,134 @@
1
+ module Neo4j
2
+ module Wrapper
3
+ module Rule
4
+
5
+
6
+ # Holds all defined rules added by the Neo4j::Rule::ClassMethods#rule method.
7
+ #
8
+ # @see Neo4j::Wrapper::Rule::ClassMethods
9
+ class Rule
10
+
11
+ attr_reader :rule_name, :filter, :triggers, :functions
12
+
13
+ def initialize(rule_name, props, &block)
14
+ @rule_name = rule_name
15
+ @triggers = props[:triggers]
16
+ @functions = props[:functions]
17
+ @triggers = [@triggers] if @triggers && !@triggers.respond_to?(:each)
18
+ @functions = [@functions] if @functions && !@functions.respond_to?(:each)
19
+ @filter = block
20
+ @bulk_update = !@functions.nil? && @functions.size == 1 && @functions.first.class.function_name == :count && @filter.nil?
21
+ end
22
+
23
+ def to_s
24
+ "Rule #{rule_name} props=#{props.inspect}"
25
+ end
26
+
27
+ def find_function(function_name, function_id)
28
+ function_id = function_id.to_s
29
+ @functions && @functions.find { |f| f.function_id == function_id && f.class.function_name == function_name }
30
+ end
31
+
32
+ # Reconstruct the properties given when created this rule
33
+ # Needed when inheriting a rule and we want to duplicate a rule
34
+ def props
35
+ props = {}
36
+ props[:triggers] = @triggers if @triggers
37
+ props[:functions] = @functions if @functions
38
+ props
39
+ end
40
+
41
+ def functions_for(property)
42
+ @functions && @functions.find_all { |f| f.calculate?(property) }
43
+ end
44
+
45
+ def execute_filter(node)
46
+ if @filter.nil?
47
+ true
48
+ elsif @filter.arity != 1
49
+ node.wrapper.instance_eval(&@filter)
50
+ else
51
+ @filter.call(node)
52
+ end
53
+ end
54
+
55
+ def bulk_update?
56
+ @bulk_update
57
+ end
58
+
59
+ # ------------------------------------------------------------------------------------------------------------------
60
+ # Class Methods
61
+ # ------------------------------------------------------------------------------------------------------------------
62
+
63
+ @rule_nodes = {}
64
+
65
+ class << self
66
+
67
+ def add(clazz, rule_name, props, &block)
68
+ rule_node = rule_node_for(clazz.to_s)
69
+ rule_node.remove_rule(rule_name) # remove any previously inherited rules
70
+ rule = Rule.new(rule_name, props, &block)
71
+ rule_node.add_rule(rule)
72
+ rule
73
+ end
74
+
75
+ def rule_names_for(clazz)
76
+ rule_node = rule_node_for(clazz)
77
+ rule_node.rules.map { |rule| rule.rule_name }
78
+ end
79
+
80
+ def rule_node_for(clazz)
81
+ return nil if clazz.nil?
82
+ @rule_nodes[clazz.to_s] ||= RuleNode.new(clazz)
83
+ end
84
+
85
+ def find_rule_node(node)
86
+ @rule_nodes && @rule_nodes.values.find { |rn| rn.rule_node?(node) }
87
+ end
88
+
89
+ def inherit(parent_class, subclass)
90
+ # copy all the rules
91
+ if rule_node = rule_node_for(parent_class)
92
+ rule_node.inherit(subclass)
93
+ end
94
+ end
95
+
96
+ def trigger?(node)
97
+ classname = node[:_classname]
98
+ @rule_nodes && classname && rule_node_for(classname) && !rule_node_for(classname).bulk_update?
99
+ end
100
+
101
+ def trigger_rules(node, *changes)
102
+ classname = node[:_classname]
103
+ return unless classname # there are no rules if there is not a :_classname property
104
+ rule_node = rule_node_for(classname)
105
+ rule_node.execute_rules(node, *changes)
106
+
107
+ # recursively add relationships for all the parent classes with rules that also pass for this node
108
+ recursive(node, rule_node.model_class, *changes)
109
+ end
110
+
111
+ def bulk_trigger_rules(classname, class_change, map)
112
+ rule_node = rule_node_for(classname)
113
+ rule_node.classes_changed(class_change)
114
+ if (clazz = rule_node.model_class.superclass) && clazz.include?(Neo4j::NodeMixin)
115
+ bulk_trigger_rules(clazz.name, class_change, map) if clazz.to_s != "Neo4j::Rails::Model"
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def recursive(node, model_class, *changes)
122
+ if (clazz = model_class.superclass) && clazz.include?(Neo4j::NodeMixin)
123
+ if clazz.to_s != "Neo4j::Rails::Model"
124
+ rule_node = rule_node_for(clazz)
125
+ rule_node && rule_node.execute_rules(node, *changes)
126
+ recursive(node, clazz, *changes)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end