activecypher 0.10.4 → 0.11.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.
@@ -7,6 +7,7 @@ module ActiveCypher
7
7
  # It maintains a connection to the database server and allows running queries.
8
8
  class Session
9
9
  include Instrumentation
10
+
10
11
  attr_reader :connection, :database
11
12
 
12
13
  def initialize(connection, database: nil)
@@ -120,53 +121,34 @@ module ActiveCypher
120
121
  # @yield [tx] The transaction to use for queries.
121
122
  # @return The result of the block.
122
123
  def run_transaction(mode = :write, db: nil, timeout: nil, metadata: nil, &)
123
- # Ensure we're running in an Async context
124
124
  if Async::Task.current?
125
- # Already in an Async task, proceed normally
126
- begin
127
- execute_transaction(mode, db: db, timeout: timeout, metadata: metadata, &)
128
- rescue StandardError => e
129
- # Ensure errors are properly propagated
130
- raise e
131
- end
125
+ # Already in an async context, just run the block.
126
+ # The block will run asynchronously within the current task.
127
+ _execute_transaction_block(mode, db, timeout, metadata, &)
132
128
  else
133
- # Wrap in an Async task
134
- result = nil
135
- error = nil
136
-
129
+ # Not in an async context, so we need to create one and wait for it to complete.
137
130
  Async do
138
- result = execute_transaction(mode, db: db, timeout: timeout, metadata: metadata, &)
139
- rescue StandardError => e
140
- error = e
131
+ _execute_transaction_block(mode, db, timeout, metadata, &)
141
132
  end.wait
142
-
143
- # Re-raise any error outside the async block
144
- raise error if error
145
-
146
- result
147
133
  end
148
134
  end
149
135
 
150
- # Helper method to execute the transaction
151
- def execute_transaction(mode, db:, timeout:, metadata:)
152
- tx = begin_transaction(db: db,
153
- access_mode: mode,
154
- tx_timeout: timeout,
155
- tx_metadata: metadata)
156
-
157
- result = yield tx # your block runs here
158
- tx.commit # happy path
159
- result
160
- rescue StandardError => e # any error rollback wrap
161
- begin
162
- tx.rollback
163
- rescue StandardError => rollback_error
164
- # Log rollback error but continue with the original error
165
- puts "Error during rollback: #{rollback_error.message}" if ENV['DEBUG']
166
- end
136
+ # Asynchronously execute a block of code within a transaction.
137
+ # This method is asynchronous and will return an `Async::Task` that will complete when the transaction is finished.
138
+ #
139
+ # @param mode [Symbol] The access mode (:read or :write).
140
+ # @param db [String] The database name to run the transaction against.
141
+ # @param timeout [Integer] Transaction timeout in milliseconds.
142
+ # @param metadata [Hash] Transaction metadata to send to the server.
143
+ # @yield [tx] The transaction to use for queries.
144
+ # @return [Async::Task] A task that will complete with the result of the block.
145
+ def async_run_transaction(mode = :write, db: nil, timeout: nil, metadata: nil, &)
146
+ # Ensure we are in an async task, otherwise the behavior is undefined.
147
+ raise 'Cannot run an async transaction outside of an Async task' unless Async::Task.current?
167
148
 
168
- # Preserve the original error
169
- raise ActiveCypher::TransactionError, e.message
149
+ Async do
150
+ _execute_transaction_block(mode, db, timeout, metadata, &)
151
+ end
170
152
  end
171
153
 
172
154
  def write_transaction(db: nil, timeout: nil, metadata: nil, &)
@@ -177,6 +159,55 @@ module ActiveCypher
177
159
  run_transaction(:read, db: db, timeout: timeout, metadata: metadata, &)
178
160
  end
179
161
 
