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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -46
- data/Gemfile +15 -14
- data/README.md +21 -14
- data/bin/neo4j-jars +1 -1
- data/lib/neo4j.rb +12 -1
- data/lib/neo4j/active_base.rb +68 -0
- data/lib/neo4j/active_base/session_registry.rb +12 -0
- data/lib/neo4j/active_node.rb +13 -21
- data/lib/neo4j/active_node/dependent/query_proxy_methods.rb +6 -6
- data/lib/neo4j/active_node/enum.rb +3 -6
- data/lib/neo4j/active_node/has_n.rb +24 -19
- data/lib/neo4j/active_node/has_n/association.rb +6 -2
- data/lib/neo4j/active_node/has_n/association/rel_factory.rb +1 -1
- data/lib/neo4j/active_node/has_n/association/rel_wrapper.rb +1 -1
- data/lib/neo4j/active_node/has_n/association_cypher_methods.rb +1 -1
- data/lib/neo4j/active_node/id_property.rb +52 -15
- data/lib/neo4j/active_node/labels.rb +32 -10
- data/lib/neo4j/active_node/labels/index.rb +5 -55
- data/lib/neo4j/active_node/node_list_formatter.rb +13 -0
- data/lib/neo4j/active_node/node_wrapper.rb +39 -37
- data/lib/neo4j/active_node/persistence.rb +27 -13
- data/lib/neo4j/active_node/query/query_proxy.rb +11 -9
- data/lib/neo4j/active_node/query/query_proxy_eager_loading.rb +4 -4
- data/lib/neo4j/active_node/query/query_proxy_enumerable.rb +1 -0
- data/lib/neo4j/active_node/query/query_proxy_link.rb +13 -9
- data/lib/neo4j/active_node/query/query_proxy_methods.rb +76 -8
- data/lib/neo4j/active_node/query/query_proxy_methods_of_mass_updating.rb +1 -1
- data/lib/neo4j/active_node/query_methods.rb +3 -3
- data/lib/neo4j/active_node/scope.rb +24 -7
- data/lib/neo4j/active_rel.rb +21 -3
- data/lib/neo4j/active_rel/initialize.rb +2 -2
- data/lib/neo4j/active_rel/persistence.rb +32 -6
- data/lib/neo4j/active_rel/persistence/query_factory.rb +3 -3
- data/lib/neo4j/active_rel/property.rb +9 -9
- data/lib/neo4j/active_rel/query.rb +6 -4
- data/lib/neo4j/active_rel/rel_wrapper.rb +24 -16
- data/lib/neo4j/active_rel/related_node.rb +5 -1
- data/lib/neo4j/active_rel/types.rb +2 -2
- data/lib/neo4j/config.rb +0 -1
- data/lib/neo4j/errors.rb +3 -0
- data/lib/neo4j/migration.rb +90 -71
- data/lib/neo4j/migrations.rb +10 -0
- data/lib/neo4j/migrations/base.rb +44 -0
- data/lib/neo4j/migrations/helpers.rb +101 -0
- data/lib/neo4j/migrations/helpers/id_property.rb +75 -0
- data/lib/neo4j/migrations/helpers/relationships.rb +66 -0
- data/lib/neo4j/migrations/helpers/schema.rb +53 -0
- data/lib/neo4j/migrations/migration_file.rb +24 -0
- data/lib/neo4j/migrations/runner.rb +110 -0
- data/lib/neo4j/migrations/schema_migration.rb +9 -0
- data/lib/neo4j/model_schema.rb +100 -0
- data/lib/neo4j/railtie.rb +29 -110
- data/lib/neo4j/schema/operation.rb +24 -13
- data/lib/neo4j/session_manager.rb +137 -0
- data/lib/neo4j/shared.rb +20 -11
- data/lib/neo4j/shared/attributes.rb +10 -16
- data/lib/neo4j/shared/callbacks.rb +3 -3
- data/lib/neo4j/shared/cypher.rb +1 -1
- data/lib/neo4j/shared/declared_properties.rb +1 -1
- data/lib/neo4j/shared/declared_property.rb +1 -1
- data/lib/neo4j/shared/enum.rb +6 -18
- data/lib/neo4j/shared/identity.rb +27 -21
- data/lib/neo4j/shared/persistence.rb +26 -17
- data/lib/neo4j/shared/property.rb +5 -2
- data/lib/neo4j/shared/query_factory.rb +4 -5
- data/lib/neo4j/shared/type_converters.rb +8 -9
- data/lib/neo4j/shared/validations.rb +1 -5
- data/lib/neo4j/tasks/migration.rake +83 -2
- data/lib/neo4j/version.rb +1 -1
- data/lib/rails/generators/neo4j/migration/migration_generator.rb +14 -0
- data/lib/rails/generators/neo4j/migration/templates/migration.erb +9 -0
- data/lib/rails/generators/neo4j/model/model_generator.rb +1 -3
- data/lib/rails/generators/neo4j_generator.rb +1 -0
- data/neo4j.gemspec +3 -3
- metadata +58 -65
- data/bin/rake +0 -17
- 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
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
26
|
-
labels.each { |label| Neo4j::Node::Wrapper.constant_for_label(label) }
|
27
|
-
end
|
27
|
+
private
|
28
28
|
|
29
|
-
|
30
|
-
|
29
|
+
def load_classes_from_labels(labels)
|
30
|
+
labels.each { |label| constant_for_label(label) }
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
def constantized_label(label)
|
38
|
+
"#{association_model_namespace}::#{label}".constantize
|
39
|
+
rescue NameError
|
40
|
+
nil
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
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::
|
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
|
-
|
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
|
-
|
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.
|
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
|
-
|
56
|
-
|
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
|
-
|
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
|
-
|
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 = (
|
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
|
-
|
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
|
-
|
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::
|
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),
|