neo4j 1.0.0.beta.21-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|