neo4j 8.1.5 → 8.2.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 +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
|