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 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