neo4j 5.0.15 → 5.1.0.rc.1
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.
- 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
|