Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions lib/active_cypher/associations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ def define_belongs_to_methods(reflection)
instance_variable_set(instance_var, associate)
end

define_build_and_create_methods(name, target_class_name)
end

# Defines build_<name> and create_<name> for singular associations
# (shared by belongs_to and has_one).
def define_build_and_create_methods(name, target_class_name)
# Define build method (e.g., build_author(name: "New Author"))
define_method("build_#{name}") do |attributes = {}|
target_class = target_class_name.constantize
Expand Down Expand Up @@ -431,25 +437,7 @@ def define_has_one_methods(reflection)
instance_variable_set(instance_var, associate)
end

# Define build method (e.g., build_profile(data: {...}))
define_method("build_#{name}") do |attributes = {}|
target_class = target_class_name.constantize
# TODO: Potentially set the inverse association reference here
# For now, just instantiate the target class
target_class.new(attributes)
end

# Define create method (e.g., create_profile(data: {...}))
define_method("create_#{name}") do |attributes = {}|
# Build the instance
instance = public_send("build_#{name}", attributes)
# Save the instance
instance.save
# If save is successful, associate it using the = method
public_send("#{name}=", instance) if instance.persisted?
# Return the instance
instance
end
define_build_and_create_methods(name, target_class_name)
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/active_cypher/associations/collection_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def to_a
# Returns the size, because counting things is the only certainty in life.
#
# @return [Integer] The number of records in the collection
def size = load_target.size
def size = load_target.size
alias length size

# Fully refresh from the database.
Expand Down
2 changes: 1 addition & 1 deletion lib/active_cypher/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def inspect
# Wrap it all up in a fake-sane object string, so you can pretend your data is organized.
"#<#{self.class} #{parts.join(', ')}>"
end

def internal_id
working_id = super
return working_id if working_id.nil?
Expand Down
118 changes: 16 additions & 102 deletions lib/active_cypher/bolt/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,6 @@ def write_transaction(db: nil, timeout: nil, metadata: nil, &)
session(database: db).write_transaction(db: db, timeout: timeout, metadata: metadata, &)
end


# ────────────────────────────────────────────────────────────────────
# HEALTH AND VERSION DETECTION METHODS
# ────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -514,21 +513,17 @@ def database_type
def health_check
return { healthy: false, response_time_ms: nil, details: 'Not connected' } unless connected?

result = nil
nil

begin
result = Sync do
case database_type
when :neo4j
perform_neo4j_health_check
when :memgraph
perform_memgraph_health_check
else
perform_generic_health_check
end
Sync do
query = case database_type
when :neo4j then 'RETURN 1 AS result'
when :memgraph then 'SHOW STORAGE INFO'
else 'RETURN 1'
end
perform_health_check_query(query)
end

result
rescue ConnectionError, ProtocolError => e
{ healthy: false, response_time_ms: nil, details: "Health check failed: #{e.message}" }
end
Expand Down Expand Up @@ -707,96 +702,15 @@ def default_version_info
}
end

# Performs Neo4j-specific health check using RETURN 1 (fallback from db.ping).
#
# @return [Hash] health check result
def perform_neo4j_health_check
start_time = Time.now

begin
write_message(Messaging::Run.new('RETURN 1 AS result', {}, {}), 'HEALTH_CHECK')
run_response = read_message

case run_response
when Messaging::Success
# Send PULL message to complete the transaction
write_message(Messaging::Pull.new({ 'n' => -1 }), 'PULL')

# Read messages until we get SUCCESS or FAILURE
response_time = nil
loop do
msg = read_message
case msg
when Messaging::Record
# Skip records, we just want to know if the query succeeded
next
when Messaging::Success
response_time = ((Time.now - start_time) * 1000).round(2)
return { healthy: true, response_time_ms: response_time, details: 'RETURN 1 succeeded' }
when Messaging::Failure
return { healthy: false, response_time_ms: nil, details: 'RETURN 1 failed with error' }
else
return { healthy: false, response_time_ms: nil, details: 'RETURN 1 unexpected response' }
end
end
else
{ healthy: false, response_time_ms: nil, details: 'RETURN 1 run failed' }
end
ensure
# Reset connection state after health check
reset!
end
end

