neo4j 5.0.15 → 5.1.0.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -5
- data/Gemfile +1 -1
- data/README.md +8 -0
- data/lib/neo4j.rb +4 -0
- data/lib/neo4j/active_node.rb +3 -1
- data/lib/neo4j/active_node/dependent/association_methods.rb +4 -2
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +3 -3
- data/lib/neo4j/active_node/has_n.rb +103 -36
- data/lib/neo4j/active_node/has_n/association.rb +10 -33
- data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +108 -0
- data/lib/neo4j/active_node/id_property.rb +19 -11
- data/lib/neo4j/active_node/id_property/accessor.rb +62 -0
- data/lib/neo4j/active_node/labels.rb +13 -2
- data/lib/neo4j/active_node/persistence.rb +19 -4
- data/lib/neo4j/active_node/property.rb +4 -3
- data/lib/neo4j/active_node/query/query_proxy.rb +29 -13
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +8 -0
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +7 -0
- data/lib/neo4j/active_node/query/query_proxy_link.rb +16 -6
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +4 -0
- data/lib/neo4j/active_node/query/query_proxy_unpersisted.rb +17 -0
- data/lib/neo4j/active_node/unpersisted.rb +49 -0
- data/lib/neo4j/active_node/validations.rb +1 -1
- data/lib/neo4j/active_rel.rb +17 -0
- data/lib/neo4j/active_rel/persistence.rb +10 -5
- data/lib/neo4j/active_rel/property.rb +17 -5
- data/lib/neo4j/railtie.rb +2 -1
- data/lib/neo4j/shared/declared_property_manager.rb +10 -0
- data/lib/neo4j/shared/initialize.rb +3 -3
- data/lib/neo4j/shared/property.rb +7 -51
- data/lib/neo4j/shared/property/default_property.rb +0 -0
- data/lib/neo4j/shared/type_converters.rb +49 -6
- data/lib/neo4j/shared/typecaster.rb +22 -18
- data/lib/neo4j/shared/validations.rb +1 -1
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +1 -1
- metadata +12 -7
@@ -23,6 +23,14 @@ module Neo4j
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def with_associations(*spec)
|
26
|
+
invalid_association_names = spec.reject do |association_name|
|
27
|
+
model.associations[association_name]
|
28
|
+
end
|
29
|
+
|
30
|
+
if invalid_association_names.size > 0
|
31
|
+
fail "Invalid associations: #{invalid_association_names.join(', ')}"
|
32
|
+
end
|
33
|
+
|
26
34
|
new_link.tap do |new_query_proxy|
|
27
35
|
new_spec = new_query_proxy.with_associations_spec + spec
|
28
36
|
new_query_proxy.with_associations_spec.replace(new_spec)
|
@@ -37,7 +37,11 @@ module Neo4j
|
|
37
37
|
|
38
38
|
# When called at the end of a QueryProxy chain, it will return the resultant relationship objects intead of nodes.
|
39
39
|
# For example, to return the relationship between a given student and their lessons:
|
40
|
+
#
|
41
|
+
# .. code-block:: ruby
|
42
|
+
#
|
40
43
|
# student.lessons.each_rel do |rel|
|
44
|
+
#
|
41
45
|
# @return [Enumerable] An enumerable containing any number of applicable relationship objects.
|
42
46
|
def each_rel(&block)
|
43
47
|
block_given? ? each(false, true, &block) : to_enum(:each, false, true)
|
@@ -45,6 +49,9 @@ module Neo4j
|
|
45
49
|
|
46
50
|
# When called at the end of a QueryProxy chain, it will return the nodes and relationships of the last link.
|
47
51
|
# For example, to return a lesson and each relationship to a given student:
|
52
|
+
#
|
53
|
+
# .. code-block:: ruby
|
54
|
+
#
|
48
55
|
# student.lessons.each_with_rel do |lesson, rel|
|
49
56
|
def each_with_rel(&block)
|
50
57
|
block_given? ? each(true, true, &block) : to_enum(:each, true, true)
|
@@ -29,10 +29,9 @@ module Neo4j
|
|
29
29
|
arg.each do |key, value|
|
30
30
|
if model && model.association?(key)
|
31
31
|
result += for_association(key, value, "n#{node_num}", model)
|
32
|
-
|
33
32
|
node_num += 1
|
34
33
|
else
|
35
|
-
result <<
|
34
|
+
result << new_for_key_and_value(model, key, value)
|
36
35
|
end
|
37
36
|
end
|
38
37
|
elsif arg.is_a?(String)
|
@@ -42,15 +41,26 @@ module Neo4j
|
|
42
41
|
end
|
43
42
|
alias_method :for_node_where_clause, :for_where_clause
|
44
43
|
|
44
|
+
def new_for_key_and_value(model, key, value)
|
45
|
+
key = (key.to_sym == :id ? model.id_property_name : key)
|
46
|
+
|
47
|
+
val = if !model
|
48
|
+
value
|
49
|
+
elsif key == model.id_property_name && value.is_a?(Neo4j::ActiveNode)
|
50
|
+
value.id
|
51
|
+
else
|
52
|
+
model.declared_property_manager.value_for_db(key, value)
|
53
|
+
end
|
54
|
+
|
55
|
+
new(:where, ->(v, _) { {v => {key => val}} })
|
56
|
+
end
|
57
|
+
|
45
58
|
def for_association(name, value, n_string, model)
|
46
59
|
neo_id = value.try(:neo_id) || value
|
47
60
|
fail ArgumentError, "Invalid value for '#{name}' condition" if not neo_id.is_a?(Integer)
|
48
61
|
|
49
|
-
dir = model.associations[name].direction
|
50
|
-
|
51
|
-
arrow = dir == :out ? '-->' : '<--'
|
52
62
|
[
|
53
|
-
new(:match, ->(v, _) { "#{v}#{
|
63
|
+
new(:match, ->(v, _) { "#{v}#{model.associations[name].arrow_cypher}(#{n_string})" }),
|
54
64
|
new(:where, ->(_, _) { {"ID(#{n_string})" => neo_id.to_i} })
|
55
65
|
]
|
56
66
|
end
|
@@ -135,6 +135,7 @@ module Neo4j
|
|
135
135
|
# support for null object pattern
|
136
136
|
'1 = 2'
|
137
137
|
end
|
138
|
+
|
138
139
|
self.where(where_arg)
|
139
140
|
end
|
140
141
|
|
@@ -203,6 +204,9 @@ module Neo4j
|
|
203
204
|
# So for a `Teacher` model inheriting from a `Person` model and an `Article` model
|
204
205
|
# if you called .as_models([Teacher, Article])
|
205
206
|
# The where clause would look something like:
|
207
|
+
#
|
208
|
+
# .. code-block:: cypher
|
209
|
+
#
|
206
210
|
# WHERE (node_var:Teacher:Person OR node_var:Article)
|
207
211
|
def as_models(models)
|
208
212
|
where_clause = models.map do |model|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveNode
|
3
|
+
module Query
|
4
|
+
module QueryProxyUnpersisted
|
5
|
+
def defer_create(other_nodes, _properties, operator)
|
6
|
+
key = [@association.name, [nil, nil, nil]].hash
|
7
|
+
@start_object.pending_associations[key] = [@association.name, operator]
|
8
|
+
if @start_object.association_proxy_cache[key]
|
9
|
+
@start_object.association_proxy_cache[key] << other_nodes
|
10
|
+
else
|
11
|
+
@start_object.association_proxy_cache[key] = [other_nodes]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module ActiveNode
|
3
|
+
module Unpersisted
|
4
|
+
def pending_associations
|
5
|
+
@pending_associations ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def pending_associations?
|
9
|
+
!@pending_associations.blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
# TODO: Change this method's name.
|
15
|
+
# Takes the pending_associations hash, which is in the format { cache_key => [:association_name, :association_operator]},
|
16
|
+
# and returns them as { association_name => [[nodes_for_persistence], :operator] }
|
17
|
+
def pending_associations_with_nodes
|
18
|
+
return unless pending_associations?
|
19
|
+
{}.tap do |deferred_nodes|
|
20
|
+
pending_associations.each_pair do |cache_key, (association_name, operator)|
|
21
|
+
nodes_for_creation = self.persisted? ? association_proxy_cache[cache_key].select { |n| !n.persisted? } : association_proxy_cache[cache_key]
|
22
|
+
deferred_nodes[association_name] = [nodes_for_creation, operator]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param [Hash] deferred_nodes A hash created by :pending_associations_with_nodes
|
28
|
+
def process_unpersisted_nodes!(deferred_nodes)
|
29
|
+
deferred_nodes.each_pair do |k, (v, o)|
|
30
|
+
save_and_associate_queue(k, v, o)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def save_and_associate_queue(association_name, node_queue, operator)
|
36
|
+
association_proc = proc { |node| save_and_associate_node(association_name, node, operator) }
|
37
|
+
node_queue.each do |element|
|
38
|
+
element.is_a?(Array) ? element.each { |node| association_proc.call(node) } : association_proc.call(element)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def save_and_associate_node(association_name, node, operator)
|
43
|
+
node.save if node.changed? || !node.persisted?
|
44
|
+
fail "Unable to defer node persistence, could not save #{node.inspect}" unless node.persisted?
|
45
|
+
operator == :<< ? send(association_name).send(operator, node) : send(:"#{association_name}=", node)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/neo4j/active_rel.rb
CHANGED
@@ -22,6 +22,23 @@ module Neo4j
|
|
22
22
|
super
|
23
23
|
end
|
24
24
|
|
25
|
+
def inspect
|
26
|
+
attribute_pairs = attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }
|
27
|
+
attribute_descriptions = attribute_pairs.join(', ')
|
28
|
+
separator = ' ' unless attribute_descriptions.empty?
|
29
|
+
|
30
|
+
cypher_representation = "#{node_cypher_representation(from_node)}-[:#{type}]->#{node_cypher_representation(to_node)}"
|
31
|
+
"#<#{self.class.name} #{cypher_representation}#{separator}#{attribute_descriptions}>"
|
32
|
+
end
|
33
|
+
|
34
|
+
def node_cypher_representation(node)
|
35
|
+
node_class = node.class
|
36
|
+
id_name = node_class.id_property_name
|
37
|
+
labels = ':' + node_class.mapped_label_names.join(':')
|
38
|
+
|
39
|
+
"(#{labels} {#{id_name}: #{node.id.inspect}})"
|
40
|
+
end
|
41
|
+
|
25
42
|
def neo4j_obj
|
26
43
|
_persisted_obj || fail('Tried to access native neo4j object on a non persisted object')
|
27
44
|
end
|
@@ -47,7 +47,14 @@ module Neo4j::ActiveRel
|
|
47
47
|
|
48
48
|
# Same as #create, but raises an error if there is a problem during save.
|
49
49
|
def create!(*args)
|
50
|
-
|
50
|
+
props = args[0] || {}
|
51
|
+
relationship_props = extract_association_attributes!(props) || {}
|
52
|
+
new(props).tap do |obj|
|
53
|
+
relationship_props.each do |prop, value|
|
54
|
+
obj.send("#{prop}=", value)
|
55
|
+
end
|
56
|
+
obj.save!
|
57
|
+
end
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
@@ -70,9 +77,7 @@ module Neo4j::ActiveRel
|
|
70
77
|
"Node class was #{node.class} (#{node.class.object_id}), expected #{type_class} (#{type_class.object_id})"
|
71
78
|
end
|
72
79
|
|
73
|
-
def _create_rel(from_node, to_node,
|
74
|
-
props = self.class.default_property_values(self)
|
75
|
-
props.merge!(args[0]) if args[0].is_a?(Hash)
|
80
|
+
def _create_rel(from_node, to_node, props = {})
|
76
81
|
set_classname(props, true)
|
77
82
|
|
78
83
|
if from_node.id.nil? || to_node.id.nil?
|
@@ -91,7 +96,7 @@ module Neo4j::ActiveRel
|
|
91
96
|
end
|
92
97
|
|
93
98
|
def create_method
|
94
|
-
self.class.
|
99
|
+
self.class.creates_unique? ? :create_unique : :create
|
95
100
|
end
|
96
101
|
end
|
97
102
|
end
|
@@ -19,8 +19,8 @@ module Neo4j::ActiveRel
|
|
19
19
|
self.class._type
|
20
20
|
end
|
21
21
|
|
22
|
-
def initialize(attributes =
|
23
|
-
super(attributes
|
22
|
+
def initialize(attributes = nil)
|
23
|
+
super(attributes)
|
24
24
|
send_props(@relationship_props) unless @relationship_props.nil?
|
25
25
|
end
|
26
26
|
|
@@ -28,6 +28,7 @@ module Neo4j::ActiveRel
|
|
28
28
|
# Extracts keys from attributes hash which are relationships of the model
|
29
29
|
# TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save?
|
30
30
|
def extract_association_attributes!(attributes)
|
31
|
+
return if attributes.blank?
|
31
32
|
{}.tap do |relationship_props|
|
32
33
|
attributes.each_key do |key|
|
33
34
|
relationship_props[key] = attributes.delete(key) if [:from_node, :to_node].include?(key)
|
@@ -55,12 +56,23 @@ module Neo4j::ActiveRel
|
|
55
56
|
Neo4j::Node.load(id)
|
56
57
|
end
|
57
58
|
|
59
|
+
def creates_unique
|
60
|
+
@creates_unique = true
|
61
|
+
end
|
62
|
+
|
58
63
|
def creates_unique_rel
|
59
|
-
|
64
|
+
warning = <<-WARNING
|
65
|
+
creates_unique_rel() is deprecated and will be removed from future releases,
|
66
|
+
use creates_unique() instead.
|
67
|
+
WARNING
|
68
|
+
|
69
|
+
ActiveSupport::Deprecation.warn(warning, caller)
|
70
|
+
|
71
|
+
creates_unique
|
60
72
|
end
|
61
73
|
|
62
|
-
def
|
63
|
-
!!@
|
74
|
+
def creates_unique?
|
75
|
+
!!@creates_unique
|
64
76
|
end
|
65
77
|
end
|
66
78
|
|
data/lib/neo4j/railtie.rb
CHANGED
@@ -11,7 +11,6 @@ module Neo4j
|
|
11
11
|
end
|
12
12
|
|
13
13
|
rake_tasks do
|
14
|
-
load 'neo4j/tasks/neo4j_server.rake'
|
15
14
|
load 'neo4j/tasks/migration.rake'
|
16
15
|
end
|
17
16
|
|
@@ -71,6 +70,8 @@ module Neo4j
|
|
71
70
|
def register_neo4j_cypher_logging
|
72
71
|
return if @neo4j_cypher_logging_registered
|
73
72
|
|
73
|
+
Neo4j::Core::Query.pretty_cypher = Neo4j::Config[:pretty_logged_cypher_queries]
|
74
|
+
|
74
75
|
Neo4j::Server::CypherSession.log_with do |message|
|
75
76
|
(Neo4j::Config[:logger] || Rails.logger).info message
|
76
77
|
end
|
@@ -113,6 +113,16 @@ module Neo4j::Shared
|
|
113
113
|
@upstream_primitives ||= {}
|
114
114
|
end
|
115
115
|
|
116
|
+
def value_for_db(key, value)
|
117
|
+
return value unless registered_properties[key]
|
118
|
+
convert_property(key, value, :to_db)
|
119
|
+
end
|
120
|
+
|
121
|
+
def value_for_ruby(key, value)
|
122
|
+
return unless registered_properties[key]
|
123
|
+
convert_property(key, value, :to_ruby)
|
124
|
+
end
|
125
|
+
|
116
126
|
protected
|
117
127
|
|
118
128
|
# Prevents repeated calls to :_attribute_type, which isn't free and never changes.
|
@@ -12,16 +12,16 @@ module Neo4j::Shared
|
|
12
12
|
private
|
13
13
|
|
14
14
|
def convert_and_assign_attributes(properties)
|
15
|
-
@attributes ||= self.class.attributes_nil_hash
|
15
|
+
@attributes ||= Hash[self.class.attributes_nil_hash]
|
16
16
|
stringify_attributes!(@attributes, properties)
|
17
|
-
self.default_properties = properties
|
17
|
+
self.default_properties = properties if respond_to?(:default_properties=)
|
18
18
|
self.class.declared_property_manager.convert_properties_to(self, :ruby, @attributes)
|
19
19
|
end
|
20
20
|
|
21
21
|
def stringify_attributes!(attr, properties)
|
22
22
|
properties.each_pair do |k, v|
|
23
23
|
key = self.class.declared_property_manager.string_key(k)
|
24
|
-
attr[key] = v
|
24
|
+
attr[key.freeze] = v
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -17,15 +17,14 @@ module Neo4j::Shared
|
|
17
17
|
|
18
18
|
# TODO: Remove the commented :super entirely once this code is part of a release.
|
19
19
|
# It calls an init method in active_attr that has a very negative impact on performance.
|
20
|
-
def initialize(attributes =
|
21
|
-
attributes = process_attributes(attributes)
|
20
|
+
def initialize(attributes = nil)
|
21
|
+
attributes = process_attributes(attributes)
|
22
22
|
@relationship_props = self.class.extract_association_attributes!(attributes)
|
23
23
|
writer_method_props = extract_writer_methods!(attributes)
|
24
24
|
validate_attributes!(attributes)
|
25
|
-
send_props(writer_method_props)
|
25
|
+
send_props(writer_method_props)
|
26
26
|
|
27
27
|
@_persisted_obj = nil
|
28
|
-
# super(attributes, options)
|
29
28
|
end
|
30
29
|
|
31
30
|
# Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr
|
@@ -36,22 +35,8 @@ module Neo4j::Shared
|
|
36
35
|
end
|
37
36
|
alias_method :[], :read_attribute
|
38
37
|
|
39
|
-
def default_properties=(properties)
|
40
|
-
default_property_keys = self.class.default_properties_keys
|
41
|
-
@default_properties = properties.select { |key| default_property_keys.include?(key) }
|
42
|
-
end
|
43
|
-
|
44
|
-
def default_property(key)
|
45
|
-
default_properties[key.to_sym]
|
46
|
-
end
|
47
|
-
|
48
|
-
def default_properties
|
49
|
-
@default_properties ||= Hash.new(nil)
|
50
|
-
# keys = self.class.default_properties.keys
|
51
|
-
# _persisted_obj.props.reject{|key| !keys.include?(key)}
|
52
|
-
end
|
53
|
-
|
54
38
|
def send_props(hash)
|
39
|
+
return hash if hash.blank?
|
55
40
|
hash.each { |key, value| self.send("#{key}=", value) }
|
56
41
|
end
|
57
42
|
|
@@ -70,13 +55,13 @@ module Neo4j::Shared
|
|
70
55
|
# Changes attributes hash to remove relationship keys
|
71
56
|
# Raises an error if there are any keys left which haven't been defined as properties on the model
|
72
57
|
def validate_attributes!(attributes)
|
73
|
-
return attributes if attributes.
|
58
|
+
return attributes if attributes.blank?
|
74
59
|
invalid_properties = attributes.keys.map(&:to_s) - self.attributes.keys
|
75
60
|
fail UndefinedPropertyError, "Undefined properties: #{invalid_properties.join(',')}" if invalid_properties.size > 0
|
76
61
|
end
|
77
62
|
|
78
63
|
def extract_writer_methods!(attributes)
|
79
|
-
return attributes if attributes.
|
64
|
+
return attributes if attributes.blank?
|
80
65
|
{}.tap do |writer_method_props|
|
81
66
|
attributes.each_key do |key|
|
82
67
|
writer_method_props[key] = attributes.delete(key) if self.respond_to?("#{key}=")
|
@@ -86,6 +71,7 @@ module Neo4j::Shared
|
|
86
71
|
|
87
72
|
# Gives support for Rails date_select, datetime_select, time_select helpers.
|
88
73
|
def process_attributes(attributes = nil)
|
74
|
+
return attributes if attributes.blank?
|
89
75
|
multi_parameter_attributes = {}
|
90
76
|
new_attributes = {}
|
91
77
|
attributes.each_pair do |key, value|
|
@@ -179,36 +165,6 @@ module Neo4j::Shared
|
|
179
165
|
@_declared_property_manager ||= DeclaredPropertyManager.new(self)
|
180
166
|
end
|
181
167
|
|
182
|
-
# TODO: Move this to the DeclaredPropertyManager
|
183
|
-
def default_property(name, &block)
|
184
|
-
reset_default_properties(name) if default_properties.respond_to?(:size)
|
185
|
-
default_properties[name] = block
|
186
|
-
end
|
187
|
-
|
188
|
-
# @return [Hash<Symbol,Proc>]
|
189
|
-
def default_properties
|
190
|
-
@default_property ||= {}
|
191
|
-
end
|
192
|
-
|
193
|
-
def default_properties_keys
|
194
|
-
@default_properties_keys ||= default_properties.keys
|
195
|
-
end
|
196
|
-
|
197
|
-
def reset_default_properties(name_to_keep)
|
198
|
-
default_properties.each_key do |property|
|
199
|
-
@default_properties_keys = nil
|
200
|
-
undef_method(property) unless property == name_to_keep
|
201
|
-
end
|
202
|
-
@default_properties_keys = nil
|
203
|
-
@default_property = {}
|
204
|
-
end
|
205
|
-
|
206
|
-
def default_property_values(instance)
|
207
|
-
default_properties.each_with_object({}) do |(key, block), result|
|
208
|
-
result[key] = block.call(instance)
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
168
|
def attribute!(name, options = {})
|
213
169
|
super(name, options)
|
214
170
|
define_method("#{name}=") do |value|
|
File without changes
|