neo4j 7.2.3 → 8.0.0.alpha.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.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -46
  3. data/Gemfile +15 -14
  4. data/README.md +21 -14
  5. data/bin/neo4j-jars +1 -1
  6. data/lib/neo4j.rb +12 -1
  7. data/lib/neo4j/active_base.rb +68 -0
  8. data/lib/neo4j/active_base/session_registry.rb +12 -0
  9. data/lib/neo4j/active_node.rb +13 -21
  10. data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +6 -6
  11. data/lib/neo4j/active_node/enum.rb +3 -6
  12. data/lib/neo4j/active_node/has_n.rb +24 -19
  13. data/lib/neo4j/active_node/has_n/association.rb +6 -2
  14. data/lib/neo4j/active_node/has_n/association/rel_factory.rb +1 -1
  15. data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +1 -1
  16. data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +1 -1
  17. data/lib/neo4j/active_node/id_property.rb +52 -15
  18. data/lib/neo4j/active_node/labels.rb +32 -10
  19. data/lib/neo4j/active_node/labels/index.rb +5 -55
  20. data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
  21. data/lib/neo4j/active_node/node_wrapper.rb +39 -37
  22. data/lib/neo4j/active_node/persistence.rb +27 -13
  23. data/lib/neo4j/active_node/query/query_proxy.rb +11 -9
  24. data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +4 -4
  25. data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +1 -0
  26. data/lib/neo4j/active_node/query/query_proxy_link.rb +13 -9
  27. data/lib/neo4j/active_node/query/query_proxy_methods.rb +76 -8
  28. data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
  29. data/lib/neo4j/active_node/query_methods.rb +3 -3
  30. data/lib/neo4j/active_node/scope.rb +24 -7
  31. data/lib/neo4j/active_rel.rb +21 -3
  32. data/lib/neo4j/active_rel/initialize.rb +2 -2
  33. data/lib/neo4j/active_rel/persistence.rb +32 -6
  34. data/lib/neo4j/active_rel/persistence/query_factory.rb +3 -3
  35. data/lib/neo4j/active_rel/property.rb +9 -9
  36. data/lib/neo4j/active_rel/query.rb +6 -4
  37. data/lib/neo4j/active_rel/rel_wrapper.rb +24 -16
  38. data/lib/neo4j/active_rel/related_node.rb +5 -1
  39. data/lib/neo4j/active_rel/types.rb +2 -2
  40. data/lib/neo4j/config.rb +0 -1
  41. data/lib/neo4j/errors.rb +3 -0
  42. data/lib/neo4j/migration.rb +90 -71
  43. data/lib/neo4j/migrations.rb +10 -0
  44. data/lib/neo4j/migrations/base.rb +44 -0
  45. data/lib/neo4j/migrations/helpers.rb +101 -0
  46. data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
  47. data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
  48. data/lib/neo4j/migrations/helpers/schema.rb +53 -0
  49. data/lib/neo4j/migrations/migration_file.rb +24 -0
  50. data/lib/neo4j/migrations/runner.rb +110 -0
  51. data/lib/neo4j/migrations/schema_migration.rb +9 -0
  52. data/lib/neo4j/model_schema.rb +100 -0
  53. data/lib/neo4j/railtie.rb +29 -110
  54. data/lib/neo4j/schema/operation.rb +24 -13
  55. data/lib/neo4j/session_manager.rb +137 -0
  56. data/lib/neo4j/shared.rb +20 -11
  57. data/lib/neo4j/shared/attributes.rb +10 -16
  58. data/lib/neo4j/shared/callbacks.rb +3 -3
  59. data/lib/neo4j/shared/cypher.rb +1 -1
  60. data/lib/neo4j/shared/declared_properties.rb +1 -1
  61. data/lib/neo4j/shared/declared_property.rb +1 -1
  62. data/lib/neo4j/shared/enum.rb +6 -18
  63. data/lib/neo4j/shared/identity.rb +27 -21
  64. data/lib/neo4j/shared/persistence.rb +26 -17
  65. data/lib/neo4j/shared/property.rb +5 -2
  66. data/lib/neo4j/shared/query_factory.rb +4 -5
  67. data/lib/neo4j/shared/type_converters.rb +8 -9
  68. data/lib/neo4j/shared/validations.rb +1 -5
  69. data/lib/neo4j/tasks/migration.rake +83 -2
  70. data/lib/neo4j/version.rb +1 -1
  71. data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
  72. data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
  73. data/lib/rails/generators/neo4j/model/model_generator.rb +1 -3
  74. data/lib/rails/generators/neo4j_generator.rb +1 -0
  75. data/neo4j.gemspec +3 -3
  76. metadata +58 -65
  77. data/bin/rake +0 -17
  78. data/lib/neo4j/shared/permitted_attributes.rb +0 -28