162
+ def async_write_transaction(db: nil, timeout: nil, metadata: nil, &)
163
+ async_run_transaction(:write, db: db, timeout: timeout, metadata: metadata, &)
164
+ end
165
+
166
+ def async_read_transaction(db: nil, timeout: nil, metadata: nil, &)
167
+ async_run_transaction(:read, db: db, timeout: timeout, metadata: metadata, &)
168
+ end
169
+
170
+ # Close the session and any active transaction.
171
+ def close
172
+ instrument('session.close') do
173
+ # If there's an active transaction, try to roll it back
174
+ @current_transaction&.rollback if @current_transaction&.active?
175
+
176
+ # Mark current transaction as complete
177
+ complete_transaction(@current_transaction) if @current_transaction
178
+ end
179
+ end
180
+
181
+ private
182
+
183
+ def _execute_transaction_block(mode, db, timeout, metadata, &block)
184
+ tx = begin_transaction(db: db, access_mode: mode, tx_timeout: timeout, tx_metadata: metadata)
185
+ begin
186
+ result = block.call(tx)
187
+ tx.commit
188
+ result
189
+ rescue StandardError => e
190
+ # On any error, rollback the transaction and re-raise the original exception
191
+ begin
192
+ tx.rollback
193
+ rescue StandardError => rollback_error
194
+ # Log rollback error but continue with the original error
195
+ puts "Error during rollback: #{rollback_error.message}" if ENV['DEBUG']
196
+ end
197
+
198
+ # Reset the connection to ensure it's in a clean state for the next transaction
199
+ begin
200
+ @connection.reset!
201
+ rescue StandardError => reset_error
202
+ # If reset fails, the connection will be marked non-viable by the pool
203
+ puts "Error during connection reset: #{reset_error.message}" if ENV['DEBUG']
204
+ end
205
+
206
+ # Wrap the error in TransactionError to maintain compatibility
207
+ raise ActiveCypher::TransactionError, e.message
208
+ end
209
+ end
210
+
180
211
  # Access the current bookmarks for this session.
181
212
  def bookmarks
182
213
  @bookmarks || []
@@ -197,17 +228,6 @@ module ActiveCypher
197
228
  @connection.reset!
198
229
  end
199
230
  end
200
-
201
- # Close the session and any active transaction.
202
- def close
203
- instrument('session.close') do
204
- # If there's an active transaction, try to roll it back
205
- @current_transaction&.rollback if @current_transaction&.active?
206
-
207
- # Mark current transaction as complete
208
- complete_transaction(@current_transaction) if @current_transaction
209
- end
210
- end
211
231
  end
212
232
  end
213
233
  end
@@ -5,6 +5,7 @@ module ActiveCypher
5
5
  # Manages transaction state (BEGIN/COMMIT/ROLLBACK) and runs queries within a transaction.
6
6
  class Transaction
7
7
  include Instrumentation
8
+
8
9
  attr_reader :bookmarks, :metadata, :connection
9
10
 
10
11
  # Initializes a new Transaction instance.
@@ -147,11 +148,7 @@ module ActiveCypher
147
148
  rescue ConnectionError
148
149
  @state = :failed
149
150
  # Mark transaction as completed in the session
150
- begin
151
- @session.complete_transaction(self)
152
- rescue StandardError
153
- nil
154
- end
151
+ @session.complete_transaction(self)
155
152
  raise
156
153
  end
157
154
 
@@ -184,11 +181,7 @@ module ActiveCypher
184
181
  ensure
185
182
  # Always mark as rolled back and complete the transaction
186
183
  @state = :rolled_back
187
- begin
188
- @session.complete_transaction(self)
189
- rescue StandardError
190
- nil
191
- end
184
+ @session.complete_transaction(self)
192
185
  end
193
186
  end
194
187
 
@@ -49,32 +49,12 @@ module ActiveCypher
49
49
  end
50
50
  end
51
51
 
52
- # Get current adapter type for ID handling
53
- # Helper for generating ID-related Cypher functions that are database-specific
54
- module CypherFunction
55
- # Generate ID equality clause with the ID value embedded in the query for Memgraph
56
- def self.id_equals(var, id_value, adapter)
57
- func = id_function(adapter)
58
- "#{func}(#{var}) = #{id_value}"
59
- end
60
-
61
- # Generate ID equality clause using a parameterized ID value for Neo4j
62
- def self.id_equals_param(var, param_name, adapter)
63
- func = id_function(adapter)
64
- "#{func}(#{var}) = $#{param_name}"
65
- end
66
-
67
- # Generate a node variable with ID predicate
68
- def self.node_with_id(node_var, id_value, adapter)
69
- func = id_function(adapter)
70
- "#{func}(#{node_var}) = #{id_value}"
71
- end
72
-
73
- # Return ID expression
74
- def self.return_id(var, as_name, adapter)
75
- func = id_function(adapter)
76
- "#{func}(#{var}) AS #{as_name}"
77
- end
52
+ # Hydrates attributes from a database record
53
+ # @param record [Hash] The raw record from the database
54
+ # @param node_alias [Symbol] The alias used for the node in the query
55
+ # @return [Hash] The hydrated attributes
56
+ def hydrate_record(record, node_alias)
57
+ raise NotImplementedError, "#{self.class} must implement #hydrate_record"
78
58
  end
