neo4j 1.0.0.beta.21-java
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +141 -0
- data/CONTRIBUTORS +17 -0
- data/Gemfile +16 -0
- data/README.rdoc +135 -0
- data/lib/generators/neo4j.rb +65 -0
- data/lib/generators/neo4j/model/model_generator.rb +39 -0
- data/lib/generators/neo4j/model/templates/model.erb +7 -0
- data/lib/neo4j.rb +77 -0
- data/lib/neo4j/config.rb +153 -0
- data/lib/neo4j/database.rb +56 -0
- data/lib/neo4j/equal.rb +21 -0
- data/lib/neo4j/event_handler.rb +116 -0
- data/lib/neo4j/index/class_methods.rb +62 -0
- data/lib/neo4j/index/index.rb +33 -0
- data/lib/neo4j/index/indexer.rb +312 -0
- data/lib/neo4j/index/indexer_registry.rb +68 -0
- data/lib/neo4j/index/lucene_query.rb +191 -0
- data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
- data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
- data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
- data/lib/neo4j/load.rb +21 -0
- data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
- data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
- data/lib/neo4j/mapping/class_methods/list.rb +13 -0
- data/lib/neo4j/mapping/class_methods/property.rb +82 -0
- data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
- data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
- data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
- data/lib/neo4j/mapping/has_list.rb +134 -0
- data/lib/neo4j/mapping/has_n.rb +83 -0
- data/lib/neo4j/mapping/node_mixin.rb +112 -0
- data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
- data/lib/neo4j/model.rb +4 -0
- data/lib/neo4j/neo4j.rb +95 -0
- data/lib/neo4j/node.rb +131 -0
- data/lib/neo4j/node_mixin.rb +4 -0
- data/lib/neo4j/node_relationship.rb +149 -0
- data/lib/neo4j/node_traverser.rb +157 -0
- data/lib/neo4j/property.rb +111 -0
- data/lib/neo4j/rails/attributes.rb +155 -0
- data/lib/neo4j/rails/callbacks.rb +34 -0
- data/lib/neo4j/rails/finders.rb +134 -0
- data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
- data/lib/neo4j/rails/mapping/property.rb +60 -0
- data/lib/neo4j/rails/model.rb +105 -0
- data/lib/neo4j/rails/persistence.rb +260 -0
- data/lib/neo4j/rails/railtie.rb +21 -0
- data/lib/neo4j/rails/relationships/mapper.rb +96 -0
- data/lib/neo4j/rails/relationships/relationship.rb +30 -0
- data/lib/neo4j/rails/relationships/relationships.rb +60 -0
- data/lib/neo4j/rails/serialization.rb +25 -0
- data/lib/neo4j/rails/timestamps.rb +65 -0
- data/lib/neo4j/rails/transaction.rb +67 -0
- data/lib/neo4j/rails/tx_methods.rb +15 -0
- data/lib/neo4j/rails/validations.rb +38 -0
- data/lib/neo4j/rails/validations/non_nil.rb +11 -0
- data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
- data/lib/neo4j/relationship.rb +169 -0
- data/lib/neo4j/relationship_mixin.rb +4 -0
- data/lib/neo4j/relationship_traverser.rb +92 -0
- data/lib/neo4j/to_java.rb +31 -0
- data/lib/neo4j/transaction.rb +68 -0
- data/lib/neo4j/type_converters.rb +117 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/orm_adapter/adapters/neo4j.rb +55 -0
- data/lib/tmp/neo4j/active_tx_log +1 -0
- data/lib/tmp/neo4j/index/lucene-store.db +0 -0
- data/lib/tmp/neo4j/index/lucene.log.active +0 -0
- data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
- data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
- data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
- data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
- data/lib/tmp/neo4j/messages.log +85 -0
- data/lib/tmp/neo4j/neostore +0 -0
- data/lib/tmp/neo4j/neostore.id +0 -0
- data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
- data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
- data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
- data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
- data/neo4j.gemspec +31 -0
- metadata +216 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
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,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
|