@@ -1,52 +1,54 @@
1
1
  require 'active_support/inflector'
2
+ require 'neo4j/core/node'
2
3
 
3
- class Neo4j::Node
4
- # The wrapping process is what transforms a raw CypherNode or EmbeddedNode from Neo4j::Core into a healthy ActiveNode (or ActiveRel) object.
5
- module Wrapper
6
- # this is a plugin in the neo4j-core so that the Ruby wrapper will be wrapped around the Neo4j::Node objects
7
- def wrapper
8
- found_class = class_to_wrap
9
- return self if not found_class
4
+ wrapping_proc = proc do |node|
5
+ found_class = Neo4j::NodeWrapping.class_to_wrap(node.labels)
6
+ next node if not found_class
10
7
 
11
- found_class.new.tap do |wrapped_node|
12
- wrapped_node.init_on_load(self, self.props)
13
- end
14
- end
8
+ found_class.new.tap do |wrapped_node|
9
+ wrapped_node.init_on_load(node, node.props)
10
+ end
11
+ end
12
+ Neo4j::Core::Node.wrapper_callback(wrapping_proc)
15
13
 
16
- def class_to_wrap
17
- load_classes_from_labels
18
- Neo4j::ActiveNode::Labels.model_for_labels(labels).tap do |model_class|
19
- Neo4j::Node::Wrapper.populate_constants_for_labels_cache(model_class, labels)
20
- end
21
- end
14
+ module Neo4j
15
+ module NodeWrapping
16
+ # Only load classes once for performance
17
+ CONSTANTS_FOR_LABELS_CACHE = {}
22
18
 
23
- private
19
+ class << self
20
+ def class_to_wrap(labels)
21
+ load_classes_from_labels(labels)
22
+ Neo4j::ActiveNode::Labels.model_for_labels(labels).tap do |model_class|
23
+ populate_constants_for_labels_cache(model_class, labels)
24
+ end
25
+ end
24
26
 
25
- def load_classes_from_labels
26
- labels.each { |label| Neo4j::Node::Wrapper.constant_for_label(label) }
27
- end
27
+ private
28
28
 
29
- # Only load classes once for performance
30
- CONSTANTS_FOR_LABELS_CACHE = {}
29
+ def load_classes_from_labels(labels)
30
+ labels.each { |label| constant_for_label(label) }
31
+ end
31
32
 
32
- def self.constant_for_label(label)
33
- CONSTANTS_FOR_LABELS_CACHE[label] || CONSTANTS_FOR_LABELS_CACHE[label] = constantized_label(label)
34
- end
33
+ def constant_for_label(label)
34
+ CONSTANTS_FOR_LABELS_CACHE[label] || CONSTANTS_FOR_LABELS_CACHE[label] = constantized_label(label)
35
+ end
35
36
 
36
- def self.constantized_label(label)
37
- "#{association_model_namespace}::#{label}".constantize
38
- rescue NameError
39
- nil
40
- end
37
+ def constantized_label(label)
38
+ "#{association_model_namespace}::#{label}".constantize
39
+ rescue NameError
40
+ nil
41
+ end
41
42
 
42
- def self.populate_constants_for_labels_cache(model_class, labels)
43
- labels.each do |label|
44
- CONSTANTS_FOR_LABELS_CACHE[label] = model_class if CONSTANTS_FOR_LABELS_CACHE[label].nil?
43
+ def populate_constants_for_labels_cache(model_class, labels)
44
+ labels.each do |label|
45
+ CONSTANTS_FOR_LABELS_CACHE[label] = model_class if CONSTANTS_FOR_LABELS_CACHE[label].nil?
46
+ end
45
47
  end
46
- end
47
48
 
