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.
Files changed (99) hide show
  1. data/CHANGELOG +141 -0
  2. data/CONTRIBUTORS +17 -0
  3. data/Gemfile +16 -0
  4. data/README.rdoc +135 -0
  5. data/lib/generators/neo4j.rb +65 -0
  6. data/lib/generators/neo4j/model/model_generator.rb +39 -0
  7. data/lib/generators/neo4j/model/templates/model.erb +7 -0
  8. data/lib/neo4j.rb +77 -0
  9. data/lib/neo4j/config.rb +153 -0
  10. data/lib/neo4j/database.rb +56 -0
  11. data/lib/neo4j/equal.rb +21 -0
  12. data/lib/neo4j/event_handler.rb +116 -0
  13. data/lib/neo4j/index/class_methods.rb +62 -0
  14. data/lib/neo4j/index/index.rb +33 -0
  15. data/lib/neo4j/index/indexer.rb +312 -0
  16. data/lib/neo4j/index/indexer_registry.rb +68 -0
  17. data/lib/neo4j/index/lucene_query.rb +191 -0
  18. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  19. data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
  20. data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
  21. data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
  22. data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
  23. data/lib/neo4j/load.rb +21 -0
  24. data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
  25. data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
  26. data/lib/neo4j/mapping/class_methods/list.rb +13 -0
  27. data/lib/neo4j/mapping/class_methods/property.rb +82 -0
  28. data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
  29. data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
  30. data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
  31. data/lib/neo4j/mapping/has_list.rb +134 -0
  32. data/lib/neo4j/mapping/has_n.rb +83 -0
  33. data/lib/neo4j/mapping/node_mixin.rb +112 -0
  34. data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
  35. data/lib/neo4j/model.rb +4 -0
  36. data/lib/neo4j/neo4j.rb +95 -0
  37. data/lib/neo4j/node.rb +131 -0
  38. data/lib/neo4j/node_mixin.rb +4 -0
  39. data/lib/neo4j/node_relationship.rb +149 -0
  40. data/lib/neo4j/node_traverser.rb +157 -0
  41. data/lib/neo4j/property.rb +111 -0
  42. data/lib/neo4j/rails/attributes.rb +155 -0
  43. data/lib/neo4j/rails/callbacks.rb +34 -0
  44. data/lib/neo4j/rails/finders.rb +134 -0
  45. data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
  46. data/lib/neo4j/rails/mapping/property.rb +60 -0
  47. data/lib/neo4j/rails/model.rb +105 -0
  48. data/lib/neo4j/rails/persistence.rb +260 -0
  49. data/lib/neo4j/rails/railtie.rb +21 -0
  50. data/lib/neo4j/rails/relationships/mapper.rb +96 -0
  51. data/lib/neo4j/rails/relationships/relationship.rb +30 -0
  52. data/lib/neo4j/rails/relationships/relationships.rb +60 -0
  53. data/lib/neo4j/rails/serialization.rb +25 -0
  54. data/lib/neo4j/rails/timestamps.rb +65 -0
  55. data/lib/neo4j/rails/transaction.rb +67 -0
  56. data/lib/neo4j/rails/tx_methods.rb +15 -0
  57. data/lib/neo4j/rails/validations.rb +38 -0
  58. data/lib/neo4j/rails/validations/non_nil.rb +11 -0
  59. data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
  60. data/lib/neo4j/relationship.rb +169 -0
  61. data/lib/neo4j/relationship_mixin.rb +4 -0
  62. data/lib/neo4j/relationship_traverser.rb +92 -0
  63. data/lib/neo4j/to_java.rb +31 -0
  64. data/lib/neo4j/transaction.rb +68 -0
  65. data/lib/neo4j/type_converters.rb +117 -0
  66. data/lib/neo4j/version.rb +3 -0
  67. data/lib/orm_adapter/adapters/neo4j.rb +55 -0
  68. data/lib/tmp/neo4j/active_tx_log +1 -0
  69. data/lib/tmp/neo4j/index/lucene-store.db +0 -0
  70. data/lib/tmp/neo4j/index/lucene.log.active +0 -0
  71. data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
  72. data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
  73. data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
  74. data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
  75. data/lib/tmp/neo4j/messages.log +85 -0
  76. data/lib/tmp/neo4j/neostore +0 -0
  77. data/lib/tmp/neo4j/neostore.id +0 -0
  78. data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
  79. data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
  80. data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
  81. data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
  82. data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
  83. data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
  84. data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
  85. data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
  86. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
  87. data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
  88. data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
  89. data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
  90. data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
  91. data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
  92. data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
  93. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
  94. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
  95. data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
  96. data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
  97. data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
  98. data/neo4j.gemspec +31 -0
  99. metadata +216 -0
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,13 @@
1
+ module Neo4j::Mapping
2
+ module ClassMethods
3
+ module List
4
+ def has_list(name, params = {})
5
+ module_eval(%Q{
6
+ def #{name}
7
+ Neo4j::Mapping::HasList.new(self, '#{name}')
8
+ end}, __FILE__, __LINE__)
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -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