79
59
 
80
60
  # Turns rows into symbols, because Rubyists fear strings.
@@ -10,8 +10,16 @@ module ActiveCypher
10
10
  # It's like ActiveRecord::ConnectionAdapter, but for weirdos like me who use graph databases.
11
11
  class AbstractBoltAdapter < AbstractAdapter
12
12
  include Instrumentation
13
+
13
14
  attr_reader :connection
14
15
 
16
+ # Returns the raw Bolt connection object
17
+ # This is useful for accessing low-level connection methods like
18
+ # read_transaction, write_transaction, async_read_transaction, etc.
19
+ def raw_connection
20
+ @connection
21
+ end
22
+
15
23
  # Establish a connection if not already active.
16
24
  # This includes auth token prep, URI parsing, and quiet suffering.
17
25
  def connect
@@ -64,7 +72,7 @@ module ActiveCypher
64
72
  # Clean disconnection. Resets the internal state.
65
73
  def disconnect
66
74
  instrument_connection(:disconnect) do
67
- @connection&.close
75
+ @connection.close if @connection
68
76
  @connection = nil
69
77
  true
70
78
  end
@@ -11,8 +11,6 @@ module ActiveCypher
11
11
  def schema_catalog
12
12
  rows = run('SHOW SCHEMA')
13
13
  parse_schema(rows)
14
- rescue StandardError
15
- introspect_fallback
16
14
  end
17
15
 
18
16
  # Use id() for Memgraph instead of elementId()
@@ -108,6 +106,30 @@ module ActiveCypher
108
106
  metadata.compact
109
107
  end
110
108
 
109
+ # Hydrates attributes from a Memgraph record
110
+ # @param record [Hash] The raw record from Memgraph
111
+ # @param node_alias [Symbol] The alias used for the node in the query
112
+ # @return [Hash] The hydrated attributes
113
+ def hydrate_record(record, node_alias)
114
+ attrs = {}
115
+ node_data = record[node_alias] || record[node_alias.to_s]
116
+
117
+ if node_data.is_a?(Array) && node_data.length >= 2
118
+ properties_container = node_data[1]
119
+ if properties_container.is_a?(Array) && properties_container.length >= 3
120
+ properties = properties_container[2]
121
+ properties.each { |k, v| attrs[k.to_sym] = v } if properties.is_a?(Hash)
122
+ end
123
+ elsif node_data.is_a?(Hash)
124
+ node_data.each { |k, v| attrs[k.to_sym] = v }
125
+ elsif node_data.respond_to?(:properties)
126
+ attrs = node_data.properties.symbolize_keys
127
+ end
128
+
129
+ attrs[:internal_id] = record[:internal_id] || record['internal_id']
130
+ attrs
131
+ end
132
+
111
133
  protected
112
134
 
113
135
  def parse_schema(rows)
@@ -174,6 +196,7 @@ module ActiveCypher
174
196
 
175
197
  module Persistence
176
198
  include PersistenceMethods
199
+
177
200
  module_function :create_record, :update_record, :destroy_record
178
201
  end
179
202
 
@@ -33,8 +33,6 @@ module ActiveCypher
33
33
 
34
34
  Schema::Catalog.new(indexes: idx_defs, constraints: con_defs,
35
35
  node_types: [], edge_types: [])
36
- rescue StandardError
37
- Schema::Catalog.new(indexes: [], constraints: [], node_types: [], edge_types: [])
38
36
  end
39
37
 
40
38
  # Use elementId() for Neo4j
@@ -130,8 +128,33 @@ module ActiveCypher
130
128
  metadata.compact
131
129
  end
132
130
 
131
+ # Hydrates attributes from a Neo4j record
132
+ # @param record [Hash] The raw record from Neo4j
133
+ # @param node_alias [Symbol] The alias used for the node in the query
134
+ # @return [Hash] The hydrated attributes
135
+ def hydrate_record(record, node_alias)
136
+ attrs = {}
137
+ node_data = record[node_alias] || record[node_alias.to_s]
138
+
139
+ if node_data.is_a?(Array) && node_data.length >= 2
140
+ properties_container = node_data[1]
141
+ if properties_container.is_a?(Array) && properties_container.length >= 3
142
+ properties = properties_container[2]
143
+ properties.each { |k, v| attrs[k.to_sym] = v } if properties.is_a?(Hash)
144
+ end
145
+ elsif node_data.is_a?(Hash)
146
+ node_data.each { |k, v| attrs[k.to_sym] = v }
147
+ elsif node_data.respond_to?(:properties)
148
+ attrs = node_data.properties.symbolize_keys
149
+ end
150
+
151
+ attrs[:internal_id] = record[:internal_id] || record['internal_id']
152
+ attrs
153
+ end
154
+
133
155
  module Persistence