48
- def self.association_model_namespace
49
- Neo4j::Config.association_model_namespace_string
49
+ def association_model_namespace
50
+ Neo4j::Config.association_model_namespace_string
51
+ end
50
52
  end
51
53
  end
52
54
  end
@@ -33,8 +33,7 @@ module Neo4j::ActiveNode
33
33
  # @param [Symbol, String] name of the attribute to increment
34
34
  # @param [Integer, Float] amount to increment
35
35
  def concurrent_increment!(attribute, by = 1)
36
- query_node = Neo4j::Session.query.match_nodes(n: neo_id)
37
- increment_by_query! query_node, attribute, by
36
+ increment_by_query! query_as(:n), attribute, by
38
37
  end
39
38
 
40
39
  # Persist the object to the database. Validations and Callbacks are included
@@ -67,7 +66,8 @@ module Neo4j::ActiveNode
67
66
  # @param [Array] labels The labels to use for creating the new node.
68
67
  # @return [Neo4j::Node] A CypherNode or EmbeddedNode
69
68
  def _create_node(node_props, labels = labels_for_create)
70
- self.class.neo4j_session.create_node(node_props, labels)
69
+ query = "CREATE (n:`#{Array(labels).join('`:`')}`) SET n = {props} RETURN n"
70
+ neo4j_query(query, {props: node_props}, wrap_level: :core_entity).to_a[0].n
71
71
  end
72
72
 
73
73
  # As the name suggests, this inserts the primary key (id property) into the properties hash.
@@ -88,11 +88,15 @@ module Neo4j::ActiveNode
88
88
 
89
89
  private
90
90
 
91
+ def destroy_query
92
+ query_as(:n).break.optional_match('(n)-[r]-()').delete(:n, :r)
93
+ end
94
+
91
95
  # The pending associations are cleared during the save process, so it's necessary to
92
96
  # build the processable hash before it begins. If there are nodes and associations that
93
97
  # need to be created after the node is saved, a new transaction is started.
94
98
  def cascade_save
95
- Neo4j::Transaction.run(pending_deferred_creations?) do
99
+ self.class.run_transaction(pending_deferred_creations?) do
96
100
  result = yield
97
101
  process_unpersisted_nodes!
98
102
  result
@@ -124,19 +128,19 @@ module Neo4j::ActiveNode
124
128
  optional_attrs.default = {}
125
129
  on_create_attrs, on_match_attrs, set_attrs = optional_attrs.values_at(*options)
126
130
 
127
- neo4j_session.query.merge(n: {self.mapped_label_names => match_attributes})
128
- .on_create_set(on_create_clause(on_create_attrs))
129
- .on_match_set(on_match_clause(on_match_attrs))
130
- .break.set(n: set_attrs)
131
- .pluck(:n).first
131
+ new_query.merge(n: {self.mapped_label_names => match_attributes})
132
+ .on_create_set(on_create_clause(on_create_attrs))
133
+ .on_match_set(on_match_clause(on_match_attrs))
134
+ .break.set(n: set_attrs)
135
+ .pluck(:n).first
132
136
  end
133
137
 
134
138
  def find_or_create(find_attributes, set_attributes = {})
135
139
  on_create_attributes = set_attributes.reverse_merge(find_attributes.merge(self.new(find_attributes).props_for_create))
136
140
 
137
- neo4j_session.query.merge(n: {self.mapped_label_names => find_attributes})
138
- .on_create_set(n: on_create_attributes)
139
- .pluck(:n).first
141
+ new_query.merge(n: {self.mapped_label_names => find_attributes})
142
+ .on_create_set(n: on_create_attributes)
143
+ .pluck(:n).first
140
144
  end
141
145
 
142
146
  # Finds the first node with the given attributes, or calls create if none found
@@ -149,8 +153,18 @@ module Neo4j::ActiveNode
149
153
  find_by(attributes) || create!(attributes, &block)
150
154
  end
151
155
 
156
+ def find_or_initialize_by(attributes)
157
+ find_by(attributes) || new(attributes).tap { |o| yield(o) if block_given? }
158
+ end
159
+
152
160
  def load_entity(id)
153
- Neo4j::Node.load(id)
161
+ query = query_base_for(id, :n).return(:n)
162
+ result = neo4j_query(query).first
163
+ result && result.n
164
+ end
165
+
166
+ def query_base_for(neo_id, var = :n)
167
+ Neo4j::ActiveBase.new_query.match(var).where(var => {neo_id: neo_id})
154
168
  end
