activecypher 0.14.1 → 0.15.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/lib/active_cypher/associations/collection_proxy.rb +4 -1
- data/lib/active_cypher/associations.rb +15 -13
- data/lib/active_cypher/base.rb +14 -2
- data/lib/active_cypher/connection_adapters/memgraph_adapter.rb +4 -0
- data/lib/active_cypher/model/connection_owner.rb +1 -2
- data/lib/active_cypher/model/core.rb +10 -0
- data/lib/active_cypher/railtie.rb +2 -2
- data/lib/active_cypher/relation.rb +3 -2
- data/lib/active_cypher/relationship.rb +19 -15
- data/lib/active_cypher/version.rb +1 -1
- data/lib/tasks/graphdb_migrate.rake +7 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cb2fe676154a99ad8fc54cfe88731a1c7ce4759235e5103e0d87662c4cd450c2
|
|
4
|
+
data.tar.gz: 8982b47d70418c6fcc2ec6e4e8a4bd9513df9ac94ab9c8d14b19f0b9af699c22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1d227c0f135e4203eee987c763b3d6bb2e61e4b24fbc3a5737166bbe1288d042d42d5cc2c83172f2df6d68f42480f76e8260a4571eee533cb53ce68c93f73228
|
|
7
|
+
data.tar.gz: 2d47c7787d443326b89d8f734107f4492a2cc34f7930ae0c46fbd9e7fda3d1249ea0840376d3653218872ece6434151a557db7b430167a690824981abe54f523
|
|
@@ -114,14 +114,20 @@ module ActiveCypher
|
|
|
114
114
|
|
|
115
115
|
# Resolve the target node class
|
|
116
116
|
target_class = target_class_name.constantize
|
|
117
|
-
a_alias = :start
|
|
118
|
-
b_alias = :target
|
|
119
117
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
owner_alias = :start
|
|
119
|
+
related_alias = :target
|
|
120
|
+
|
|
121
|
+
# owner = the node we're querying from (self)
|
|
122
|
+
# related = the node we want to fetch
|
|
123
|
+
owner_node = Cyrel::Pattern::Node.new(owner_alias, labels: self.class.label_name)
|
|
124
|
+
related_node = Cyrel::Pattern::Node.new(related_alias, labels: target_class.label_name)
|
|
123
125
|
|
|
124
|
-
#
|
|
126
|
+
# The relationship token renders the arrow direction itself:
|
|
127
|
+
# :out → -[:TYPE]-> e.g. (start:Person)-[:ENJOYS]->(target:Activity)
|
|
128
|
+
# :in → <-[:TYPE]- e.g. (start:Activity)<-[:ENJOYS]-(target:Person)
|
|
129
|
+
# :both → -[:TYPE]- e.g. (start)-[:TYPE]-(target)
|
|
130
|
+
# Node order is always [owner, rel, related] — never swapped.
|
|
125
131
|
rel_direction = case direction
|
|
126
132
|
when :out then Cyrel::Direction::OUT
|
|
127
133
|
when :in then Cyrel::Direction::IN
|
|
@@ -133,17 +139,13 @@ module ActiveCypher
|
|
|
133
139
|
direction: rel_direction
|
|
134
140
|
)
|
|
135
141
|
|
|
136
|
-
|
|
137
|
-
path = case direction
|
|
138
|
-
when :in then Cyrel::Pattern::Path.new([b_node, rel_node, a_node])
|
|
139
|
-
else Cyrel::Pattern::Path.new([a_node, rel_node, b_node])
|
|
140
|
-
end
|
|
142
|
+
path = Cyrel::Pattern::Path.new([owner_node, rel_node, related_node])
|
|
141
143
|
|
|
142
144
|
# Compose query MATCH – WHERE – RETURN
|
|
143
145
|
query = Cyrel::Query.new
|
|
144
146
|
.match(path)
|
|
145
|
-
.where(Cyrel.node_id(
|
|
146
|
-
.return_(
|
|
147
|
+
.where(Cyrel.node_id(owner_alias).eq(internal_id))
|
|
148
|
+
.return_(related_alias)
|
|
147
149
|
|
|
148
150
|
base_relation = Relation.new(target_class, query)
|
|
149
151
|
|
data/lib/active_cypher/base.rb
CHANGED
|
@@ -13,7 +13,7 @@ module ActiveCypher
|
|
|
13
13
|
class Base
|
|
14
14
|
# @!attribute [rw] connects_to_mappings
|
|
15
15
|
# @return [Hash] Because every base class needs a mapping it will never use directly.
|
|
16
|
-
class_attribute :connects_to_mappings, default: {}
|
|
16
|
+
class_attribute :connects_to_mappings, default: { reading: :primary, writing: :primary }
|
|
17
17
|
|
|
18
18
|
# Rails/ActiveModel foundations
|
|
19
19
|
include Logging
|
|
@@ -30,6 +30,7 @@ module ActiveCypher
|
|
|
30
30
|
include Model::Querying
|
|
31
31
|
include Model::Abstract
|
|
32
32
|
include Model::Attributes
|
|
33
|
+
include Model::ConnectionHandling
|
|
33
34
|
include Model::ConnectionOwner
|
|
34
35
|
include Model::Persistence
|
|
35
36
|
include Model::Destruction
|
|
@@ -44,7 +45,7 @@ module ActiveCypher
|
|
|
44
45
|
# Determine the current role (e.g., :writing, :reading)
|
|
45
46
|
# ActiveCypher::RuntimeRegistry.current_role defaults to :writing
|
|
46
47
|
# Only use db_key for pool lookup
|
|
47
|
-
mapping = connects_to_mappings
|
|
48
|
+
mapping = connects_to_mappings
|
|
48
49
|
role = ActiveCypher::RuntimeRegistry.current_role || :writing
|
|
49
50
|
# Debug guardrails removed in release code; rely on role/shard registry.
|
|
50
51
|
|
|
@@ -81,6 +82,17 @@ module ActiveCypher
|
|
|
81
82
|
# Wrap it all up in a fake-sane object string, so you can pretend your data is organized.
|
|
82
83
|
"#<#{self.class} #{parts.join(', ')}>"
|
|
83
84
|
end
|
|
85
|
+
|
|
86
|
+
def internal_id
|
|
87
|
+
working_id = super
|
|
88
|
+
return working_id if working_id.nil?
|
|
89
|
+
|
|
90
|
+
if connection.respond_to?(:id_type_conversion)
|
|
91
|
+
connection.id_type_conversion(working_id)
|
|
92
|
+
else
|
|
93
|
+
working_id
|
|
94
|
+
end
|
|
95
|
+
end
|
|
84
96
|
|
|
85
97
|
# Because Rails needs to feel included, too.
|
|
86
98
|
ActiveSupport.run_load_hooks(:active_cypher, self)
|
|
@@ -7,7 +7,6 @@ module ActiveCypher
|
|
|
7
7
|
module ConnectionOwner
|
|
8
8
|
extend ActiveSupport::Concern
|
|
9
9
|
include ActiveCypher::Logging
|
|
10
|
-
include ActiveCypher::Model::ConnectionHandling
|
|
11
10
|
|
|
12
11
|
def self.db_key_for(mapping, role)
|
|
13
12
|
return nil unless mapping.is_a?(Hash)
|
|
@@ -66,7 +65,7 @@ module ActiveCypher
|
|
|
66
65
|
# Always dynamically fetch the connection for the current db_key
|
|
67
66
|
def connection
|
|
68
67
|
handler = connection_handler
|
|
69
|
-
mapping = connects_to_mappings
|
|
68
|
+
mapping = connects_to_mappings
|
|
70
69
|
role = ActiveCypher::RuntimeRegistry.current_role || :writing
|
|
71
70
|
db_key = ConnectionOwner.db_key_for(mapping, role)
|
|
72
71
|
db_key = db_key.to_sym if db_key.respond_to?(:to_sym)
|
|
@@ -36,6 +36,16 @@ module ActiveCypher
|
|
|
36
36
|
clear_changes_information
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
|
+
|
|
40
|
+
def ==(other)
|
|
41
|
+
# Compares by class and internal graph id only.
|
|
42
|
+
# Note: an unsaved modification will still compare equal to the persisted version.
|
|
43
|
+
return false unless other.instance_of?(self.class)
|
|
44
|
+
|
|
45
|
+
internal_id == other.internal_id
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
39
49
|
end
|
|
40
50
|
end
|
|
41
51
|
end
|
|
@@ -44,8 +44,8 @@ module ActiveCypher
|
|
|
44
44
|
# Find all abstract node base classes with connects_to mappings
|
|
45
45
|
ObjectSpace.each_object(Class) do |klass|
|
|
46
46
|
next unless klass < ActiveCypher::Base
|
|
47
|
-
next unless klass.
|
|
48
|
-
next
|
|
47
|
+
next unless klass.abstract_class?
|
|
48
|
+
next if klass.connects_to_mappings.empty?
|
|
49
49
|
|
|
50
50
|
# Register pools for each role in connects_to mapping
|
|
51
51
|
klass.connects_to_mappings.each_value do |conn_name|
|
|
@@ -164,8 +164,8 @@ module ActiveCypher
|
|
|
164
164
|
# 1. Pull out the node payload and the elementId string
|
|
165
165
|
# ------------------------------------------------------------
|
|
166
166
|
if row.is_a?(Hash)
|
|
167
|
-
node_payload = row[:n] || row['n'] || row
|
|
168
|
-
element_id = row[:internal_id] || row['internal_id']
|
|
167
|
+
node_payload = row[:n] || row['n'] || row[:target] || row['target'] || row
|
|
168
|
+
element_id = row[:internal_id] || row['internal_id'] || node_payload&.dig(1, 0)
|
|
169
169
|
else # Array row: [node, id]
|
|
170
170
|
node_payload, element_id = row
|
|
171
171
|
end
|
|
@@ -176,6 +176,7 @@ module ActiveCypher
|
|
|
176
176
|
# ------------------------------------------------------------
|
|
177
177
|
if node_payload.is_a?(Array) && node_payload.first == 78
|
|
178
178
|
# Re‑use the adapter's private helper for consistency
|
|
179
|
+
# why is it private? This seems to be the only place it's called
|
|
179
180
|
node_payload = model_class.connection
|
|
180
181
|
.send(:process_node, node_payload)
|
|
181
182
|
end
|
|
@@ -29,9 +29,6 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
|
29
29
|
|
|
30
30
|
module ActiveCypher
|
|
31
31
|
class Relationship
|
|
32
|
-
# Define connects_to_mappings as a class attribute to match ActiveCypher::Base
|
|
33
|
-
class_attribute :connects_to_mappings, default: {}
|
|
34
|
-
|
|
35
32
|
# --------------------------------------------------------------
|
|
36
33
|
# Mix‑ins
|
|
37
34
|
# --------------------------------------------------------------
|
|
@@ -44,7 +41,6 @@ module ActiveCypher
|
|
|
44
41
|
include Model::ConnectionOwner
|
|
45
42
|
include Logging
|
|
46
43
|
include Model::Abstract
|
|
47
|
-
include Model::ConnectionHandling
|
|
48
44
|
include Model::Callbacks
|
|
49
45
|
include Model::Countable
|
|
50
46
|
|
|
@@ -203,22 +199,24 @@ module ActiveCypher
|
|
|
203
199
|
|
|
204
200
|
rel_type = relationship_type
|
|
205
201
|
|
|
202
|
+
id_func = connection.class::ID_FUNCTION
|
|
203
|
+
|
|
206
204
|
# Build WHERE conditions for the attributes
|
|
207
205
|
conditions = []
|
|
208
206
|
params = {}
|
|
209
207
|
|
|
210
|
-
attributes.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
if attributes.key?(:internal_id)
|
|
209
|
+
where_clause = "#{id_func}(r) = $p1"
|
|
210
|
+
params['p1'] = attributes[:internal_id]
|
|
211
|
+
else
|
|
212
|
+
attributes.each_with_index do |(key, value), index|
|
|
213
|
+
param_name = :"p#{index + 1}"
|
|
214
|
+
conditions << "r.#{key} = $#{param_name}"
|
|
215
|
+
params[param_name] = value
|
|
216
|
+
end
|
|
217
|
+
where_clause = conditions.join(' AND ')
|
|
214
218
|
end
|
|
215
219
|
|
|
216
|
-
where_clause = conditions.join(' AND ')
|
|
217
|
-
|
|
218
|
-
# Determine ID function based on adapter type
|
|
219
|
-
adapter_class = connection.class
|
|
220
|
-
id_func = adapter_class.const_defined?(:ID_FUNCTION) ? adapter_class::ID_FUNCTION : 'id'
|
|
221
|
-
|
|
222
220
|
cypher = <<~CYPHER
|
|
223
221
|
MATCH ()-[r:#{rel_type}]-()
|
|
224
222
|
WHERE #{where_clause}
|
|
@@ -234,6 +232,12 @@ module ActiveCypher
|
|
|
234
232
|
# Extract relationship data and instantiate
|
|
235
233
|
rel_data = row[:r] || row['r']
|
|
236
234
|
rid = row[:rid] || row['rid']
|
|
235
|
+
from_node_id = (row[:from_node] || row['from_node'])&.dig(1, 0)
|
|
236
|
+
to_node_id = (row[:to_node] || row['to_node'])&.dig(1, 0)
|
|
237
|
+
|
|
238
|
+
# this is extra queries, but easier than navigating instantiation from the row data
|
|
239
|
+
from_node = Object.const_get(self.from_class).find(from_node_id)
|
|
240
|
+
to_node = Object.const_get(self.to_class).find(to_node_id)
|
|
237
241
|
|
|
238
242
|
# Extract properties from the relationship data
|
|
239
243
|
# Memgraph returns relationships wrapped as [type_code, [actual_data]]
|
|
@@ -256,7 +260,7 @@ module ActiveCypher
|
|
|
256
260
|
attrs = attrs.transform_keys(&:to_sym)
|
|
257
261
|
attrs[:internal_id] = rid if rid
|
|
258
262
|
|
|
259
|
-
instantiate(attrs)
|
|
263
|
+
instantiate(attrs, from_node: from_node, to_node: to_node)
|
|
260
264
|
end
|
|
261
265
|
|
|
262
266
|
# Find the first relationship or raise an exception
|
|
@@ -8,11 +8,13 @@ namespace :graphdb do
|
|
|
8
8
|
puts 'GraphDB migrations complete'
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
namespace :migrate do
|
|
12
|
+
# bin/rails graphdb:migrate:status
|
|
13
|
+
desc 'Show graph database migration status'
|
|
14
|
+
task status: :environment do
|
|
15
|
+
ActiveCypher::Migrator.new.status.each do |m|
|
|
16
|
+
puts format('%-4<status>s %<version>s %<name>s', m)
|
|
17
|
+
end
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activecypher
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.15.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Abdelkader Boudih
|
|
@@ -293,7 +293,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
293
293
|
- !ruby/object:Gem::Version
|
|
294
294
|
version: '0'
|
|
295
295
|
requirements: []
|
|
296
|
-
rubygems_version:
|
|
296
|
+
rubygems_version: 4.0.6
|
|
297
297
|
specification_version: 4
|
|
298
298
|
summary: OpenCypher Adapter ala ActiveRecord
|
|
299
299
|
test_files: []
|