neo4j 7.2.3 → 8.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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),