FIX Read-Modify-Write issue with dynamic fields (#290)

* FIX Read-Modify-Write issue with dynamic fields

Ruby caching + queueing updates might cause some dynamic fields to not be updated.

* Small fix for true dynamic fields

* Add suite of tests for new settings page

* Treat nil values as deletions to keep the hash clean

* Test fix
This commit is contained in:
soky srm
2025-11-05 12:19:33 +01:00
committed by GitHub
parent b4e4c37834
commit 164fe5375d
3 changed files with 382 additions and 5 deletions

View File

@@ -28,6 +28,9 @@ class Settings::ProvidersController < ApplicationController
updated_fields = []
# This hash will store only the updates for dynamic (non-declared) fields
dynamic_updates = {}
# Perform all updates within a transaction for consistency
Setting.transaction do
provider_params.each do |param_key, param_value|
@@ -44,10 +47,42 @@ class Settings::ProvidersController < ApplicationController
next
end
# Set the value using dynamic hash-style access
Setting[field.setting_key] = value
key_str = field.setting_key.to_s
# Check if the setting is a declared field in setting.rb
# Use method_defined? to check if the setter actually exists on the singleton class,
# not just respond_to? which returns true for dynamic fields due to respond_to_missing?
if Setting.singleton_class.method_defined?("#{key_str}=")
# If it's a declared field (e.g., openai_model), set it directly.
# This is safe and uses the proper setter.
Setting.public_send("#{key_str}=", value)
else
# If it's a dynamic field, add it to our batch hash
# to avoid the Read-Modify-Write conflict.
dynamic_updates[key_str] = value
end
updated_fields << param_key
end
# Now, if we have any dynamic updates, apply them all at once
if dynamic_updates.any?
# 1. READ the current hash once
current_dynamic = Setting.dynamic_fields.dup
# 2. MODIFY by merging changes
# Treat nil values as deletions to keep the hash clean
dynamic_updates.each do |key, value|
if value.nil?
current_dynamic.delete(key)
else
current_dynamic[key] = value
end
end
# 3. WRITE the complete, merged hash back once
Setting.dynamic_fields = current_dynamic
end
end
if updated_fields.any?
@@ -61,6 +96,9 @@ class Settings::ProvidersController < ApplicationController
rescue => error
Rails.logger.error("Failed to update provider settings: #{error.message}")
flash.now[:alert] = "Failed to update provider settings: #{error.message}"
# Set @provider_configurations so the view can render properly
Provider::Factory.ensure_adapters_loaded
@provider_configurations = Provider::ConfigurationRegistry.all
render :show, status: :unprocessable_entity
end