# Performs Memgraph-specific health check using SHOW STORAGE INFO.
#
# @return [Hash] health check result
def perform_memgraph_health_check
start_time = Time.now

begin
write_message(Messaging::Run.new('SHOW STORAGE INFO', {}, {}), 'HEALTH_CHECK')
run_response = read_message

case run_response
when Messaging::Success
# Send PULL message to complete the transaction
write_message(Messaging::Pull.new({ 'n' => -1 }), 'PULL')

# Read messages until we get SUCCESS or FAILURE
response_time = nil
loop do
msg = read_message
case msg
when Messaging::Record
# Skip records, we just want to know if the query succeeded
next
when Messaging::Success
response_time = ((Time.now - start_time) * 1000).round(2)
return { healthy: true, response_time_ms: response_time, details: 'SHOW STORAGE INFO succeeded' }
when Messaging::Failure
return { healthy: false, response_time_ms: nil, details: 'SHOW STORAGE INFO failed with error' }
else
return { healthy: false, response_time_ms: nil, details: 'SHOW STORAGE INFO unexpected response' }
end
end
else
{ healthy: false, response_time_ms: nil, details: 'SHOW STORAGE INFO run failed' }
end
ensure
# Reset connection state after health check
reset!
end
end

# Performs generic health check using simple RETURN 1 query.
# Performs a health check by running the given query and draining the result.
#
# @param query [String] database-appropriate health check query
# @return [Hash] health check result
def perform_generic_health_check
def perform_health_check_query(query)
start_time = Time.now

begin
write_message(Messaging::Run.new('RETURN 1', {}, {}), 'HEALTH_CHECK')
write_message(Messaging::Run.new(query, {}, {}), 'HEALTH_CHECK')
run_response = read_message

case run_response
Expand All @@ -814,15 +728,15 @@ def perform_generic_health_check
next
when Messaging::Success
response_time = ((Time.now - start_time) * 1000).round(2)
return { healthy: true, response_time_ms: response_time, details: 'RETURN 1 succeeded' }
return { healthy: true, response_time_ms: response_time, details: "#{query} succeeded" }
when Messaging::Failure
return { healthy: false, response_time_ms: nil, details: 'RETURN 1 failed with error' }
return { healthy: false, response_time_ms: nil, details: "#{query} failed with error" }
else
return { healthy: false, response_time_ms: nil, details: 'RETURN 1 unexpected response' }
return { healthy: false, response_time_ms: nil, details: "#{query} unexpected response" }
end
end
else
{ healthy: false, response_time_ms: nil, details: 'RETURN 1 run failed' }
{ healthy: false, response_time_ms: nil, details: "#{query} run failed" }
end
ensure
# Reset connection state after health check
Expand Down
8 changes: 4 additions & 4 deletions lib/active_cypher/bolt/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ def initialize(uri:, adapter:, auth_token:, pool_size: DEFAULT_POOL_SIZE, secure
#
# @yieldparam session [Bolt::Session] The session to use
# @return [Object] The result of the block
def with_session(**kw, &block)
def with_session(**kw, &)
Sync do
_acquire_session(**kw, &block)
_acquire_session(**kw, &)
end
rescue Async::TimeoutError => e
raise ActiveCypher::ConnectionError, "Connection pool timeout: #{e.message}"
Expand All @@ -55,11 +55,11 @@ def with_session(**kw, &block)
#
# @yieldparam session [Bolt::Session] The session to use
# @return [Async::Task] A task that resolves to the block's result
def async_with_session(**kw, &block)
def async_with_session(**kw, &)
raise 'Cannot run async_with_session outside of an Async task' unless Async::Task.current?

Async do
_acquire_session(**kw, &block)
_acquire_session(**kw, &)
end
end

Expand Down
Loading