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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13d58b195820ff94f9899f26eb281f1621b1ea64
4
- data.tar.gz: a49c32fd9ef2a70e2b825d6de8cbf169a61fe88f
3
+ metadata.gz: 895f04165de18db60874c876b6be4a1118f8622a
4
+ data.tar.gz: 031cde1a701badd8ebe273c1abc6b753df0d14c3
5
5
  SHA512:
6
- metadata.gz: 740c3277e7d4a5ec4c34965adf2e4fa871cbe59f3658d89f396861ca0d12d8129426dddc186aab2c940d35134bd077e92650fc2b355d87e5370afd907395ff00
7
- data.tar.gz: f4e8ba8d4d366202a803b8bc035d31868ca8aa2cd546807021a1ad0e3df220a999b5520add6dd9e7c1c9a874f061d4b6cfd3aa9f63c7debc20b1fbd7d54eda57
6
+ metadata.gz: 4bccc04892a44df3dae9bd29858b771e6e8c98c7e00a59e31b88df1cd4835476044f57dd76376e92c17d3973e1e2f77a1ed256be8808109a38fe188a8300b935
7
+ data.tar.gz: 751d813901d03a86174ea3b6677bb643668987dbb385b278a042f1d64cf3fbd51ad02723fe73f2b5c14c8d81bd7ba1279ae39fe9c82963fdc96caad1ffefe247
@@ -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
- if branch = ENV['TRAVIS_BRANCH']
8
- if `curl --head https://github.com/#{ENV['TRAVIS_REPO_SLUG']}-core/tree/#{branch} | head -1` =~ /200 OK/
9
- gem 'neo4j-core', github: "#{ENV['TRAVIS_REPO_SLUG']}-core", branch: branch
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 add_to_cache(object)
99
+ def init_cache
100
+ @cached_rels ||= []
96
101
  @cached_result ||= []
97
- @cached_result << object
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
- "#{name}:`#{target_class.mapped_label_name}`"
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
- def each(node = true, rel = nil, &block)
6
- return super if with_associations_spec.size.zero?
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
- query_from_association_spec.pluck(identity, "[#{with_associations_return_clause}]").map do |record, eager_data|
9
- eager_data.each_with_index do |eager_records, index|
10
- record.association_proxy(with_associations_spec[index]).cache_result(eager_records)
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
- yield(record)
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 with_associations_spec
18
- @with_associations_spec ||= []
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 with_associations(*spec)
22
- invalid_association_names = spec.reject do |association_name|
23
- model.associations[association_name]
24
- end
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
- if !invalid_association_names.empty?
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
- new_spec = new_query_proxy.with_associations_spec + spec
32
- new_query_proxy.with_associations_spec.replace(new_spec)
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 with_associations_return_clause(variables = with_associations_spec)
39
- variables.map { |n| "#{n}_collection" }.join(',')
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
- def query_from_association_spec
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
- with_associations_spec.inject(query_as(identity).with(identity)) do |query, association_name|
45
- with_association_query_part(query, association_name, previous_with_variables).tap do
46
- previous_with_variables << association_name
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.return(identity)
145
+ end.pluck(identity, "[#{with_associations_return_clause}]")
49
146
  end
50
147
 
51
- def with_association_query_part(base_query, association_name, previous_with_variables)
52
- association = model.associations[association_name]
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
- base_query.optional_match("(#{identity})#{association.arrow_cypher}(#{association_name})")
55
- .where(association.target_where_clause)
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.constantize.new
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
 
@@ -1,3 +1,3 @@
1
1
  module Neo4j
2
- VERSION = '8.1.5'
2
+ VERSION = '8.2.1'
3
3
  end
@@ -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.0.0')
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.5
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-08-18 00:00:00.000000000 Z
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.0.0
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.0.0
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.12
352
+ rubygems_version: 2.6.13
353
353
  signing_key:
354
354
  specification_version: 4
355
355
  summary: A graph database for Ruby