Module: Neo4j::Wrapper::Rule::ClassMethods

Defined in:
lib/neo4j-wrapper/rule/class_methods.rb

Overview

Allows you to group nodes by providing a rule.

Example, finding all nodes of a certain class

Just add a rule without a code block, then all nodes of that class will be grouped under the given key (all for the example below).

class Person
  include Neo4j::NodeMixin
  rule :all
end

Then you can get all the nodes of type Person (and siblings) by

Person.all.each {|x| ...}

Example, finding all nodes with a given condition on a property

 class Person
   include Neo4j::NodeMixin
   property :age
   rule(:old) { age > 10 }
 end

Now we can find all nodes with a property <tt>age</tt> above 10.

Chain Rules

class NewsStory
  include Neo4j::NodeMixin
  has_n :readers
  rule(:featured) { |node| node[:featured] == true }
  rule(:young_readers) { !readers.find{|user| !user.young?}}
end

You can combine two rules. Let say you want to find all stories which are featured and has young readers:

NewsStory.featured.young_readers.each {...}

Trigger Other Rules

You can let one rule trigger another rule. Let say you have readers of some magazine and want to know if the magazine has old or young readers. So when a reader change from young to old you want to trigger all the magazine that he reads (a but stupid example)

Example

class Reader
  include Neo4j::NodeMixin
  property :age
  rule(:young, :triggers => :readers) { age < 15 }
end

class NewsStory
  include Neo4j::NodeMixin
  has_n :readers
  rule(:young_readers) { !readers.find{|user| !user.young?}}
end

Performance Considerations

If you have many rules and many updates this can be a bit slow. In order to speed it up somewhat you can use the raw java node object instead by providing an argument in your block.

Example:

class Person
  include Neo4j::NodeMixin
  property :age
  rule(:old) {|node| node[:age] > 10 }
end

Thread Safe ?

Yes, since operations are performed in an transaction. However you may get a deadlock exception:

Instance Method Summary collapse

Instance Method Details

#add_function_for(rule_name, function_name_or_class, function_id = '_classname') ⇒ Object

Returns a proc that will call add method on the given function Can be used in migration to trigger rules on already existing nodes. Parameter function_id is default to '_classname' which means that the function 'reacts' on changes on the property '_classname'. That property changes only when a node is created or deleted. Function using function_id '_classname' is typically used for counting number of nodes of a class.



176
177
178
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 176

def add_function_for(rule_name, function_name_or_class, function_id = '_classname')
  function_for(:add, rule_name, function_name_or_class, function_id)
end

#delete_function_for(rule_name, function_name_or_class, function_id = '_classname') ⇒ Object

See #add_function_for Calls the delete method on the function.



182
183
184
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 182

def delete_function_for(rule_name, function_name_or_class, function_id = '_classname')
  function_for(:delete, rule_name, function_name_or_class, function_id)
end

#delete_rulesObject

This is typically used for RSpecs to clean up rule nodes created by the #rule method. It also remove all the rule class methods.



151
152
153
154
155
156
157
158
159
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 151

def delete_rules
  singelton = class << self;
    self;
  end
  rule_node = Rule.rule_node_for(self)

  rule_node.rule_names.each { |rule_name| singelton.send(:remove_method, rule_name) }
  rule_node.rules.clear
end

#function_for(method, rule_name, function_name_or_class, function_id = '_classname') ⇒ Object

Returns a proc that calls the given method on the given function.



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 187

def function_for(method, rule_name, function_name_or_class, function_id = '_classname')
  function_name = function_name_or_class.is_a?(Symbol) ? function_name_or_class : function_name_or_class.function_name
  rule_node = Rule.rule_node_for(self)
  rule = rule_node.find_rule(rule_name)
  rule_node_raw = rule_node.rule_node

  function = rule_node.find_function(rule_name, function_name, function_id)
  lambda do |node|
    new_value = node[function_id]
    function.send(method, rule.rule_name, rule_node_raw, new_value)
  end
end

#inherit_rules_from(clazz) ⇒ Object



145
146
147
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 145

def inherit_rules_from(clazz)
  Rule.inherit(clazz, self)
end

#ref_node(&block) ⇒ Object

Assigns the reference node for a class via a supplied block.

Examples:

usage:

class Person
  include Neo4j::NodeMixin
  ref_node { Neo4j.default_ref_node }
end


138
139
140
141
142
143
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 138

def ref_node(&block)
  singleton = class << self;
    self;
  end
  singleton.send(:define_method, :ref_node_for_class) { block.call }
end

#ref_node_for_classObject



127
128
129
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 127

def ref_node_for_class
  Neo4j.ref_node #The reference node for a type falls back to the threadlocal ref node by default.
end

#rule(rule_name, props = {}, &block) ⇒ Object

Creates an rule node attached to the Neo4j.ref_node Can be used to rule all instances of a specific Ruby class.

Example of usage:

class Person
  include Neo4j::NodeMixin
  property :age
  rule :all
  rule :young { self[:age] < 10 }
  rule(:old, :functions => [Sum.new[:age]) { age > 20 }
end

p1 = Person.new :age => 5
p2 = Person.new :age => 7
p3 = Person.new :age => 12
Neo4j::Transaction.finish
Person.all    # =>  [p1,p2,p3]
Person.young  # =>  [p1,p2]
p1.young?    # => true
p1.sum(old, :age) # the some of the old people's age


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 99

def rule(rule_name, props = {}, &block)
  singleton = class << self;
    self;
  end

  # define class methods
  singleton.send(:define_method, rule_name) do |*args, &cypher_block|
    rule_node = Rule.rule_node_for(self)
    rule_node.traversal(rule_name, args.first, &cypher_block)
  end unless respond_to?(rule_name)

  # define instance methods
  self.send(:define_method, "#{rule_name}?") do
    instance_eval &block
  end

  rule = Rule.add(self, rule_name, props, &block)

  rule.functions && rule.functions.each do |func|
    singleton.send(:define_method, func.class.function_name) do |r_name, *args|
      rule_node = Rule.rule_node_for(self)
      function_id = args.empty? ? "_classname" : args[0]
      function = rule_node.find_function(r_name, func.class.function_name, function_id)
      function.value(rule_node.rule_node, r_name)
    end unless respond_to?(func.class.function_name)
  end
end

#trigger_rules(node, *changes) ⇒ Object

Force to trigger the rules. You don't normally need that since it will be done automatically. This can be useful if you need to trigger rules on already existing nodes in the database. Can also be called from an migration.



166
167
168
# File 'lib/neo4j-wrapper/rule/class_methods.rb', line 166

def trigger_rules(node, *changes)
  Rule.trigger_rules(node, *changes)
end