neo4j 8.1.5 → 8.2.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 +10 -0
- data/Gemfile +5 -3
- data/lib/neo4j/active_node/has_n.rb +15 -2
- data/lib/neo4j/active_node/has_n/association.rb +2 -2
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +143 -28
- data/lib/neo4j/active_rel/rel_wrapper.rb +3 -2
- data/lib/neo4j/version.rb +1 -1
- data/neo4j.gemspec +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 895f04165de18db60874c876b6be4a1118f8622a
|
4
|
+
data.tar.gz: 031cde1a701badd8ebe273c1abc6b753df0d14c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4bccc04892a44df3dae9bd29858b771e6e8c98c7e00a59e31b88df1cd4835476044f57dd76376e92c17d3973e1e2f77a1ed256be8808109a38fe188a8300b935
|
7
|
+
data.tar.gz: 751d813901d03a86174ea3b6677bb643668987dbb385b278a042f1d64cf3fbd51ad02723fe73f2b5c14c8d81bd7ba1279ae39fe9c82963fdc96caad1ffefe247
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file.
|
|
3
3
|
This file should follow the standards specified on [http://keepachangelog.com/]
|
4
4
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
5
5
|
|
6
|
+
## [8.2.1] 2017-09-01
|
7
|
+
|
8
|
+
- Bringing forward changes from 8.1.x
|
9
|
+
|
10
|
+
## [8.2.0] 2017-09-01
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Ability to load nested associations with one query using `with_associations` (big thanks to @klobuczek / see #1398)
|
15
|
+
|
6
16
|
## [8.1.4] 2017-08-17
|
7
17
|
|
8
18
|
### Fixed
|
data/Gemfile
CHANGED
@@ -4,9 +4,11 @@ gemspec
|
|
4
4
|
|
5
5
|
# gem 'neo4j-core', github: 'neo4jrb/neo4j-core', branch: 'master' if ENV['CI']
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
branch = ENV['TRAVIS_PULL_REQUEST_BRANCH'] || ENV['TRAVIS_BRANCH']
|
8
|
+
slug = ENV['TRAVIS_PULL_REQUEST_SLUG'] || ENV['TRAVIS_REPO_SLUG']
|
9
|
+
if branch
|
10
|
+
if `curl --head https://github.com/#{slug}-core/tree/#{branch} | head -1` =~ /200 OK/
|
11
|
+
gem 'neo4j-core', github: "#{slug}-core", branch: branch
|
10
12
|
else
|
11
13
|
gem 'neo4j-core', github: 'neo4jrb/neo4j-core', branch: 'master'
|
12
14
|
end
|
@@ -38,6 +38,10 @@ module Neo4j::ActiveNode
|
|
38
38
|
result_nodes.each(&block)
|
39
39
|
end
|
40
40
|
|
41
|
+
def each_rel(&block)
|
42
|
+
rels.each(&block)
|
43
|
+
end
|
44
|
+
|
41
45
|
# .count always hits the database
|
42
46
|
def_delegator :@query_proxy, :count
|
43
47
|
|
@@ -92,9 +96,18 @@ module Neo4j::ActiveNode
|
|
92
96
|
@enumerable = (@cached_result || @query_proxy)
|
93
97
|
end
|
94
98
|
|
95
|
-
def
|
99
|
+
def init_cache
|
100
|
+
@cached_rels ||= []
|
96
101
|
@cached_result ||= []
|
97
|
-
|
102
|
+
end
|
103
|
+
|
104
|
+
def add_to_cache(object, rel = nil)
|
105
|
+
@cached_rels << rel if rel
|
106
|
+
(@cached_result ||= []) << object
|
107
|
+
end
|
108
|
+
|
109
|
+
def rels
|
110
|
+
@cached_rels || super
|
98
111
|
end
|
99
112
|
|
100
113
|
def cache_query_proxy_result
|
@@ -79,11 +79,11 @@ module Neo4j
|
|
79
79
|
@target_classes_or_nil ||= discovered_model if target_class_names
|
80
80
|
end
|
81
81
|
|
82
|
-
def target_where_clause
|
82
|
+
def target_where_clause(var = name)
|
83
83
|
return if model_class == false
|
84
84
|
|
85
85
|
Array.new(target_classes).map do |target_class|
|
86
|
-
"#{
|
86
|
+
"#{var}:`#{target_class.mapped_label_name}`"
|
87
87
|
end.join(' OR ')
|
88
88
|
end
|
89
89
|
|
@@ -2,58 +2,173 @@ module Neo4j
|
|
2
2
|
module ActiveNode
|
3
3
|
module Query
|
4
4
|
module QueryProxyEagerLoading
|
5
|
-
|
6
|
-
|
5
|
+
class IdentityMap < Hash
|
6
|
+
def add(node)
|
7
|
+
self[node.neo_id] ||= node
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class AssociationTree < Hash
|
12
|
+
attr_accessor :model, :name, :association, :path
|
13
|
+
|
14
|
+
def initialize(model, name = nil)
|
15
|
+
super()
|
16
|
+
self.model = name ? target_class(model, name) : model
|
17
|
+
self.name = name
|
18
|
+
self.association = name ? model.associations[name] : nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def clone
|
22
|
+
super.tap { |copy| copy.each { |key, value| copy[key] = value.clone } }
|
23
|
+
end
|
7
24
|
|
8
|
-
|
9
|
-
|
10
|
-
|
25
|
+
def add_spec(spec)
|
26
|
+
if spec.is_a?(Array)
|
27
|
+
spec.each { |s| add_spec(s) }
|
28
|
+
elsif spec.is_a?(Hash)
|
29
|
+
spec.each { |k, v| (self[k] ||= AssociationTree.new(model, k)).add_spec(v) }
|
30
|
+
else
|
31
|
+
self[spec] ||= AssociationTree.new(model, spec)
|
11
32
|
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def paths(*prefix)
|
36
|
+
values.flat_map { |v| [[*prefix, v]] + v.paths(*prefix, v) }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
12
40
|
|
13
|
-
|
41
|
+
def target_class(model, key)
|
42
|
+
association = model.associations[key]
|
43
|
+
fail "Invalid association: #{[*path, key].join('.')}" unless association
|
44
|
+
model.associations[key].target_class
|
14
45
|
end
|
15
46
|
end
|
16
47
|
|
17
|
-
def
|
18
|
-
|
48
|
+
def pluck_vars(node, rel)
|
49
|
+
return super if with_associations_tree.size.zero?
|
50
|
+
|
51
|
+
perform_query
|
19
52
|
end
|
20
53
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
54
|
+
def perform_query
|
55
|
+
@_cache = IdentityMap.new
|
56
|
+
query_from_association_tree
|
57
|
+
.map do |record, eager_data|
|
58
|
+
cache_and_init(record, with_associations_tree)
|
59
|
+
eager_data.zip(with_associations_tree.paths.map(&:last)).each do |eager_records, element|
|
60
|
+
eager_records.first.zip(eager_records.last).each do |eager_record|
|
61
|
+
add_to_cache(*eager_record, element)
|
62
|
+
end
|
63
|
+
end
|
25
64
|
|
26
|
-
|
27
|
-
fail "Invalid associations: #{invalid_association_names.join(', ')}"
|
65
|
+
record
|
28
66
|
end
|
67
|
+
end
|
29
68
|
|
69
|
+
def with_associations(*spec)
|
30
70
|
new_link.tap do |new_query_proxy|
|
31
|
-
|
32
|
-
new_query_proxy.
|
71
|
+
new_query_proxy.with_associations_tree = with_associations_tree.clone
|
72
|
+
new_query_proxy.with_associations_tree.add_spec(spec)
|
33
73
|
end
|
34
74
|
end
|
35
75
|
|
76
|
+
def with_associations_tree
|
77
|
+
@with_associations_tree ||= AssociationTree.new(model)
|
78
|
+
end
|
79
|
+
|
80
|
+
def with_associations_tree=(tree)
|
81
|
+
@with_associations_tree = tree
|
82
|
+
end
|
83
|
+
|
84
|
+
def first
|
85
|
+
(query.clause?(:order) ? self : order(order_property)).limit(1).to_a.first
|
86
|
+
end
|
87
|
+
|
36
88
|
private
|
37
89
|
|
38
|
-
def
|
39
|
-
|
90
|
+
def add_to_cache(rel, node, element)
|
91
|
+
direction = element.association.direction
|
92
|
+
node = cache_and_init(node, element)
|
93
|
+
if rel.is_a?(Neo4j::ActiveRel)
|
94
|
+
rel.instance_variable_set(direction == :in ? '@from_node' : '@to_node', node)
|
95
|
+
end
|
96
|
+
@_cache[direction == :out ? rel.start_node_neo_id : rel.end_node_neo_id]
|
97
|
+
.association_proxy(element.name).add_to_cache(node, rel)
|
98
|
+
end
|
99
|
+
|
100
|
+
def init_associations(node, element)
|
101
|
+
element.keys.each { |key| node.association_proxy(key).init_cache }
|
102
|
+
end
|
103
|
+
|
104
|
+
def cache_and_init(node, element)
|
105
|
+
@_cache.add(node).tap { |n| init_associations(n, element) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def with_associations_return_clause(variables = path_names)
|
109
|
+
var_list(variables, &:itself)
|
110
|
+
end
|
111
|
+
|
112
|
+
def var_list(variables)
|
113
|
+
variables.map { |n| yield(escape("#{n}_collection")) }.join(',')
|
40
114
|
end
|
41
115
|
|
42
|
-
|
116
|
+
# In neo4j version 2.1.8 this fails due to a bug:
|
117
|
+
# MATCH (`n`) WITH `n` RETURN `n`
|
118
|
+
# but this
|
119
|
+
# MATCH (`n`) WITH n RETURN `n`
|
120
|
+
# and this
|
121
|
+
# MATCH (`n`) WITH `n` AS `n` RETURN `n`
|
122
|
+
# does not
|
123
|
+
def var_list_fixing_neo4j_2_1_8_bug(variables)
|
124
|
+
var_list(variables) { |var| "#{var} AS #{var}" }
|
125
|
+
end
|
126
|
+
|
127
|
+
def escape(s)
|
128
|
+
"`#{s}`"
|
129
|
+
end
|
130
|
+
|
131
|
+
def path_name(path)
|
132
|
+
path.map(&:name).join('.')
|
133
|
+
end
|
134
|
+
|
135
|
+
def path_names
|
136
|
+
with_associations_tree.paths.map { |path| path_name(path) }
|
137
|
+
end
|
138
|
+
|
139
|
+
def query_from_association_tree
|
43
140
|
previous_with_variables = []
|
44
|
-
|
45
|
-
with_association_query_part(query,
|
46
|
-
previous_with_variables <<
|
141
|
+
with_associations_tree.paths.inject(query_as(identity).with(identity)) do |query, path|
|
142
|
+
with_association_query_part(query, path, previous_with_variables).tap do
|
143
|
+
previous_with_variables << path_name(path)
|
47
144
|
end
|
48
|
-
end.
|
145
|
+
end.pluck(identity, "[#{with_associations_return_clause}]")
|
49
146
|
end
|
50
147
|
|
51
|
-
def with_association_query_part(base_query,
|
52
|
-
|
148
|
+
def with_association_query_part(base_query, path, previous_with_variables)
|
149
|
+
optional_match_with_where(base_query, path)
|
150
|
+
.with(identity,
|
151
|
+
"[collect(#{escape("#{path_name(path)}_rel")}), collect(#{escape path_name(path)})] AS #{escape("#{path_name(path)}_collection")}",
|
152
|
+
*var_list_fixing_neo4j_2_1_8_bug(previous_with_variables))
|
153
|
+
end
|
154
|
+
|
155
|
+
def optional_match_with_where(base_query, path)
|
156
|
+
path
|
157
|
+
.each_with_index.map { |_, index| path[0..index] }
|
158
|
+
.inject(optional_match(base_query, path)) do |query, path_prefix|
|
159
|
+
query.where(path_prefix.last.association.target_where_clause(escape(path_name(path_prefix))))
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def optional_match(base_query, path)
|
164
|
+
base_query.optional_match(
|
165
|
+
"(#{identity})#{path.each_with_index.map do |element, index|
|
166
|
+
relationship_part(element.association, path_name(path[0..index]))
|
167
|
+
end.join}")
|
168
|
+
end
|
53
169
|
|
54
|
-
|
55
|
-
|
56
|
-
.with(identity, "collect(#{association_name}) AS #{association_name}_collection", *with_associations_return_clause(previous_with_variables))
|
170
|
+
def relationship_part(association, path_name)
|
171
|
+
"#{association.arrow_cypher(escape("#{path_name}_rel"))}(#{escape(path_name)})"
|
57
172
|
end
|
58
173
|
end
|
59
174
|
end
|
@@ -11,8 +11,9 @@ module Neo4j
|
|
11
11
|
def wrapper(rel)
|
12
12
|
rel.props.symbolize_keys!
|
13
13
|
begin
|
14
|
-
most_concrete_class = class_from_type(rel.rel_type)
|
15
|
-
most_concrete_class
|
14
|
+
most_concrete_class = class_from_type(rel.rel_type).constantize
|
15
|
+
return rel unless most_concrete_class < Neo4j::ActiveRel
|
16
|
+
most_concrete_class.new
|
16
17
|
rescue NameError => e
|
17
18
|
raise e unless e.message =~ /(uninitialized|wrong) constant/
|
18
19
|
|
data/lib/neo4j/version.rb
CHANGED
data/neo4j.gemspec
CHANGED
@@ -30,7 +30,7 @@ A Neo4j OGM (Object-Graph-Mapper) for Ruby heavily inspired by ActiveRecord.
|
|
30
30
|
s.add_dependency('orm_adapter', '~> 0.5.0')
|
31
31
|
s.add_dependency('activemodel', '>= 4.0')
|
32
32
|
s.add_dependency('activesupport', '>= 4.0')
|
33
|
-
s.add_dependency('neo4j-core', '>= 7.
|
33
|
+
s.add_dependency('neo4j-core', '>= 7.2.2')
|
34
34
|
s.add_dependency('neo4j-community', '~> 2.0') if RUBY_PLATFORM =~ /java/
|
35
35
|
s.add_development_dependency('railties', '>= 4.0')
|
36
36
|
s.add_development_dependency('pry')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: neo4j
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.1
|
4
|
+
version: 8.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andreas Ronge, Brian Underwood, Chris Grigg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: orm_adapter
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 7.
|
61
|
+
version: 7.2.2
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 7.
|
68
|
+
version: 7.2.2
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: railties
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -349,7 +349,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
349
349
|
version: '0'
|
350
350
|
requirements: []
|
351
351
|
rubyforge_project: neo4j
|
352
|
-
rubygems_version: 2.6.
|
352
|
+
rubygems_version: 2.6.13
|
353
353
|
signing_key:
|
354
354
|
specification_version: 4
|
355
355
|
summary: A graph database for Ruby
|