155
169
 
156
170
  private
@@ -54,7 +54,8 @@ module Neo4j
54
54
  end
55
55
 
56
56
  def inspect
57
- "#<QueryProxy #{@context} CYPHER: #{self.to_cypher.inspect}>"
57
+ formatted_nodes = Neo4j::ActiveNode::NodeListFormatter.new(to_a)
58
+ "#<QueryProxy #{@context} #{formatted_nodes.inspect}>"
58
59
  end
59
60
 
60
61
  attr_reader :start_object, :query_proxy
@@ -65,7 +66,7 @@ module Neo4j
65
66
  def identity
66
67
  @node_var || _result_string
67
68
  end
68
- alias_method :node_identity, :identity
69
+ alias node_identity identity
69
70
 
70
71
  # The relationship identifier most recently used by the QueryProxy chain.
71
72
  attr_reader :rel_var
@@ -144,10 +145,10 @@ module Neo4j
144
145
  define_method(method) { |*args| build_deeper_query_proxy(method.to_sym, args) }
145
146
  end
146
147
  # Since there are rel_where and rel_order methods, it seems only natural for there to be node_where and node_order
147
- alias_method :node_where, :where
148
- alias_method :node_order, :order
149
- alias_method :offset, :skip
150
- alias_method :order_by, :order
148
+ alias node_where where
149
+ alias node_order order
150
+ alias offset skip
151
+ alias order_by order
151
152
 
152
153
  # Cypher string for the QueryProxy's query. This will not include params. For the full output, see <tt>to_cypher_with_params</tt>.
153
154
  delegate :to_cypher, to: :query
@@ -186,7 +187,8 @@ module Neo4j
186
187
  #
187
188
  # @return [QueryProxy] A new QueryProxy
188
189
  def branch(&block)
189
- fail LocalJumpError, 'no block given' if block.nil?
190
+ fail LocalJumpError, 'no block given' if !block
191
+
190
192
  instance_eval(&block).query.proxy_as(self.model, identity)
191
193
  end
192
194
 
@@ -200,7 +202,7 @@ module Neo4j
200
202
  fail 'Can only create relationships on associations' if !@association
201
203
  other_nodes = _nodeify!(*other_nodes)
202
204
 
203
- Neo4j::Transaction.run do
205
+ Neo4j::ActiveBase.run_transaction do
204
206
  other_nodes.each do |other_node|
205
207
  other_node.save unless other_node.neo_id
206
208
 
@@ -291,7 +293,7 @@ module Neo4j
291
293
  end
292
294
 
293
295
  def _query
294
- _session.query(context: @context)
296
+ Neo4j::ActiveBase.new_query(context: @context)
295
297
  end
296
298
 
297
299
  # TODO: Refactor this. Too much happening here.
@@ -10,7 +10,7 @@ module Neo4j
10
10
  record.association_proxy(with_associations_spec[index]).cache_result(eager_records)
11
11
  end
12
12
 
13
- block.call(record)
13
+ yield(record)
14
14
  end
15
15
  end
16
16
 
@@ -23,7 +23,7 @@ module Neo4j
23
23
  model.associations[association_name]
24
24
  end
25
25
 
26
- if invalid_association_names.size > 0
26
+ if !invalid_association_names.empty?
27
27
  fail "Invalid associations: #{invalid_association_names.join(', ')}"
28
28
  end
29
29
 
@@ -52,8 +52,8 @@ module Neo4j
52
52
  association = model.associations[association_name]
53
53
 
54
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))
55
+ .where(association.target_where_clause)
56
+ .with(identity, "collect(#{association_name}) AS #{association_name}_collection", *with_associations_return_clause(previous_with_variables))
57
57
  end
58
58
  end
59
59
  end
@@ -75,6 +75,7 @@ module Neo4j
75
75
  def pluck(*args)
76
76
  transformable_attributes = (model ? model.attribute_names : []) + %w(uuid neo_id)
77
77
  arg_list = args.map do |arg|
78
+ arg = Neo4j::ActiveNode::Query::QueryProxy::Link.converted_key(model, arg)
78
79
  if transformable_attributes.include?(arg.to_s)
79
80
  {identity => arg}
80
81
  else
