activecypher 0.9.0 → 0.10.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b70a7612e29fd973054f654ad63f28b6fe965d993073ffd39aefcdf10798326
4
- data.tar.gz: c23ad33676db0a63bb389bb8edc597eaa4eb6f8cdcfbf44eb32e0eb4c640c1fd
3
+ metadata.gz: cee43c77d5c77240791bd23a3ea08410ae58034f29f86d4bd0ec35bc65640b43
4
+ data.tar.gz: 9a5947493ac00f596f11a3ae6e5ceba5c1bb145b56a203c38e27f9c011790fdb
5
5
  SHA512:
6
- metadata.gz: 3688ca19c11ee45562da2a6ccd6d65be740d4ba838802ec8dc52e8453ee80e42fbd771585e31b21ab857b6acb7abc449423debbae9cd2d86a5f9207009cc847d
7
- data.tar.gz: f86b0d54cea7d36aa2d9093d37d0a816ab4fed6485b3697bc31c9a76abda329cae4adb5c33845baccaa5e86153c36aa4996146150b224b2eb7dec927fa43a8f2
6
+ metadata.gz: 8465c9e97cd9ce106b7a77e252714c72ac0fcb5470b35eccc0315e9277830402157ef07671b3a1cc757f757be0719f7475781a515322113bf23c698c88bd473c
7
+ data.tar.gz: fbb7585d26ac13eda94bad3e72a7511d133d80ba4b4840c3c9001f323d818f6d6c61d45f26916a1e40ef93555bb1a75e6d4240e776f1d31f22412023faf9dbd3
@@ -73,6 +73,7 @@ module ActiveCypher
73
73
  # Because apparently typing .where(attrs).limit(1).first was giving people RSI
74
74
  def find_by(attributes = {})
75
75
  return nil if attributes.blank?
76
+
76
77
  where(attributes).limit(1).first
77
78
  end
78
79
 
@@ -83,12 +84,12 @@ module ActiveCypher
83
84
  # For when nil isn't dramatic enough and you need your code to scream at you
84
85
  def find_by!(attributes = {})
85
86
  # Format attributes nicely for the error message
86
- formatted_attrs = attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
87
-
87
+ formatted_attrs = attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
88
+
88
89
  find_by(attributes) || raise(ActiveCypher::RecordNotFound,
89
90
  "Couldn't find #{name} with #{formatted_attrs}. " \
90
91
  "Perhaps it's hiding in another graph, or maybe it never existed. " \
91
- "Who can say in this vast, uncaring universe of nodes and relationships?")
92
+ 'Who can say in this vast, uncaring universe of nodes and relationships?')
92
93
  end
93
94
 
94
95
  # Instantiates and immediately saves a new record. YOLO mode.
@@ -159,6 +159,21 @@ module ActiveCypher
159
159
  new(attrs, from_node: from_node, to_node: to_node).tap(&:save)
160
160
  end
161
161
 
162
+ # Bang version of create - raises exception if save fails
163
+ # For when you want your relationship failures to be as dramatic as your breakups
164
+ def create!(attrs = {}, from_node:, to_node:)
165
+ relationship = create(attrs, from_node: from_node, to_node: to_node)
166
+ if relationship.persisted?
167
+ relationship
168
+ else
169
+ error_msgs = relationship.errors.full_messages.join(', ')
170
+ error_msgs = 'Validation failed' if error_msgs.empty?
171
+ raise ActiveCypher::RecordNotSaved,
172
+ "#{name} could not be saved: #{error_msgs}. " \
173
+ "Perhaps the nodes aren't ready for this kind of commitment?"
174
+ end
175
+ end
176
+
162
177
  # Instantiate from DB row, marking the instance as persisted.
163
178
  def instantiate(attributes, from_node: nil, to_node: nil)
164
179
  instance = allocate
@@ -168,6 +183,82 @@ module ActiveCypher
168
183
  to_node: to_node)
169
184
  instance
170
185
  end