134
156
  include PersistenceMethods
157
+
135
158
  module_function :create_record, :update_record, :destroy_record
136
159
  end
137
160
 
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/atomic/atomic_reference'
4
3
  require 'timeout'
5
4
 
6
5
  module ActiveCypher
@@ -24,29 +23,27 @@ module ActiveCypher
24
23
 
25
24
  # Merge the resolved config with any additional options
26
25
  @spec = resolved_config.merge(@spec.except(:url))
27
-
28
26
  end
29
27
 
30
- @conn_ref = Concurrent::AtomicReference.new # holds the adapter instance
28
+ @conn_ref = nil # holds the adapter instance
31
29
  @creation_mutex = Mutex.new # prevents multiple threads from creating connections simultaneously
32
- @retry_count = Concurrent::AtomicReference.new(0)
33
30
  end
34
31
 
35
- # Returns a live adapter, initialising it once in a thread‑safe way.
32
+ # Returns a live adapter, initializing it once in a thread‑safe way.
36
33
  def connection
37
- # Fast path —already connected and alive
38
- conn = @conn_ref.value
34
+ # Fast path — already connected and alive
35
+ conn = @conn_ref
39
36
  return conn if conn&.active?
40
37
 
41
38
  # Use mutex for the slow path to prevent thundering herd
42
39
  @creation_mutex.synchronize do
43
40
  # Check again inside the mutex in case another thread created it
44
- conn = @conn_ref.value
41
+ conn = @conn_ref
45
42
  return conn if conn&.active?
46
43
 
47
44
  # Create a new connection
48
45
  new_conn = build_connection
49
- @conn_ref.set(new_conn)
46
+ @conn_ref = new_conn
50
47
  return new_conn
51
48
  end
52
49
  end
@@ -54,12 +51,12 @@ module ActiveCypher
54
51
 
55
52
  # Check if the pool has a persistent connection issue
56
53
  def troubled?
57
- @retry_count.value >= @spec[:max_retries]
54
+ @retry_count >= @spec[:max_retries]
58
55
  end
59
56
 
60
57
  # Explicitly close and reset the connection
61
58
  def disconnect
62
- conn = @conn_ref.value
59
+ conn = @conn_ref
63
60
  return unless conn
64
61
 
65
62
  begin
@@ -68,7 +65,7 @@ module ActiveCypher
68
65
  # Log but don't raise to ensure cleanup continues
69
66
  puts "Warning: Error disconnecting: #{e.message}" if ENV['DEBUG']
70
67
  ensure
71
- @conn_ref.set(nil)
68
+ @conn_ref = nil
72
69
  end
73
70
  end
74
71
 
@@ -91,8 +88,8 @@ module ActiveCypher
91
88
  rescue Timeout::Error
92
89
  begin
93
90
  adapter.disconnect
94
- rescue StandardError
95
- nil
91
+ rescue StandardError => e
92
+ puts "Warning: Error disconnecting during timeout cleanup: #{e.message}" if ENV['DEBUG']
96
93
  end
97
94
  raise ConnectionError, "Connection timed out after #{@spec[:pool_timeout]} seconds"
98
95
  end
@@ -32,11 +32,7 @@ module ActiveCypher
32
32
 
33
33
  # 5. Gather unique connections for all model classes referenced in this profile
34
34
  model_classes = dsl_context.nodes.map { |node| node[:model_class] }.uniq
35
- connections = model_classes.map do |klass|
36
- klass.connection
37
- rescue StandardError
38
- nil
39
- end.compact.uniq
35
+ connections = model_classes.map(&:connection).compact.uniq
40
36
 
41
37
  # 6. Wipe all nodes in each relevant connection
42
38
  connections.each do |conn|
@@ -78,11 +74,7 @@ module ActiveCypher
78
74
  end
79
75
 
80
76
  # Gather unique connections from all model classes
81
- connections = model_classes.map do |klass|
82
- klass.connection
83
- rescue StandardError
84
- nil
85
- end.compact.uniq
77
+ connections = model_classes.map(&:connection).compact.uniq
86
78
 
87
79
  # Wipe all nodes in each connection
