@@ -14,9 +14,8 @@ class ValidationError < StandardError; end
1414 field :enable_banking_application_id , type : :string , default : ENV [ "ENABLE_BANKING_APPLICATION_ID" ]
1515 field :enable_banking_certificate , type : :text , default : ENV [ "ENABLE_BANKING_CERTIFICATE" ]
1616
17- # Single hash field for all dynamic provider credentials and other dynamic settings
18- # This allows unlimited dynamic fields without declaring them upfront
19- field :dynamic_fields , type : :hash , default : { }
17+ # Dynamic fields are now stored as individual entries with "dynamic:" prefix
18+ # This prevents race conditions and ensures each field is independently managed
2019
2120 # Onboarding and app settings
2221 ONBOARDING_STATES = %w[ open closed invite_only ] . freeze
@@ -53,16 +52,16 @@ def onboarding_state=(state)
5352 end
5453
5554 # Support dynamic field access via bracket notation
56- # First checks if it's a declared field, then falls back to dynamic_fields hash
55+ # First checks if it's a declared field, then falls back to individual dynamic entries
5756 def []( key )
5857 key_str = key . to_s
5958
6059 # Check if it's a declared field first
6160 if respond_to? ( key_str )
6261 public_send ( key_str )
6362 else
64- # Fall back to dynamic_fields hash
65- dynamic_fields [ key_str ]
63+ # Fall back to individual dynamic entry lookup
64+ find_by ( var : dynamic_key_name ( key_str ) ) &. value
6665 end
6766 end
6867
@@ -73,38 +72,50 @@ def []=(key, value)
7372 if respond_to? ( "#{ key_str } =" )
7473 public_send ( "#{ key_str } =" , value )
7574 else
76- # Otherwise, manage in dynamic_fields hash
77- current_dynamic = dynamic_fields . dup
75+ # Store as individual dynamic entry
76+ dynamic_key = dynamic_key_name ( key_str )
7877 if value . nil?
79- current_dynamic . delete ( key_str ) # treat nil as delete
78+ where ( var : dynamic_key ) . destroy_all
79+ clear_cache
8080 else
81- current_dynamic [ key_str ] = value
81+ # Use upsert for atomic insert/update to avoid race conditions
82+ upsert ( { var : dynamic_key , value : value . to_yaml } , unique_by : :var )
83+ clear_cache
8284 end
83- self . dynamic_fields = current_dynamic # persists & busts cache
8485 end
8586 end
8687
8788 # Check if a dynamic field exists (useful to distinguish nil value vs missing key)
8889 def key? ( key )
8990 key_str = key . to_s
90- respond_to? ( key_str ) || dynamic_fields . key? ( key_str )
91+ return true if respond_to? ( key_str )
92+
93+ # Check if dynamic entry exists
94+ where ( var : dynamic_key_name ( key_str ) ) . exists?
9195 end
9296
9397 # Delete a dynamic field
9498 def delete ( key )
9599 key_str = key . to_s
96100 return nil if respond_to? ( key_str ) # Can't delete declared fields
97101
98- current_dynamic = dynamic_fields . dup
99- value = current_dynamic . delete ( key_str )
100- self . dynamic_fields = current_dynamic
102+ dynamic_key = dynamic_key_name ( key_str )
103+ value = self [ key_str ]
104+ where ( var : dynamic_key ) . destroy_all
105+ clear_cache
101106 value
102107 end
103108
104109 # List all dynamic field keys (excludes declared fields)
105110 def dynamic_keys
106- dynamic_fields . keys
111+ where ( "var LIKE ?" , "dynamic:%" ) . pluck ( :var ) . map { | var | var . sub ( /^dynamic:/ , "" ) }
107112 end
113+
114+ private
115+
116+ def dynamic_key_name ( key_str )
117+ "dynamic:#{ key_str } "
118+ end
108119 end
109120
110121 # Validates OpenAI configuration requires model when custom URI base is set
0 commit comments