@@ -12,11 +12,7 @@ module Neo4j
12
12
  end
13
13
 
14
14
  def args(var, rel_var)
15
- if @arg.respond_to?(:call)
16
- @arg.call(var, rel_var)
17
- else
18
- [@arg] + @args
19
- end
15
+ @arg.respond_to?(:call) ? @arg.call(var, rel_var) : [@arg, @args].flatten
20
16
  end
21
17
 
22
18
  class << self
@@ -43,7 +39,7 @@ module Neo4j
43
39
  end
44
40
  result
45
41
  end
46
- alias_method :for_node_where_clause, :for_where_clause
42
+ alias for_node_where_clause for_where_clause
47
43
 
48
44
  def for_where_not_clause(*args)
49
45
  for_where_clause(*args).each do |link|
@@ -52,7 +48,7 @@ module Neo4j
52
48
  end
53
49
 
54
50
  def new_for_key_and_value(model, key, value)
55
- key = (key.to_sym == :id ? model.id_property_name : key)
51
+ key = converted_key(model, key)
56
52
 
57
53
  val = if !model
58
54
  value
@@ -88,8 +84,8 @@ module Neo4j
88
84
  [new(:order, ->(_, v) { arg.is_a?(String) ? arg : {v => arg} })]
89
85
  end
90
86
 
91
- def for_order_clause(arg, _)
92
- [new(:order, ->(v, _) { arg.is_a?(String) ? arg : {v => arg} })]
87
+ def for_order_clause(arg, model)
88
+ [new(:order, ->(v, _) { arg.is_a?(String) ? arg : {v => converted_keys(model, arg)} })]
93
89
  end
94
90
 
95
91
  def for_args(model, clause, args, association = nil)
@@ -110,6 +106,14 @@ module Neo4j
110
106
  default
111
107
  end
112
108
 
109
+ def converted_keys(model, arg)
110
+ arg.is_a?(Hash) ? Hash[arg.map { |key, value| [converted_key(model, key), value] }] : arg
111
+ end
112
+
113
+ def converted_key(model, key)
114
+ key.to_sym == :id ? model.id_property_name : key
115
+ end
116
+
113
117
  def converted_value(model, key, value)
114
118
  model.declared_properties.value_for_where(key, value)
115
119
  end
@@ -1,7 +1,9 @@
1
1
  module Neo4j
2
2
  module ActiveNode
3
3
  module Query
4
+ # rubocop:disable Metrics/ModuleLength
4
5
  module QueryProxyMethods
6
+ # rubocop:enable Metrics/ModuleLength
5
7
  FIRST = 'HEAD'
6
8
  LAST = 'LAST'
7
9
 
@@ -15,10 +17,6 @@ module Neo4j
15
17
  rels.first
16
18
  end
17
19
 
18
- def as(node_var)
19
- new_link(node_var)
20
- end
21
-
22
20
  # Give ability to call `#find` on associations to get a scoped find
23
21
  # Doesn't pass through via `method_missing` because Enumerable has a `#find` method
24
22
  def find(*args)
@@ -66,14 +64,14 @@ module Neo4j
66
64
  query_with_target(target) { |var| !self.exists?(nil, var) }
67
65
  end
68
66
 
69
- alias_method :blank?, :empty?
67
+ alias blank? empty?
70
68
 
71
69
  # @param [Neo4j::ActiveNode, Neo4j::Node, String] other An instance of a Neo4j.rb model, a Neo4j-core node, or a string uuid
72
70
  # @param [String, Symbol] target An identifier of a link in the Cypher chain
73
71
  # @return [Boolean]
74
72
  def include?(other, target = nil)
75
73
  query_with_target(target) do |var|
76
- where_filter = if other.respond_to?(:neo_id)
74
+ where_filter = if other.respond_to?(:neo_id) || association_id_key == :neo_id
77
75
  "ID(#{var}) = {other_node_id}"
78
76
  else
79
77
  "#{var}.#{association_id_key} = {other_node_id}"
@@ -129,7 +127,7 @@ module Neo4j
129
127
  def rels_to(node)
130
128
  self.match_to(node).pluck(rel_var)
131
129
  end
132
- alias_method :all_rels_to, :rels_to
130
+ alias all_rels_to rels_to
133
131
 
