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.
- 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),
|