88
80
  connections.each do |conn|
@@ -104,17 +96,13 @@ module ActiveCypher
104
96
  next unless klass < ActiveCypher::Base
105
97
  next if klass.respond_to?(:abstract_class?) && klass.abstract_class?
106
98
 
107
- begin
108
- conn = klass.connection
109
- # Store connection details for comparison
110
- model_connections[klass] = {
111
- adapter: conn.class.name,
112
- config: conn.instance_variable_get(:@config),
113
- object_id: conn.object_id
114
- }
115
- rescue StandardError
116
- # Skip if can't get connection
117
- end
99
+ conn = klass.connection
100
+ # Store connection details for comparison
101
+ model_connections[klass] = {
102
+ adapter: conn.class.name,
103
+ config: conn.instance_variable_get(:@config),
104
+ object_id: conn.object_id
105
+ }
118
106
  end
119
107
 
120
108
  relationships.each do |rel|
@@ -132,7 +132,7 @@ module ActiveCypher
132
132
  # @param key [String, Symbol] The key to check
133
133
  # @return [Boolean] True if the key contains sensitive information
134
134
  def sensitive_key?(key)
135
- return true if key.to_s.match?(/(^|[\-_])(?:password|token|secret|credential|key)($|[\-_])/i)
135
+ return true if key.to_s.match?(/(^|[-_])(?:password|token|secret|credential|key)($|[-_])/i)
136
136
 
137
137
  # Check against Rails filter parameters if available
138
138
  if defined?(Rails) && Rails.application
@@ -14,11 +14,7 @@ module ActiveCypher
14
14
  self.backend ||= begin
15
15
  base = Logger.new($stdout)
16
16
  base.level = ENV.fetch('AC_LOG_LEVEL', 'info').upcase
17
- .then do |lvl|
18
- Logger.const_get(lvl)
19
- rescue StandardError
20
- Logger::INFO
21
- end
17
+ .then { |lvl| Logger.const_get(lvl) }
22
18
  ActiveSupport::TaggedLogging.new(base).tap { |l| l.tagged! 'ActiveCypher' }
23
19
  end
24
20
  end
@@ -64,8 +64,6 @@ module ActiveCypher
64
64
  def existing_versions
65
65
  @connection.execute_cypher('MATCH (m:SchemaMigration) RETURN m.version AS version')
66
66
  .map { |r| r[:version].to_s }
67
- rescue StandardError
68
- []
69
67
  end
70
68
 
71
69
  def ensure_schema_migration_constraint
@@ -23,7 +23,8 @@ module ActiveCypher
23
23
 
24
24
  # Returns the adapter class being used by this model
25
25
  def adapter_class
26
- connection&.class
26
+ conn = connection
27
+ conn&.class
27
28
  end
28
29
 
29
30
  # Always dynamically fetch the connection for the current db_key
@@ -39,9 +39,7 @@ module ActiveCypher
39
39
  adapter = connection.id_handler
40
40
  label_string = labels.map { |l| ":#{l}" }.join
41
41
 
42
- # Handle ID format based on adapter's preferred flavor of existential crisis
43
- # Neo4j insists on string IDs like "4:uuid:wtf" because simple integers are for peasants
44
- # Memgraph keeps it real with numeric IDs because it doesn't need to prove anything
42
+ # Handle ID format based on adapter type
45
43
  formatted_id = if adapter.id_function == 'elementId'
46
44
  internal_db_id.to_s # String for Neo4j
47
45
  else
@@ -59,7 +57,7 @@ module ActiveCypher
59
57
  record = result.first
60
58
 
61
59
  if record
62
- attrs = _hydrate_attributes_from_memgraph_record(record, node_alias)
60
+ attrs = connection.hydrate_record(record, node_alias)
63
61
  return instantiate(attrs)
64
62
  end
65
63
 
@@ -97,28 +95,6 @@ module ActiveCypher
97
95
  # @return [Object] The new, possibly persisted record
98
96
  # Because sometimes you just want to live dangerously.
99
97
  def create(attrs = {}) = new(attrs).tap(&:save)