186
+
187
+ # -- Querying methods ----------------------------------------
188
+ # Find the first relationship matching the given attributes
189
+ # Like finding a needle in a haystack, if the haystack was made of graph edges
190
+ def find_by(attributes = {})
191
+ return nil if attributes.blank?
192
+
193
+ rel_type = relationship_type
194
+
195
+ # Build WHERE conditions for the attributes
196
+ conditions = []
197
+ params = {}
198
+
199
+ attributes.each_with_index do |(key, value), index|
200
+ param_name = :"p#{index + 1}"
201
+ conditions << "r.#{key} = $#{param_name}"
202
+ params[param_name] = value
203
+ end
204
+
205
+ where_clause = conditions.join(' AND ')
206
+
207
+ # Determine ID function based on adapter type
208
+ adapter_class = connection.class
209
+ id_func = adapter_class.const_defined?(:ID_FUNCTION) ? adapter_class::ID_FUNCTION : 'id'
210
+
211
+ cypher = <<~CYPHER
212
+ MATCH ()-[r:#{rel_type}]-()
213
+ WHERE #{where_clause}
214
+ RETURN r, #{id_func}(r) as rid, startNode(r) as from_node, endNode(r) as to_node
215
+ LIMIT 1
216
+ CYPHER
217
+
218
+ result = connection.execute_cypher(cypher, params, 'Find Relationship By')
219
+ row = result.first
220
+
221
+ return nil unless row
222
+
223
+ # Extract relationship data and instantiate
224
+ rel_data = row[:r] || row['r']
225
+ rid = row[:rid] || row['rid']
226
+
227
+ # Extract properties from the relationship data
228
+ # Memgraph returns relationships wrapped as [type_code, [actual_data]]
229
+ attrs = {}
230
+
231
+ if rel_data.is_a?(Array) && rel_data.length == 2
232
+ # Extract the actual relationship data from the second element
233
+ actual_data = rel_data[1]
234
+
235
+ if actual_data.is_a?(Array) && actual_data.length >= 5
236
+ # Format: [rel_id, start_id, end_id, type, properties, ...]
237
+ props = actual_data[4]
238
+ attrs = props.is_a?(Hash) ? props : {}
239
+ end
240
+ elsif rel_data.is_a?(Hash)
241
+ attrs = rel_data
242
+ end
243
+
244
+ # Convert string keys to symbols for attributes
245
+ attrs = attrs.transform_keys(&:to_sym)
246
+ attrs[:internal_id] = rid if rid
247
+
248
+ instantiate(attrs)
249
+ end
250
+
251
+ # Find the first relationship or raise an exception
252
+ # For when nil just isn't dramatic enough for your data access needs
253
+ def find_by!(attributes = {})
254
+ # Format attributes nicely for the error message
255
+ formatted_attrs = attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
256
+
257
+ find_by(attributes) || raise(ActiveCypher::RecordNotFound,
258
+ "Couldn't find #{name} with #{formatted_attrs}. " \
259
+ 'Maybe these nodes were never meant to be connected? ' \
260
+ 'Or perhaps their relationship status is... complicated?')
261
+ end
171
262
  end
172
263
 
173
264
  # --------------------------------------------------------------
@@ -232,6 +323,21 @@ module ActiveCypher
232
323
  # --------------------------------------------------------------
233
324
  private
234
325
 
326
+ # Initialize from database attributes, marking as persisted
327
+ def init_with_attributes(attributes, from_node: nil, to_node: nil)
328
+ # Initialize the model first to set up attributes
329
+ initialize({}, from_node: from_node, to_node: to_node)
330
+
331
+ # Now we're not a new record
332
+ @new_record = false
333
+
334
+ # Assign the attributes from the database
335
+ assign_attributes(attributes) if attributes
336
+
337
+ # Clear any change tracking
338
+ clear_changes_information
339
+ end
340
+
235
341
  def create_relationship
236
342
  raise 'Source node must be persisted' unless from_node&.persisted?
237
343
  raise 'Target node must be persisted' unless to_node&.persisted?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveCypher
4
- VERSION = '0.9.0'
4
+ VERSION = '0.10.0'
5
5
 
6
6
  def self.gem_version
7
7
  Gem::Version.new VERSION
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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih