Rules and Functions

1 Rules

Rules can be used to group a set of nodes. Rules and functions are very fast for read operations. The grouping of nodes take place when you update, insert, delete a node, relationship or property. So, if you use much more read operations than write operations, using rules and function might give you better performance compared to using lucene queries or traversals.

Example of rule groups: all nodes of a class, all nodes with property x == y, all nodes with relationship type z == q.

1.1 Find all nodes of a class

Notice this rule is always included when using Neo4j::Model

class Person
  include Neo4j::NodeMixin
  rule :all
end

You can then find all nodes of the class Person.

Person.all.each {|p| puts p}

This also works for subclasses.

class Employee < Person
end

Employee.all # only the employee subclass nodes

When a node is created or deleted, it will add or remove a relationship (all in the example above) to the rule node. Every node class has its own rule node which in turn has a relationship to the Neo4j.ref_node. When finding all the nodes of a rule group, Neo4j.rb simply traverses the relationships from the rule node with the same name as the rule group.

1.2 Rule Groups

You can add a proc to the rule which will decide if the node should be included or not in the given rule group.

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

The rule group old will contain all nodes that evaluate the given proc to true. The proc will be called when a node or relationship is created, deleted, or updated.

To find all nodes of class Reader with property age > 10:

Reader.old  # returns an Enumerable object

Each node will also have a method old? Example:

r = Neo4j::Transaction.run {Reader.new :age => 1}
r.old? #=> false
Neo4j::Transaction.run {r.age = 15}
r.old? #=> true

Notice that you must commit the transaction in order to trigger the Rules, as shown in the example above.

1.3 Chaining Rules

You can combine rules:

class NewsStory
  include Neo4j::NodeMixin
  has_n :readers

  rule :all
  rule(:featured) { |node| node[:featured] == true }
  rule(:embargoed) { |node| node[:publish_date] > 2010 }
end

NewsStory.featured.embargoed
NewsStory.all.featured.embargoed

1.4 Rules Triggering other Rules

Let say we have two classes: Reader and NewsStory. We want to find out if a story has young or old readers. You can trigger other rules with the :triggers parameter.

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

class NewsStory
  include Neo4j::NodeMixin
  has_n :readers

  # young readers for only young readers - find first person which is not young, if not found then the story has only young readers
  rule(:young_readers) { !readers.find { |user| !user.young? } }
end

When a node in the young rule group changes, it will trigger the incoming relationship readers (defined by has_n :readers).

user  = Reader.new :age => 200
story = NewsStory.new 
story.readers << user

# create a new transaction so that it can trigger rules
NewsStory.young_readers #  should NOT include story

user[:age] = 2
# create a new transaction so that it can trigger rules

NewsStory.young_readers # should include story

2 Functions

Each rule group can have a set of functions.

This is only available in upcoming 1.0.0.beta.24 release (or latest from github)

2.1 Counting

To count all nodes:

class Person
  include Neo4j::NodeMixin
  include :all, :functions => [Count.new]
end

Person.new
# create new transaction, required since rules are triggered when transaction finish
Person.all.count # => 1
# same as
Person.count(:all)

To count only a subset:

class Person
  include Neo4j::NodeMixin
  # notice the :functions parameter can take an array of functions, or just one function
  rule(:old, :functions => [Count.new]) {  age > 10 }
end
Person.count(:old)

Neo4j/Rails Neo4j::Model already includes one rule:

rule(:all, functions => Count.new)
. The count method on the Person.all.count will not traverse and count all nodes. Instead, it will read the count function value on the rule node, which is much faster.

2.2 Sum

The following function will sum the age of every people in the rule group old:

class Person
  include Neo4j::NodeMixin
  rule(:old, :functions => Sum.new(:age)) {  age > 10 }
end

Person.sum(:old, :age)
# same as
Person.old.sum(:age)

2.3 Creating your own Rule Function

In order to create your own rule function, inherit from the Neo4j::Functions::Function class and implement the update and function_name method. Here is the implementation of the sum function:

class Sum < Function
      # Updates the function's value.
      # Called after the transactions commits and a property has been changed on a node.
      #
      # ==== Arguments
      # * rule_name :: the name of the rule group
      # * rule_node :: the node which contains the value of this function
      # * old_value new value :: the changed value of the property (when the transaction commits)
      def update(rule_name, rule_node, old_value, new_value)
        key            = rule_node_property(rule_name)
        rule_node[key] ||= 0
        old_value      ||= 0
        new_value      ||= 0
        rule_node[key] += new_value - old_value
      end

      def self.function_name
        :sum
      end

3 Performance

You may get some better performance using a parameter in your proc.

Example:

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

Then a ruby object (of type Person in the example above) will not be created that wraps the Java Node Instead it will use the java node object as a parameter as shown above.

4 Migrations

See Migrations