134
132
  # When called, this method returns a single node that satisfies the match specified in the params hash.
135
133
  # If no existing node is found to satisfy the match, one is created or associated as expected.
@@ -137,13 +135,21 @@ module Neo4j
137
135
  fail 'Method invalid when called on Class objects' unless source_object
138
136
  result = self.where(params).first
139
137
  return result unless result.nil?
140
- Neo4j::Transaction.run do
138
+ Neo4j::ActiveBase.run_transaction do
141
139
  node = model.find_or_create_by(params)
142
140
  self << node
143
141
  return node
144
142
  end
145
143
  end
146
144
 
145
+ def find_or_initialize_by(attributes, &block)
146
+ find_by(attributes) || initialize_by_current_chain_params(attributes, &block)
147
+ end
148
+
149
+ def first_or_initialize(attributes = {}, &block)
150
+ first || initialize_by_current_chain_params(attributes, &block)
151
+ end
152
+
147
153
  # A shortcut for attaching a new, optional match to the end of a QueryProxy chain.
148
154
  def optional(association, node_var = nil, rel_var = nil)
149
155
  self.send(association, node_var, rel_var, optional: true)
@@ -167,8 +173,70 @@ module Neo4j
167
173
  where("(#{where_clause})")
168
174
  end
169
175
 
176
+ # Matches all nodes having at least a relation
177
+ #
178
+ # @example Load all people having a friend
179
+ # Person.all.having_rel(:friends).to_a # => Returns a list of `Person`
180
+ #
181
+ # @example Load all people having a best friend
182
+ # Person.all.having_rel(:friends, best: true).to_a # => Returns a list of `Person`
183
+ #
184
+ # @return [QueryProxy] A new QueryProxy
185
+ def having_rel(association_name, rel_properties = {})
186
+ association = association_or_fail(association_name)
187
+ where("(#{identity})#{association.arrow_cypher(nil, rel_properties)}()")
188
+ end
189
+
190
+ # Matches all nodes not having a certain relation
191
+ #
192
+ # @example Load all people not having friends
193
+ # Person.all.not_having_rel(:friends).to_a # => Returns a list of `Person`
194
+ #
195
+ # @example Load all people not having best friends
196
+ # Person.all.not_having_rel(:friends, best: true).to_a # => Returns a list of `Person`
197
+ #
198
+ # @return [QueryProxy] A new QueryProxy
199
+ def not_having_rel(association_name, rel_properties = {})
200
+ association = association_or_fail(association_name)
201
+ where_not("(#{identity})#{association.arrow_cypher(nil, rel_properties)}()")
202
+ end
203
+
170
204
  private
171
205
 
206
+ def association_or_fail(association_name)
207
+ model.associations[association_name] || fail(ArgumentError, "No such association #{association_name}")
208
+ end
209
+
210
+ def find_inverse_association!(model, source, association)
211
+ model.associations.values.find do |reverse_association|
212
+ association.inverse_of?(reverse_association) ||
213
+ reverse_association.inverse_of?(association) ||
214
+ inverse_relation_of?(source, association, model, reverse_association)
215
+ end || fail("Could not find reverse association for #{@context}")
216
+ end
217
+
218
+ def inverse_relation_of?(source, source_association, target, target_association)
219
+ source_association.direction != target_association.direction &&
220
+ source == target_association.target_class &&
221
+ target == source_association.target_class &&
222
+ source_association.relationship_class_name == target_association.relationship_class_name
223
+ end
224
+
225
+ def initialize_by_current_chain_params(params = {})
226
+ result = new(where_clause_params.merge(params))
227
+
228
+ inverse_association = find_inverse_association!(model, source_object.class, association) if source_object
229
+ result.tap do |m|
230
+ yield(m) if block_given?
231
+ m.public_send(inverse_association.name) << source_object if inverse_association
232
+ end
233
+ end
234
+
235
+ def where_clause_params
236
+ query.clauses.select { |c| c.is_a?(Neo4j::Core::QueryClauses::WhereClause) && c.arg.is_a?(Hash) }
237
+ .map! { |e| e.arg[identity] }.compact.inject { |a, b| a.merge(b) } || {}
238
+ end
239
+
172
240
  def first_and_last(func, target)
173
241
  new_query, pluck_proc = if self.query.clause?(:order)
174
242
  [self.query.with(identity),