-
Notifications
You must be signed in to change notification settings - Fork 276
Neo4j::Wrapper Rules and Functions
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 have not too many writes but mostly read 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. The Neo4j::Rails::Model
has the same functionality (since it includes the same mixin).
Notice this rules is always included when using Neo4j::Model
class Person
include Neo4j::NodeMixin
rule :all # Do not use the :all rule for Neo4j::Rails::Model since it is already included !
end
You can then find all node 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
TIP: 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 it’s 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 traverse the relationships from the rule node with the same name as the rule group.
Notice The Neo4j::Rails::Model already defines the rule :all
with a count function (see below).
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 evaluates the given proc to true
. The proc will be called
when a node or relationship is created, deleted, 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
TIP: Notice that you must commit the transaction in order to trigger the Rules, as shown in the example above.
You can combine rules, example
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
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
The traversals can be converted to cypher queries by using the query
method.
In this example we are going to use the Neo4j::Rails::Model
instead of including the Neo4j::NodeMixin
(they both include the same mixin). See Cypher DSL query how to use the Neo4j.rb Cypher DSL.
class Person < Neo4j::Rails::Model
end
Person.all.query.to_s # => "START n0=node(2) MATCH (n0)-[:`_all`]->(default_ret) RETURN default_ret"
Person.all.query.to_a # => returns a once only forward read Enumerable of all person instances.
Notice that the all rule is by default included when using the Neo4j::Rails::Model
To filter using a cypher where clause you can use a hash.
# "START n0=node(2) MATCH (n0)-[:`_all`]->(default_ret) WHERE default_ret.age = 42 RETURN default_ret"
Person.all.query(:age => 42).to_a #=> only return person objects with age property == 42
The query a set of nodes scoped by a rule with a Cypher DSL block.
Person.all.query{|p| ret(m).asc(m[:strength])}
You can also use Rules and Cypher on node instances.
Let say you have defined the following classes:
class Monster < Neo4j::Rails::Model
rule(:dangerous) { |m| m[:strength] > 15 }
end
class Dungeon < Neo4j::Rails::Model
property :name, :index => :exact # lucene index
has_n(:monsters).to(Monster)
end
class Room < Neo4j::Rails::Model
has_n(:monsters).to(Monster)
end
When you declare a has_n
relationship using a to
specifier the relationship accessor method will get access to the rule methods (above Monster#dangerous
). That means that you can from a Dungeon
or Room
instance use the monsters.dangerous
method. Since that method is a rule method you can also combine that with Cypher !
Example: return all monsters of strengh > 16
dungeon = Dungeon.find_by_name("Dragonwald")
dungeon.monsters { |m| m[:strength] > 16 }
Example: Using rules with cypher: find all dangerous monsters with swords
dungeon = Dungeon.find_by_name("Dragonwald")
dungeon.monsters.dangerous { |m| m[:weapon?] == 'sword' }
Example: returns the rooms we should avoid
dungeon = Dungeon.find_by_name("Dragonwald")
dungeon.monsters.dangerous { |m| m.incoming(Room.monsters) }
Each rule group can have a set of functions.
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)
TIP: 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.
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)
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
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 an parameter as shown above.
WARNING: Much of the information in this wiki is out of date. We are in the process of moving things to readthedocs
- Project Introduction
- Neo4j::ActiveNode
- Neo4j::ActiveRel
- Search and Scope
- Validation, Uniqueness, and Case Sensitivity
- Indexing VS Legacy Indexing
- Optimized Methods
- Inheritance
- Core: Nodes & Rels
- Introduction
- Persistence
- Find : Lucene
- Relationships
- Third Party Gems & extensions
- Scaffolding & Generators
- HA Cluster