activegraph 11.5.0.beta.3 → 12.0.0.beta.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 +8 -0
- data/README.md +17 -17
- data/activegraph.gemspec +1 -1
- data/lib/active_graph/base.rb +4 -0
- data/lib/active_graph/core/element.rb +142 -0
- data/lib/active_graph/core/entity.rb +4 -0
- data/lib/active_graph/core/label.rb +5 -166
- data/lib/active_graph/core/node.rb +0 -4
- data/lib/active_graph/core/query_clauses.rb +4 -4
- data/lib/active_graph/core/schema.rb +23 -28
- data/lib/active_graph/core/type.rb +13 -0
- data/lib/active_graph/migrations/helpers/schema.rb +10 -13
- data/lib/active_graph/model_schema.rb +1 -1
- data/lib/active_graph/node/has_n.rb +1 -1
- data/lib/active_graph/node/labels.rb +15 -12
- data/lib/active_graph/node/persistence.rb +0 -11
- data/lib/active_graph/node/query/query_proxy/link.rb +15 -4
- data/lib/active_graph/node/query/query_proxy.rb +3 -3
- data/lib/active_graph/node/query/query_proxy_eager_loading.rb +1 -1
- data/lib/active_graph/node/query/query_proxy_enumerable.rb +4 -4
- data/lib/active_graph/node/query/query_proxy_methods.rb +14 -16
- data/lib/active_graph/node/query/query_proxy_methods_of_mass_updating.rb +1 -5
- data/lib/active_graph/node/query.rb +1 -1
- data/lib/active_graph/node/query_methods.rb +9 -12
- data/lib/active_graph/railtie.rb +11 -10
- data/lib/active_graph/relationship/initialize.rb +2 -2
- data/lib/active_graph/relationship/persistence.rb +3 -1
- data/lib/active_graph/relationship/property.rb +1 -5
- data/lib/active_graph/relationship/query.rb +14 -9
- data/lib/active_graph/relationship/related_node.rb +4 -8
- data/lib/active_graph/relationship/types.rb +8 -0
- data/lib/active_graph/relationship/wrapping.rb +1 -1
- data/lib/active_graph/relationship.rb +4 -1
- data/lib/active_graph/shared/identity.rb +6 -3
- data/lib/active_graph/shared/persistence.rb +12 -1
- data/lib/active_graph/shared/query_factory.rb +1 -1
- data/lib/active_graph/shared/type_converters.rb +3 -2
- data/lib/active_graph/version.rb +1 -1
- data/lib/active_graph.rb +0 -2
- data/lib/rails/generators/migration_helper.rb +0 -3
- metadata +22 -26
@@ -108,7 +108,7 @@ module ActiveGraph
|
|
108
108
|
|
109
109
|
# Deletes all nodes and connected relationships from Cypher.
|
110
110
|
def delete_all
|
111
|
-
neo4j_query("MATCH (n:`#{mapped_label_name}`)
|
111
|
+
neo4j_query("MATCH (n:`#{mapped_label_name}`) DETACH DELETE n")
|
112
112
|
end
|
113
113
|
|
114
114
|
# Returns each node to Ruby and calls `destroy`. Be careful, as this can be a very slow operation if you have many nodes. It will generate at least
|
@@ -127,11 +127,15 @@ module ActiveGraph
|
|
127
127
|
@mapped_label_name || label_for_model
|
128
128
|
end
|
129
129
|
|
130
|
+
alias mapped_element_name mapped_label_name
|
131
|
+
|
130
132
|
# @return [ActiveGraph::Label] the label for this class
|
131
133
|
def mapped_label
|
132
134
|
ActiveGraph::Core::Label.new(mapped_label_name)
|
133
135
|
end
|
134
136
|
|
137
|
+
alias mapped_element mapped_label
|
138
|
+
|
135
139
|
def base_class
|
136
140
|
unless self < ActiveGraph::Node
|
137
141
|
fail "#{name} doesn't belong in a hierarchy descending from Node"
|
@@ -160,6 +164,7 @@ module ActiveGraph
|
|
160
164
|
|
161
165
|
self.mapped_label_name = name
|
162
166
|
end
|
167
|
+
|
163
168
|
# rubocop:enable Naming/AccessorMethodName
|
164
169
|
|
165
170
|
private
|
@@ -184,20 +189,18 @@ module ActiveGraph
|
|
184
189
|
end
|
185
190
|
|
186
191
|
def label_for_model
|
187
|
-
|
192
|
+
name.nil? ? object_id.to_s.to_sym : decorated_label_name
|
188
193
|
end
|
189
194
|
|
190
195
|
def decorated_label_name
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
name.to_sym
|
196
|
+
case ActiveGraph::Config[:module_handling]
|
197
|
+
when :demodulize
|
198
|
+
name.demodulize
|
199
|
+
when Proc
|
200
|
+
ActiveGraph::Config[:module_handling].call name
|
201
|
+
else
|
202
|
+
name
|
203
|
+
end.to_sym
|
201
204
|
end
|
202
205
|
end
|
203
206
|
end
|
@@ -70,17 +70,6 @@ module ActiveGraph::Node
|
|
70
70
|
neo4j_query(query, {props: node_props}, wrap: false).to_a[0][:n]
|
71
71
|
end
|
72
72
|
|
73
|
-
# As the name suggests, this inserts the primary key (id property) into the properties hash.
|
74
|
-
# The method called here, `default_property_values`, is a holdover from an earlier version of the gem. It does NOT
|
75
|
-
# contain the default values of properties, it contains the Default Property, which we now refer to as the ID Property.
|
76
|
-
# It will be deprecated and renamed in a coming refactor.
|
77
|
-
# @param [Hash] converted_props A hash of properties post-typeconversion, ready for insertion into the DB.
|
78
|
-
def inject_primary_key!(converted_props)
|
79
|
-
self.class.default_property_values(self).tap do |destination_props|
|
80
|
-
destination_props.merge!(converted_props) if converted_props.is_a?(Hash)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
73
|
# @return [Array] Labels to be set on the node during a create event
|
85
74
|
def labels_for_create
|
86
75
|
self.class.mapped_label_names
|
@@ -147,8 +147,8 @@ module ActiveGraph
|
|
147
147
|
|
148
148
|
val = if !model
|
149
149
|
value
|
150
|
-
elsif key == model.id_property_name
|
151
|
-
value
|
150
|
+
elsif key == model.id_property_name
|
151
|
+
try_id(value)
|
152
152
|
else
|
153
153
|
converted_value(model, key, value)
|
154
154
|
end
|
@@ -156,13 +156,24 @@ module ActiveGraph
|
|
156
156
|
new(:where, ->(v, _) { {v => {key => val}} })
|
157
157
|
end
|
158
158
|
|
159
|
+
private def try_id(value)
|
160
|
+
case value
|
161
|
+
when Shared::Identity
|
162
|
+
value.id
|
163
|
+
when Enumerable
|
164
|
+
value.map(&method(:try_id))
|
165
|
+
else
|
166
|
+
value
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
159
170
|
def for_association(name, value, n_string, model)
|
160
171
|
neo_id = value.try(:neo_id) || value
|
161
|
-
fail ArgumentError, "Invalid value for '#{name}' condition"
|
172
|
+
fail ArgumentError, "Invalid value for '#{name}' condition" unless neo_id.is_a?(String)
|
162
173
|
|
163
174
|
[
|
164
175
|
new(:match, ->(v, _) { "(#{v})#{model.associations[name].arrow_cypher}(#{n_string})" }),
|
165
|
-
new(:where, ->(_, _) { {"
|
176
|
+
new(:where, ->(_, _) { {"elementId(#{n_string})" => neo_id} })
|
166
177
|
]
|
167
178
|
end
|
168
179
|
|
@@ -223,7 +223,7 @@ module ActiveGraph
|
|
223
223
|
|
224
224
|
ActiveGraph::Base.transaction do
|
225
225
|
other_nodes.each do |other_node|
|
226
|
-
if other_node.
|
226
|
+
if other_node.element_id
|
227
227
|
other_node.try(:delete_reverse_has_one_core_rel, association)
|
228
228
|
else
|
229
229
|
other_node.save
|
@@ -238,7 +238,7 @@ module ActiveGraph
|
|
238
238
|
|
239
239
|
def _nodeify!(*args)
|
240
240
|
other_nodes = [args].flatten!.map! do |arg|
|
241
|
-
|
241
|
+
arg.is_a?(String) ? @model.find_by(id: arg) : arg
|
242
242
|
end.compact
|
243
243
|
|
244
244
|
if @model && other_nodes.any? { |other_node| !other_node.class.mapped_label_names.include?(@model.mapped_label_name) }
|
@@ -351,7 +351,7 @@ module ActiveGraph
|
|
351
351
|
fail 'Crazy error' if !(start_object || @query_proxy)
|
352
352
|
|
353
353
|
if start_object
|
354
|
-
:"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id}"
|
354
|
+
:"#{start_object.class.name.gsub('::', '_').downcase}#{start_object.neo_id&.gsub(/[:\-]/, '_')}"
|
355
355
|
else
|
356
356
|
@query_proxy.node_var || :"node#{_chain_level}"
|
357
357
|
end
|
@@ -62,7 +62,7 @@ module ActiveGraph
|
|
62
62
|
if rel.is_a?(ActiveGraph::Relationship)
|
63
63
|
rel.instance_variable_set(direction == :in ? '@from_node' : '@to_node', node)
|
64
64
|
end
|
65
|
-
@_cache[direction == :out ? rel.
|
65
|
+
@_cache[direction == :out ? rel.start_node_element_id : rel.end_node_element_id]
|
66
66
|
.association_proxy(element.name).add_to_cache(node, rel)
|
67
67
|
end
|
68
68
|
|
@@ -62,22 +62,22 @@ module ActiveGraph
|
|
62
62
|
# Does exactly what you would hope. Without it, comparing `bobby.lessons == sandy.lessons` would evaluate to false because it
|
63
63
|
# would be comparing the QueryProxy objects, not the lessons themselves.
|
64
64
|
def ==(other)
|
65
|
-
|
65
|
+
to_a == other
|
66
66
|
end
|
67
67
|
|
68
68
|
# For getting variables which have been defined as part of the association chain
|
69
69
|
def pluck(*args)
|
70
|
-
transformable_attributes = (model ? model.attribute_names : []) + %w(uuid neo_id)
|
70
|
+
transformable_attributes = (model ? model.attribute_names + [model.id_property_name.to_s] : []) + %w(uuid neo_id)
|
71
71
|
arg_list = args.map do |arg|
|
72
72
|
arg = ActiveGraph::Node::Query::QueryProxy::Link.converted_key(model, arg)
|
73
73
|
if transformable_attributes.include?(arg.to_s)
|
74
|
-
{identity => arg}
|
74
|
+
{ identity => arg }
|
75
75
|
else
|
76
76
|
arg
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
|
80
|
+
query.pluck(*arg_list)
|
81
81
|
end
|
82
82
|
|
83
83
|
protected
|
@@ -88,7 +88,7 @@ module ActiveGraph
|
|
88
88
|
def include?(other, target = nil)
|
89
89
|
query_with_target(target) do |var|
|
90
90
|
where_filter = if other.respond_to?(:neo_id) || association_id_key == :neo_id
|
91
|
-
"
|
91
|
+
"elementId(#{var}) = $other_node_id"
|
92
92
|
else
|
93
93
|
"#{var}.#{association_id_key} = $other_node_id"
|
94
94
|
end
|
@@ -99,12 +99,12 @@ module ActiveGraph
|
|
99
99
|
end
|
100
100
|
|
101
101
|
def exists?(node_condition = nil, target = nil)
|
102
|
-
unless [
|
102
|
+
unless [String, Hash, NilClass].any? { |c| node_condition.is_a?(c) }
|
103
103
|
fail(ActiveGraph::InvalidParameterError, ':exists? only accepts ids or conditions')
|
104
104
|
end
|
105
105
|
query_with_target(target) do |var|
|
106
106
|
start_q = exists_query_start(node_condition, var)
|
107
|
-
result = start_q.query.reorder.return("
|
107
|
+
result = start_q.query.reorder.return("elementId(#{var}) AS proof_of_life LIMIT 1").first
|
108
108
|
!!result
|
109
109
|
end
|
110
110
|
end
|
@@ -119,9 +119,9 @@ module ActiveGraph
|
|
119
119
|
def match_to(node)
|
120
120
|
first_node = node.is_a?(Array) ? node.first : node
|
121
121
|
where_arg = if first_node.respond_to?(:neo_id)
|
122
|
-
{neo_id: node.is_a?(Array) ? node.map(&:neo_id) : node}
|
122
|
+
{ neo_id: node.is_a?(Array) ? node.map(&:neo_id) : node }
|
123
123
|
elsif !node.nil?
|
124
|
-
{association_id_key => node.is_a?(Array) ? ids_array(node) : node}
|
124
|
+
{ association_id_key => node.is_a?(Array) ? ids_array(node) : node }
|
125
125
|
else
|
126
126
|
# support for null object pattern
|
127
127
|
'1 = 2'
|
@@ -130,7 +130,6 @@ module ActiveGraph
|
|
130
130
|
self.where(where_arg)
|
131
131
|
end
|
132
132
|
|
133
|
-
|
134
133
|
# Gives you the first relationship between the last link of a QueryProxy chain and a given node
|
135
134
|
# Shorthand for `MATCH (start)-[r]-(other_node) WHERE ID(other_node) = #{other_node.neo_id} RETURN r`
|
136
135
|
# @param [#neo_id, String, Enumerable] node An object to be sent to `match_to`. See params for that method.
|
@@ -145,6 +144,7 @@ module ActiveGraph
|
|
145
144
|
def rels_to(node)
|
146
145
|
self.match_to(node).pluck(rel_var)
|
147
146
|
end
|
147
|
+
|
148
148
|
alias all_rels_to rels_to
|
149
149
|
|
150
150
|
# When called, this method returns a single node that satisfies the match specified in the params hash.
|
@@ -260,7 +260,7 @@ module ActiveGraph
|
|
260
260
|
[self.query.with(identity),
|
261
261
|
proc { |var| "#{func}(COLLECT(#{var})) as #{var}" }]
|
262
262
|
else
|
263
|
-
ord_prop = (func == LAST ? {order_property => :DESC} : order_property)
|
263
|
+
ord_prop = (func == LAST ? { order_property => :DESC } : order_property)
|
264
264
|
[self.order(ord_prop).limit(1),
|
265
265
|
proc { |var| var }]
|
266
266
|
end
|
@@ -285,16 +285,14 @@ module ActiveGraph
|
|
285
285
|
yield(target || identity)
|
286
286
|
end
|
287
287
|
|
288
|
-
def exists_query_start(condition, target)
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
when String
|
295
|
-
self.where(model.primary_key => condition)
|
288
|
+
def exists_query_start(condition, target = nil)
|
289
|
+
return self unless condition
|
290
|
+
return exists_query_start(model.primary_key => condition) if condition.is_a?(String)
|
291
|
+
|
292
|
+
if condition.key?(:neo_id)
|
293
|
+
where("elementId(#{target}) = $neo_id").params(**condition.slice(:neo_id))
|
296
294
|
else
|
297
|
-
|
295
|
+
where(**condition)
|
298
296
|
end
|
299
297
|
end
|
300
298
|
end
|
@@ -25,11 +25,7 @@ module ActiveGraph
|
|
25
25
|
# @param identifier [String,Symbol] the optional identifier of the link in the chain to delete.
|
26
26
|
def delete_all(identifier = nil)
|
27
27
|
query_with_target(identifier) do |target|
|
28
|
-
|
29
|
-
self.query.with(target).optional_match("(#{target})-[#{target}_rel]-()").delete("#{target}, #{target}_rel").exec
|
30
|
-
rescue Neo4j::Driver::Exceptions::ClientException # <=- Seems hacky
|
31
|
-
self.query.delete(target).exec
|
32
|
-
end
|
28
|
+
query.detach_delete(target).exec
|
33
29
|
clear_source_object_cache
|
34
30
|
end
|
35
31
|
end
|
@@ -16,7 +16,7 @@ module ActiveGraph
|
|
16
16
|
# @param node_var [Symbol, String] The variable name to specify in the query
|
17
17
|
# @return [ActiveGraph::Core::Query]
|
18
18
|
def query_as(node_var)
|
19
|
-
self.class.query_as(node_var, false).where("
|
19
|
+
self.class.query_as(node_var, false).where("elementId(#{node_var})" => neo_id)
|
20
20
|
end
|
21
21
|
|
22
22
|
# Starts a new QueryProxy with the starting identifier set to the given argument and QueryProxy source_object set to the node instance.
|
@@ -2,12 +2,12 @@ module ActiveGraph
|
|
2
2
|
module Node
|
3
3
|
module QueryMethods
|
4
4
|
def exists?(node_condition = nil)
|
5
|
-
unless [
|
5
|
+
unless [String, Hash, NilClass].any? { |c| node_condition.is_a?(c) }
|
6
6
|
fail(ActiveGraph::InvalidParameterError, ':exists? only accepts ids or conditions')
|
7
7
|
end
|
8
8
|
query_start = exists_query_start(node_condition)
|
9
9
|
start_q = query_start.respond_to?(:query_as) ? query_start.query_as(:n) : query_start
|
10
|
-
result = start_q.return('
|
10
|
+
result = start_q.return('elementId(n) AS proof_of_life LIMIT 1').first
|
11
11
|
!!result
|
12
12
|
end
|
13
13
|
|
@@ -18,7 +18,7 @@ module ActiveGraph
|
|
18
18
|
|
19
19
|
# Returns the last node of this class, sorted by ID. Note that this may not be the first node created since Neo4j recycles IDs.
|
20
20
|
def last
|
21
|
-
self.query_as(:n).limit(1).order(n: {primary_key => :desc}).pluck(:n).first
|
21
|
+
self.query_as(:n).limit(1).order(n: { primary_key => :desc }).pluck(:n).first
|
22
22
|
end
|
23
23
|
|
24
24
|
# @return [Integer] number of nodes of this class
|
@@ -51,16 +51,13 @@ module ActiveGraph
|
|
51
51
|
|
52
52
|
private
|
53
53
|
|
54
|
-
def exists_query_start(
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
self.query_as(:n).where(n: {primary_key => node_condition})
|
60
|
-
when Hash
|
61
|
-
self.where(node_condition.keys.first => node_condition.values.first)
|
54
|
+
def exists_query_start(condition)
|
55
|
+
return exists_query_start(primary_key => condition) if condition&.is_a?(String)
|
56
|
+
|
57
|
+
if condition&.key?(:neo_id)
|
58
|
+
query_as(:n).where('elementId(n)' => condition[:neo_id])
|
62
59
|
else
|
63
|
-
|
60
|
+
where(**condition)
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
data/lib/active_graph/railtie.rb
CHANGED
@@ -1,8 +1,16 @@
|
|
1
|
-
# Need the action_dispatch railtie to have action_dispatch.rescue_responses initialized correctly
|
2
|
-
require 'action_dispatch/railtie'
|
3
|
-
require 'rails/railtie'
|
4
1
|
require 'active_graph'
|
5
2
|
|
3
|
+
if defined?(Rails)
|
4
|
+
# Need the action_dispatch railtie to have action_dispatch.rescue_responses initialized correctly
|
5
|
+
require 'action_dispatch/railtie'
|
6
|
+
require 'rails/generators'
|
7
|
+
require 'rails/generators/active_model'
|
8
|
+
require 'rails/generators/named_base'
|
9
|
+
require 'rails/railtie'
|
10
|
+
require File.expand_path('../rails/generators/migration_helper.rb', __dir__)
|
11
|
+
Rails::Generators::GeneratedAttribute.include ActiveGraph::Generators::GeneratedAttribute
|
12
|
+
end
|
13
|
+
|
6
14
|
module ActiveGraph
|
7
15
|
class Railtie < ::Rails::Railtie
|
8
16
|
def empty_config
|
@@ -39,13 +47,6 @@ module ActiveGraph
|
|
39
47
|
ActiveGraph::Config[:verbose_query_logs] = false
|
40
48
|
end
|
41
49
|
|
42
|
-
# By default, Rails loads generators from load path.
|
43
|
-
# However, if we want to place generators at a different location we have to use "generators" hook
|
44
|
-
# https://api.rubyonrails.org/classes/Rails/Railtie.html
|
45
|
-
generators do
|
46
|
-
require File.expand_path('../rails/generators/migration_helper.rb', __dir__)
|
47
|
-
end
|
48
|
-
|
49
50
|
# Starting Neo after :load_config_initializers allows apps to
|
50
51
|
# register migrations in config/initializers
|
51
52
|
initializer 'neo4j.start', after: :load_config_initializers do |app|
|
@@ -19,8 +19,8 @@ module ActiveGraph::Relationship
|
|
19
19
|
def init_on_reload(unwrapped_reloaded)
|
20
20
|
@attributes = nil
|
21
21
|
init_on_load(unwrapped_reloaded,
|
22
|
-
unwrapped_reloaded.
|
23
|
-
unwrapped_reloaded.
|
22
|
+
unwrapped_reloaded.start_node_element_id,
|
23
|
+
unwrapped_reloaded.end_node_element_id,
|
24
24
|
unwrapped_reloaded.type)
|
25
25
|
self
|
26
26
|
end
|
@@ -5,7 +5,9 @@ module ActiveGraph::Relationship
|
|
5
5
|
include ActiveGraph::Shared::Persistence
|
6
6
|
|
7
7
|
class RelInvalidError < RuntimeError; end
|
8
|
+
|
8
9
|
class ModelClassInvalidError < RuntimeError; end
|
10
|
+
|
9
11
|
class RelCreateFailedError < RuntimeError; end
|
10
12
|
|
11
13
|
def from_node_identifier
|
@@ -84,7 +86,7 @@ module ActiveGraph::Relationship
|
|
84
86
|
end
|
85
87
|
|
86
88
|
def query_as(neo_id, var = :r)
|
87
|
-
ActiveGraph::Base.new_query.match("()-[#{var}]->()").where(var => {neo_id:
|
89
|
+
ActiveGraph::Base.new_query.match("()-[#{var}]->()").where(var => { neo_id: })
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
@@ -15,7 +15,7 @@ module ActiveGraph::Relationship
|
|
15
15
|
alias end_node to_node
|
16
16
|
|
17
17
|
%w(start_node end_node).each do |direction|
|
18
|
-
define_method("#{direction}
|
18
|
+
define_method("#{direction}_element_id") { send(direction).neo_id if direction }
|
19
19
|
end
|
20
20
|
|
21
21
|
# @return [String] a string representing the relationship type that will be created
|
@@ -45,10 +45,6 @@ module ActiveGraph::Relationship
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
def id_property_name
|
49
|
-
false
|
50
|
-
end
|
51
|
-
|
52
48
|
%w(to_class from_class).each do |direction|
|
53
49
|
define_method(direction.to_s) do |argument = nil|
|
54
50
|
if !argument.nil?
|
@@ -6,18 +6,17 @@ module ActiveGraph::Relationship
|
|
6
6
|
|
7
7
|
module ClassMethods
|
8
8
|
# Returns the object with the specified neo4j id.
|
9
|
-
# @param [String
|
9
|
+
# @param [String] id of node to find
|
10
10
|
def find(id)
|
11
|
-
fail "Unknown argument #{id.class} in find method (expected String
|
12
|
-
find_by_id(id)
|
11
|
+
fail "Unknown argument #{id.class} in find method (expected String)" unless [Integer, String].any?(&id.method(:is_a?))
|
12
|
+
find_by_id(id) || fail(RecordNotFound.new("Couldn't find #{name} with 'id'=#{id.inspect}", name, id))
|
13
13
|
end
|
14
14
|
|
15
15
|
# Loads the relationship using its neo_id.
|
16
16
|
def find_by_id(key)
|
17
17
|
query = ActiveGraph::Base.new_query
|
18
|
-
result = query.match('()-[r]-()').where(
|
19
|
-
|
20
|
-
result[:r]
|
18
|
+
result = query.match('()-[r]-()').where("r.#{id_property_name}" => key).limit(1).return(:r).first
|
19
|
+
result&.send(:[], :r)
|
21
20
|
end
|
22
21
|
|
23
22
|
# Performs a very basic match on the relationship.
|
@@ -29,6 +28,10 @@ module ActiveGraph::Relationship
|
|
29
28
|
where_query.where(where_string(args)).pluck(:r1)
|
30
29
|
end
|
31
30
|
|
31
|
+
def find_by(args)
|
32
|
+
where(args).first
|
33
|
+
end
|
34
|
+
|
32
35
|
# Performs a basic match on the relationship, returning all results.
|
33
36
|
# This is not executed lazily, it will immediately return matching objects.
|
34
37
|
def all
|
@@ -36,11 +39,11 @@ module ActiveGraph::Relationship
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def first
|
39
|
-
all_query.limit(1).order('
|
42
|
+
all_query.limit(1).order('r1.created_at').pluck(:r1).first
|
40
43
|
end
|
41
44
|
|
42
45
|
def last
|
43
|
-
all_query.limit(1).order('
|
46
|
+
all_query.limit(1).order('r1.created_at DESC').pluck(:r1).first
|
44
47
|
end
|
45
48
|
|
46
49
|
private
|
@@ -89,7 +92,9 @@ module ActiveGraph::Relationship
|
|
89
92
|
def where_string(args)
|
90
93
|
case args
|
91
94
|
when Hash
|
92
|
-
args.
|
95
|
+
args.transform_keys { |key| key == :neo_id ? 'elementId(r1)' : "r1.#{key}" }
|
96
|
+
.transform_values { |v| v.is_a?(Integer) ? v : "'#{v}'" }
|
97
|
+
.map { |k, v| "#{k} = #{v}" }.join(', ')
|
93
98
|
else
|
94
99
|
args
|
95
100
|
end
|
@@ -17,7 +17,7 @@ module ActiveGraph::Relationship
|
|
17
17
|
|
18
18
|
# Loads the node if needed, then conducts comparison.
|
19
19
|
def ==(other)
|
20
|
-
loaded if @node.is_a?(
|
20
|
+
loaded if @node.is_a?(String)
|
21
21
|
@node == other
|
22
22
|
end
|
23
23
|
|
@@ -62,15 +62,11 @@ module ActiveGraph::Relationship
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def method_missing(*args, **kwargs, &block)
|
65
|
-
|
66
|
-
loaded.send(*args, &block)
|
67
|
-
else
|
68
|
-
loaded.send(*args, **kwargs, &block)
|
69
|
-
end
|
65
|
+
loaded.send(*args, **kwargs, &block)
|
70
66
|
end
|
71
67
|
|
72
68
|
def respond_to_missing?(method_name, include_private = false)
|
73
|
-
loaded if @node.is_a?(
|
69
|
+
loaded if @node.is_a?(String)
|
74
70
|
@node.respond_to?(method_name) ? true : super
|
75
71
|
end
|
76
72
|
|
@@ -85,7 +81,7 @@ module ActiveGraph::Relationship
|
|
85
81
|
end
|
86
82
|
|
87
83
|
def valid_node_param?(node)
|
88
|
-
node.nil? || node.is_a?(
|
84
|
+
node.nil? || node.is_a?(String) || node.respond_to?(:neo_id)
|
89
85
|
end
|
90
86
|
end
|
91
87
|
end
|
@@ -10,6 +10,7 @@ module ActiveGraph
|
|
10
10
|
include ActiveGraph::Relationship::Initialize
|
11
11
|
include ActiveGraph::Shared::Identity
|
12
12
|
include ActiveGraph::Shared::Marshal
|
13
|
+
include ActiveGraph::Node::IdProperty
|
13
14
|
include ActiveGraph::Shared::SerializedProperties
|
14
15
|
include ActiveGraph::Relationship::Property
|
15
16
|
include ActiveGraph::Relationship::Persistence
|
@@ -24,6 +25,8 @@ module ActiveGraph
|
|
24
25
|
class FrozenRelError < ActiveGraph::Error; end
|
25
26
|
|
26
27
|
def initialize(from_node = nil, to_node = nil, args = nil)
|
28
|
+
self.class.ensure_id_property_info! # So that we make sure all objects have an id_property
|
29
|
+
|
27
30
|
load_nodes(node_or_nil(from_node), node_or_nil(to_node))
|
28
31
|
resolved_args = hash_or_nil(from_node, args)
|
29
32
|
symbol_args = sanitize_input_parameters(resolved_args)
|
@@ -58,7 +61,7 @@ module ActiveGraph
|
|
58
61
|
private
|
59
62
|
|
60
63
|
def node_or_nil(node)
|
61
|
-
node.is_a?(ActiveGraph::Node) || node.is_a?(
|
64
|
+
node.is_a?(ActiveGraph::Node) || node.is_a?(String) ? node : nil
|
62
65
|
end
|
63
66
|
|
64
67
|
def hash_or_nil(node_or_hash, hash_or_nil)
|
@@ -4,6 +4,7 @@ module ActiveGraph
|
|
4
4
|
def ==(other)
|
5
5
|
other.class == self.class && other.id == id
|
6
6
|
end
|
7
|
+
|
7
8
|
alias eql? ==
|
8
9
|
|
9
10
|
# Returns an Enumerable of all (primary) key attributes
|
@@ -12,11 +13,13 @@ module ActiveGraph
|
|
12
13
|
_persisted_obj ? [id] : nil
|
13
14
|
end
|
14
15
|
|
15
|
-
# @return [
|
16
|
-
def
|
17
|
-
_persisted_obj
|
16
|
+
# @return [String, nil] the neo4j id of the node if persisted or nil
|
17
|
+
def element_id
|
18
|
+
_persisted_obj&.element_id
|
18
19
|
end
|
19
20
|
|
21
|
+
alias neo_id element_id
|
22
|
+
|
20
23
|
def id
|
21
24
|
if self.class.id_property_name
|
22
25
|
send(self.class.id_property_name)
|
@@ -134,7 +134,7 @@ module ActiveGraph::Shared
|
|
134
134
|
def exist?
|
135
135
|
return if !_persisted_obj
|
136
136
|
|
137
|
-
neo4j_query(query_as(:n).return('
|
137
|
+
neo4j_query(query_as(:n).return('elementId(n)')).any?
|
138
138
|
end
|
139
139
|
|
140
140
|
# Returns +true+ if the object was destroyed.
|
@@ -223,6 +223,17 @@ module ActiveGraph::Shared
|
|
223
223
|
end
|
224
224
|
end
|
225
225
|
|
226
|
+
# As the name suggests, this inserts the primary key (id property) into the properties hash.
|
227
|
+
# The method called here, `default_property_values`, is a holdover from an earlier version of the gem. It does NOT
|
228
|
+
# contain the default values of properties, it contains the Default Property, which we now refer to as the ID Property.
|
229
|
+
# It will be deprecated and renamed in a coming refactor.
|
230
|
+
# @param [Hash] converted_props A hash of properties post-typeconversion, ready for insertion into the DB.
|
231
|
+
def inject_primary_key!(converted_props)
|
232
|
+
self.class.default_property_values(self).tap do |destination_props|
|
233
|
+
destination_props.merge!(converted_props) if converted_props.is_a?(Hash)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
226
237
|
protected
|
227
238
|
|
228
239
|
def increment_by_query!(match_query, attribute, by, element_name = :n)
|
@@ -47,7 +47,7 @@ module ActiveGraph::Shared
|
|
47
47
|
|
48
48
|
def match_query
|
49
49
|
base_query
|
50
|
-
.match(match_string).where("
|
50
|
+
.match(match_string).where("elementId(#{identifier}) = $#{identifier_id}")
|
51
51
|
.params(identifier_id.to_sym => graph_object.neo_id)
|
52
52
|
end
|
53
53
|
|
@@ -389,7 +389,7 @@ module ActiveGraph::Shared
|
|
389
389
|
def included(_)
|
390
390
|
ActiveGraph::Shared::TypeConverters.constants.each do |constant_name|
|
391
391
|
constant = ActiveGraph::Shared::TypeConverters.const_get(constant_name)
|
392
|
-
register_converter(constant) if constant.respond_to?(:convert_type)
|
392
|
+
register_converter(constant, force: false) if constant.respond_to?(:convert_type)
|
393
393
|
end
|
394
394
|
end
|
395
395
|
|
@@ -425,7 +425,8 @@ module ActiveGraph::Shared
|
|
425
425
|
found_converter.respond_to?(:converted?) ? found_converter.converted?(value) : value.is_a?(found_converter.db_type)
|
426
426
|
end
|
427
427
|
|
428
|
-
def register_converter(converter)
|
428
|
+
def register_converter(converter, force: true)
|
429
|
+
return if CONVERTERS.key?(converter.convert_type) && !force
|
429
430
|
CONVERTERS[converter.convert_type] = converter
|
430
431
|
end
|
431
432
|
end
|