100
-
101
- private
102
-
103
- def _hydrate_attributes_from_memgraph_record(record, node_alias)
104
- attrs = {}
105
- node_data = record[node_alias] || record[node_alias.to_s]
106
-
107
- if node_data.is_a?(Array) && node_data.length >= 2
108
- properties_container = node_data[1]
109
- if properties_container.is_a?(Array) && properties_container.length >= 3
110
- properties = properties_container[2]
111
- properties.each { |k, v| attrs[k.to_sym] = v } if properties.is_a?(Hash)
112
- end
113
- elsif node_data.is_a?(Hash)
114
- node_data.each { |k, v| attrs[k.to_sym] = v }
115
- elsif node_data.respond_to?(:properties)
116
- attrs = node_data.properties.symbolize_keys
117
- end
118
-
119
- attrs[:internal_id] = record[:internal_id] || record['internal_id']
120
- attrs
121
- end
122
98
  end
123
99
  end
124
100
  end
@@ -73,11 +73,7 @@ module ActiveCypher
73
73
 
74
74
  return @connection if defined?(@connection) && @connection
75
75
 
76
- begin
77
- from_class.constantize.connection
78
- rescue StandardError
79
- nil
80
- end
76
+ from_class.constantize.connection
81
77
  end
82
78
 
83
79
  # --------------------------------------------------------------
@@ -301,9 +297,14 @@ module ActiveCypher
301
297
  _run(:update) { update_relationship }
302
298
  end
303
299
  end
304
- rescue StandardError => e
305
- log_error "Failed to save #{self.class}: #{e.class} #{e.message}"
306
- false
300
+ rescue ActiveCypher::RecordNotSaved, RuntimeError => e
301
+ # Only catch specific validation errors, let other errors propagate
302
+ if e.message.include?('must be persisted') || e.message.include?('creation returned no id')
303
+ log_error "Failed to save #{self.class}: #{e.message}"
304
+ false
305
+ else
306
+ raise
307
+ end
307
308
  end
308
309
 
309
310
  # Bang version of save - raises exception if save fails
@@ -366,16 +367,14 @@ module ActiveCypher
366
367
 
367
368
  props = attributes.except('internal_id').compact
368
369
  rel_ty = self.class.relationship_type
369
- arrow = '->' # outgoing by default
370
-
371
370
  adapter = self.class.connection.id_handler
372
- parts = []
373
371
 
374
372
  # Build the Cypher query based on the adapter
375
373
  id_clause = adapter.with_direct_node_ids(from_node.internal_id, to_node.internal_id)
374
+ parts = []
376
375
  parts << "MATCH (p), (h) WHERE #{id_clause}"
377
- parts << "CREATE (p)-[r:#{rel_ty}]#{arrow}(h)"
378
- parts << 'SET r += $props' unless props.empty? # only if we have props
376
+ parts << "CREATE (p)-[r:#{rel_ty}]->(h)"
377
+ parts << 'SET r += $props' unless props.empty?
379
378
  parts << "RETURN #{adapter.return_id}"
380
379
 
381
380
  cypher = parts.join(' ')
@@ -383,7 +382,6 @@ module ActiveCypher
383
382
 
384
383
  # Execute Cypher query
385
384
  result = self.class.connection.execute_cypher(cypher, params, 'Create Relationship')
386
-
387
385
  row = result.first
388
386
 
389
387
  # Try different ways to access the ID
@@ -399,9 +397,6 @@ module ActiveCypher
399
397
  @new_record = false
400
398
  changes_applied
401
399
  true
402
- rescue StandardError => e
403
- log_error "Failed to save #{self.class}: #{e.class} – #{e.message}"
404
- false
405
400
  end
406
401
 
407
402
  def update_relationship
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveCypher
4
- VERSION = '0.10.4'
4
+ VERSION = '0.11.1'
5
5
 
6
6
  def self.gem_version
7
7
  Gem::Version.new VERSION
@@ -9,20 +9,14 @@ module Cyrel
9
9
 
10
10
  def initialize(ast_node)
11
11
  @ast_node = ast_node
12
- @ast_node_hash = ast_node.hash
13
12
  super()
14
13
  end
15
14
 
16
15
  def render(query)
17
- # Use a simple cache key based on AST node structure
18
- cache_key = [@ast_node_hash, @ast_node.class.name].join(':')
19
-
20
- SimpleCache.instance.fetch(cache_key) do
21
- # Create a compiler that delegates parameter registration to the query
22
- compiler = QueryIntegratedCompiler.new(query)
23
- compiler.compile(ast_node)
24
- compiler.output.string
25
- end
16
+ # Create a compiler that delegates parameter registration to the query
17
+ compiler = QueryIntegratedCompiler.new(query)
18
+ compiler.compile(ast_node)
19
+ compiler.output.string
26
20
  end
27
21
 
28
22
  # Ruby 3.0+ pattern matching support