Module: Lich::Common::Settings
- Defined in:
- documented/common/settings.rb
Overview
Provides configuration settings for the Lich framework.
This module manages settings with support for logging and path navigation.
Defined Under Namespace
Classes: CircularReferenceError
Constant Summary collapse
- LOG_LEVEL_NONE =
Logging Configuration
0- LOG_LEVEL_ERROR =
1- LOG_LEVEL_INFO =
2- LOG_LEVEL_DEBUG =
3- DEFAULT_SCOPE =
":".freeze
- @@log_level =
Default: logging disabled
LOG_LEVEL_NONE- @@log_prefix =
"[SettingsModule]".freeze
Class Method Summary collapse
- .[](name) ⇒ Object
- .[]=(name, value) ⇒ Object
- ._log(level, prefix, message_proc) ⇒ Object
-
._reattach_live!(proxy) ⇒ Boolean
Reattaches the live proxy to the current script context.
- .auto ⇒ Object
- .auto=(_val) ⇒ Object
- .autoload ⇒ Object
- .clear ⇒ Object
-
.container?(value) ⇒ Boolean
Checks if the given value is a container (Hash or Array).
-
.current_script_settings(scope = DEFAULT_SCOPE, script_name: nil) ⇒ OpenStruct
Retrieves the current settings for the specified script and scope.
-
.get_log_level ⇒ Integer
Retrieves the current logging level for the Settings module.
-
.get_scoped_setting(scope_string, key_name, script_name: nil) ⇒ Object
Retrieves a setting value for a specific scope and key.
- .get_value_from_container(container, key) ⇒ Object
- .load ⇒ Object
- .method_missing(method, *args, &block) ⇒ Object
-
.navigate_to_path(create_missing = true, scope = DEFAULT_SCOPE) ⇒ Array<Object>
Navigates to the specified path within the current settings structure.
-
.refresh_data(scope = DEFAULT_SCOPE) ⇒ OpenStruct
Refreshes the settings data for the specified scope by clearing the cache.
- .reset_path_and_return(value) ⇒ Object
- .respond_to_missing?(method_name, include_private = false) ⇒ Boolean
-
.root_proxy_for(scope, script_name: Script.current.name) ⇒ SettingsProxy
Retrieves the root proxy for the specified scope and script name.
- .save ⇒ Object
- .save_all ⇒ Object
-
.save_proxy_changes(proxy) ⇒ void
Saves changes made to the specified proxy back to the database.
-
.save_to_database(data_to_save, scope = DEFAULT_SCOPE, script_name: nil) ⇒ void
Saves the specified data to the database for the given script and scope.
-
.set_log_level(level) ⇒ void
Sets the logging level for the Settings module.
-
.set_script_settings(scope = DEFAULT_SCOPE, name, value, script_name: nil) ⇒ void
Sets a specific setting for the current script and scope.
-
.to_h(scope = DEFAULT_SCOPE) ⇒ Object
Added scope to match to_hash for consistency if used directly.
-
.to_hash(scope = DEFAULT_SCOPE) ⇒ Hash
Converts the current settings for the specified scope to a Hash.
-
.wrap_value_if_container(value, scope, path_array, script_name: nil) ⇒ Object
Wraps a value in a proxy if it is a container (Hash or Array).
Class Method Details
.[](name) ⇒ Object
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'documented/common/settings.rb', line 481 def self.[](name) scope_to_use = DEFAULT_SCOPE _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "Settings.[]: name: #{name.inspect}, current_path: #{@path_navigator.path.inspect}, safe_nav: #{@safe_navigation_active}" }) if @path_navigator.path.empty? data_for_scope = current_script_settings(scope_to_use) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "Settings.[] (top-level): data_for_scope (DUP) (object_id: #{data_for_scope.object_id}): #{data_for_scope.inspect}" }) value = get_value_from_container(data_for_scope, name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "Settings.[] (top-level): value for '#{name}': #{value.inspect}" }) if value.nil? && !data_for_scope.is_a?(Array) && (!data_for_scope.is_a?(Hash) || !data_for_scope.key?(name)) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.[] (top-level): Key '#{name}' not found or value is nil. Activating safe_navigation." }) @safe_navigation_active = true end return reset_path_and_return(wrap_value_if_container(value, scope_to_use, [name])) else current_target, _ = navigate_to_path(false, scope_to_use) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "Settings.[] (path-based): current_target: #{current_target.inspect}" }) value = get_value_from_container(current_target, name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "Settings.[] (path-based): value for '#{name}': #{value.inspect}" }) new_path = @path_navigator.path + [name] if value.nil? && !current_target.is_a?(Array) && (!current_target.is_a?(Hash) || !current_target.key?(name)) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.[] (path-based): Key '#{name}' not found or value is nil in path. Activating safe_navigation." }) @safe_navigation_active = true end return reset_path_and_return(wrap_value_if_container(value, scope_to_use, new_path)) end end |
.[]=(name, value) ⇒ Object
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 |
# File 'documented/common/settings.rb', line 509 def self.[]=(name, value) scope_to_use = DEFAULT_SCOPE _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "Settings.[]=: name: #{name.inspect}, value: #{value.inspect}, current_path: #{@path_navigator.path.inspect}" }) @safe_navigation_active = false # Reset safe navigation on assignment if @path_navigator.path.empty? set_script_settings(scope_to_use, name, value) else target, root_settings = navigate_to_path(true, scope_to_use) if target && (target.is_a?(Hash) || target.is_a?(Array)) actual_value = value.is_a?(SettingsProxy) ? unwrap_proxies(value) : value target[name] = actual_value save_to_database(root_settings, scope_to_use) reset_path_and_return(value) else _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "Settings.[]=: Cannot assign to non-container or nil target at path #{@path_navigator.path.inspect}" }) reset_path_and_return(nil) end end end |
._log(level, prefix, message_proc) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'documented/common/settings.rb', line 58 def self._log(level, prefix, ) return unless Lich.respond_to?(:log) return unless level <= @@log_level level_str = case level when LOG_LEVEL_ERROR then "[ERROR]" when LOG_LEVEL_INFO then "[INFO]" when LOG_LEVEL_DEBUG then "[DEBUG]" else "[UNKNOWN]" end begin = .call Lich.log("#{prefix} #{level_str} #{}") rescue => e Lich.log("#{prefix} [ERROR] Logging failed: #{e.} - Original message proc: #{.source_location if .respond_to?(:source_location)}") end end |
._reattach_live!(proxy) ⇒ Boolean
Reattaches the live proxy to the current script context.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'documented/common/settings.rb', line 81 def self._reattach_live!(proxy) script_name = Script.current.name scope = proxy.scope path = proxy.path _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "reattach_live!: scope=#{scope.inspect} path=#{path.inspect}" }) @path_navigator.reset_path @path_navigator.set_path(path) live, _root = @path_navigator.navigate_to_path(script_name, true, scope) if live.nil? _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "reattach_live!: failed to resolve live target for path=#{path.inspect} scope=#{scope.inspect}" }) return false end # Swap the proxy onto the live object via SettingsProxy API (encapsulated) # Centralizes invariants/logging within SettingsProxy itself. proxy.rebind_to_live!(live) true end |
.auto ⇒ Object
616 617 618 619 620 |
# File 'documented/common/settings.rb', line 616 def self.auto Lich.deprecated('Settings.auto', 'not using, not applicable,', caller[0], fe_log: true) if Lich.respond_to?(:deprecated) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.auto called (legacy deprecated no-op)." }) nil end |
.auto=(_val) ⇒ Object
611 612 613 614 |
# File 'documented/common/settings.rb', line 611 def self.auto=(_val) Lich.deprecated('Settings.auto=(val)', 'not using, not applicable,', caller[0], fe_log: true) if Lich.respond_to?(:deprecated) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.auto= called (legacy deprecated no-op)." }) end |
.autoload ⇒ Object
622 623 624 625 626 |
# File 'documented/common/settings.rb', line 622 def self.autoload Lich.deprecated('Settings.autoload', 'not using, not applicable,', caller[0], fe_log: true) if Lich.respond_to?(:deprecated) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.autoload called (legacy deprecated no-op)." }) nil end |
.clear ⇒ Object
605 606 607 608 609 |
# File 'documented/common/settings.rb', line 605 def self.clear Lich.deprecated('Settings.clear', 'not using, not applicable,', caller[0], fe_log: true) if Lich.respond_to?(:deprecated) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.clear called (legacy deprecated no-op)." }) nil end |
.container?(value) ⇒ Boolean
Checks if the given value is a container (Hash or Array).
113 114 115 |
# File 'documented/common/settings.rb', line 113 def self.container?(value) value.is_a?(Hash) || value.is_a?(Array) end |
.current_script_settings(scope = DEFAULT_SCOPE, script_name: nil) ⇒ OpenStruct
Retrieves the current settings for the specified script and scope.
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'documented/common/settings.rb', line 352 def self.current_script_settings(scope = DEFAULT_SCOPE, script_name: nil) # Use provided script_name or fall back to Script.current.name script_name = script_name || Script.current.name cache_key = "#{script_name || ""}::#{scope}" # Use an empty string if script_name is nil _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "current_script_settings: Request for scope: #{scope.inspect}, cache_key: #{cache_key}" }) cached_data = @settings_cache[cache_key] if cached_data _log(LOG_LEVEL_INFO, @@log_prefix, -> { "current_script_settings: Cache hit for #{cache_key} (object_id: #{cached_data.object_id}). Returning DUP: #{cached_data.inspect}" }) return cached_data.dup else _log(LOG_LEVEL_INFO, @@log_prefix, -> { "current_script_settings: Cache miss for #{cache_key}. Loading from DB." }) settings = @db_adapter.get_settings(script_name, scope) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "current_script_settings: Loaded from DB (object_id: #{settings.object_id}): #{settings.inspect}" }) @settings_cache[cache_key] = settings _log(LOG_LEVEL_INFO, @@log_prefix, -> { "current_script_settings: Stored in cache (object_id: #{@settings_cache[cache_key].object_id}). Returning DUP." }) return settings.dup end end |
.get_log_level ⇒ Integer
Retrieves the current logging level for the Settings module.
54 55 56 |
# File 'documented/common/settings.rb', line 54 def self.get_log_level @@log_level end |
.get_scoped_setting(scope_string, key_name, script_name: nil) ⇒ Object
Retrieves a setting value for a specific scope and key.
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 |
# File 'documented/common/settings.rb', line 536 def self.get_scoped_setting(scope_string, key_name, script_name: nil) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "get_scoped_setting: scope: #{scope_string.inspect}, key: #{key_name.inspect}, script_name: #{script_name.inspect}" }) data_for_scope = current_script_settings(scope_string, script_name: script_name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "get_scoped_setting: data_for_scope (DUP) (object_id: #{data_for_scope.object_id}): #{data_for_scope.inspect}" }) value = get_value_from_container(data_for_scope, key_name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "get_scoped_setting: value for '#{key_name}': #{value.inspect}" }) if value.nil? && key_name key_absent_in_hash = data_for_scope.is_a?(Hash) && !data_for_scope.key?(key_name) key_invalid_for_array = data_for_scope.is_a?(Array) && (!key_name.is_a?(Integer) || key_name < 0 || key_name >= data_for_scope.length) if key_absent_in_hash || key_invalid_for_array || (data_for_scope.nil? || (data_for_scope.is_a?(Hash) && data_for_scope.empty?)) _log(Settings::LOG_LEVEL_INFO, @@log_prefix, -> { "get_scoped_setting: Key '#{key_name}' not found in scope '#{scope_string}'. Value will be nil, supporting '|| default' idiom." }) end end wrap_value_if_container(value, scope_string, key_name ? [key_name] : [], script_name: script_name) end |
.get_value_from_container(container, key) ⇒ Object
646 647 648 649 650 651 652 653 654 655 656 657 |
# File 'documented/common/settings.rb', line 646 def self.get_value_from_container(container, key) if container.is_a?(Hash) container[key] elsif container.is_a?(Array) && key.is_a?(Integer) container[key] elsif container.is_a?(Array) && !key.is_a?(Integer) _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "get_value_from_container: Attempted to access Array with non-Integer key: #{key.inspect}" }) nil else nil end end |
.load ⇒ Object
589 590 591 592 |
# File 'documented/common/settings.rb', line 589 def self.load _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.load called (legacy, aliasing to refresh_data)." }) refresh_data end |
.method_missing(method, *args, &block) ⇒ Object
628 629 630 631 632 633 634 635 636 637 638 639 640 |
# File 'documented/common/settings.rb', line 628 def self.method_missing(method, *args, &block) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "method_missing: method: #{method}, args: #{args.inspect}, path: #{@path_navigator.path.inspect}" }) if @safe_navigation_active && !@path_navigator.path.empty? if method.to_s.end_with?("=") _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "method_missing: Attempted assignment (#{method}) on a nil path due to safe navigation." }) return reset_path_and_return(nil) end return reset_path_and_return(nil) end _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "method_missing: Delegating to path_navigator: #{method}" }) @path_navigator.send(method, *args, &block) end |
.navigate_to_path(create_missing = true, scope = DEFAULT_SCOPE) ⇒ Array<Object>
Navigates to the specified path within the current settings structure.
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'documented/common/settings.rb', line 422 def self.navigate_to_path(create_missing = true, scope = DEFAULT_SCOPE) root_for_scope = current_script_settings(scope) return [root_for_scope, root_for_scope] if @path_navigator.path.empty? target = root_for_scope @path_navigator.path.each do |key| if target.is_a?(Hash) && target.key?(key) target = target[key] elsif target.is_a?(Array) && key.is_a?(Integer) && key >= 0 && key < target.length target = target[key] elsif create_missing && (target.is_a?(Hash) || target.is_a?(Array)) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "navigate_to_path: Creating missing segment '#{key}' in DUPPED structure for scope #{scope.inspect}" }) new_node = key.is_a?(Integer) ? [] : {} if target.is_a?(Hash) target[key] = new_node elsif target.is_a?(Array) && key.is_a?(Integer) target[key] = new_node end target = new_node else return [nil, root_for_scope] end end [target, root_for_scope] end |
.refresh_data(scope = DEFAULT_SCOPE) ⇒ OpenStruct
Refreshes the settings data for the specified scope by clearing the cache.
405 406 407 408 409 410 411 |
# File 'documented/common/settings.rb', line 405 def self.refresh_data(scope = DEFAULT_SCOPE) script_name = Script.current.name cache_key = "#{script_name || ""}::#{scope}" # Use an empty string if script_name is nil _log(LOG_LEVEL_INFO, @@log_prefix, -> { "refresh_data: Deleting cache for scope: #{scope.inspect}, cache_key: #{cache_key}" }) @settings_cache.delete(cache_key) current_script_settings(scope) end |
.reset_path_and_return(value) ⇒ Object
413 414 415 |
# File 'documented/common/settings.rb', line 413 def self.reset_path_and_return(value) @path_navigator.reset_path_and_return(value) end |
.respond_to_missing?(method_name, include_private = false) ⇒ Boolean
642 643 644 |
# File 'documented/common/settings.rb', line 642 def self.respond_to_missing?(method_name, include_private = false) @path_navigator.respond_to?(method_name, include_private) || super end |
.root_proxy_for(scope, script_name: Script.current.name) ⇒ SettingsProxy
Retrieves the root proxy for the specified scope and script name.
153 154 155 156 157 158 159 160 161 |
# File 'documented/common/settings.rb', line 153 def self.root_proxy_for(scope, script_name: Script.current.name) raise ArgumentError, "scope must be a non-empty String" if scope.nil? || scope.to_s.strip.empty? script_name ||= "" cache_key = "#{script_name}::#{scope}" root = @settings_cache[cache_key] ||= @db_adapter.get_settings(script_name, scope) SettingsProxy.new(self, scope, [], root, script_name: script_name) end |
.save ⇒ Object
584 585 586 587 |
# File 'documented/common/settings.rb', line 584 def self.save _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.save called (legacy no-op)." }) :noop end |
.save_all ⇒ Object
599 600 601 602 603 |
# File 'documented/common/settings.rb', line 599 def self.save_all Lich.deprecated('Settings.save_all', 'not using, not applicable,', caller[0], fe_log: true) if Lich.respond_to?(:deprecated) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.save_all called (legacy deprecated no-op)." }) nil end |
.save_proxy_changes(proxy) ⇒ void
This method returns an undefined value.
Saves changes made to the specified proxy back to the database.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'documented/common/settings.rb', line 167 def self.save_proxy_changes(proxy) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Initiated for proxy.scope: #{proxy.scope.inspect}, proxy.path: #{proxy.path.inspect}, proxy.target_object_id: #{proxy.target.object_id}" }) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: proxy.target data: #{proxy.target.inspect}" }) path = proxy.path scope = proxy.scope # Use proxy.script_name if available (for InstanceSettings), fall back to Script.current.name script_name = proxy.respond_to?(:script_name) && proxy.script_name ? proxy.script_name : Script.current.name cache_key = "#{script_name || ""}::#{scope}" # Local helper to keep cache in sync with the just-persisted root sync_cache = lambda do |root_obj| cached = @settings_cache[cache_key] if cached && !cached.equal?(root_obj) if cached.is_a?(Hash) && root_obj.is_a?(Hash) cached.replace(root_obj) elsif cached.is_a?(Array) && root_obj.is_a?(Array) cached.clear cached.concat(root_obj) else @settings_cache[cache_key] = root_obj end elsif cached.nil? @settings_cache[cache_key] = root_obj end _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Cache synchronized post-save for #{cache_key}" }) end # --- Refresh-before-save to prevent stale-cache overwrites --- # IMPORTANT: If proxy.target IS the cached object, we must NOT replace it # from the database, as that would wipe out the changes we're trying to save. # Only refresh when the proxy is detached or working with a different object. fresh_root = @db_adapter.get_settings(script_name, scope) cached = @settings_cache[cache_key] if cached # Skip refresh if proxy.target is the same object as cached - we'd wipe out our changes! if cached.equal?(proxy.target) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Skipping refresh - proxy.target IS the cached object (object_id: #{cached.object_id})" }) current_root_for_scope = cached elsif cached.is_a?(Hash) && fresh_root.is_a?(Hash) cached.replace(fresh_root) current_root_for_scope = cached elsif cached.is_a?(Array) && fresh_root.is_a?(Array) cached.clear cached.concat(fresh_root) current_root_for_scope = cached else @settings_cache[cache_key] = fresh_root current_root_for_scope = fresh_root end _log(LOG_LEVEL_INFO, @@log_prefix, -> { "save_proxy_changes: Cache state for #{cache_key} (object_id: #{current_root_for_scope.object_id}): #{current_root_for_scope.inspect}" }) else @settings_cache[cache_key] = fresh_root current_root_for_scope = fresh_root _log(LOG_LEVEL_INFO, @@log_prefix, -> { "save_proxy_changes: Cache populated from DB for #{cache_key} (object_id: #{current_root_for_scope.object_id}): #{current_root_for_scope.inspect}" }) end # ------------------------------------------------------------- # EMPTY PATH → Save *current root* (not proxy.target). Also covers detached "view" proxies. if path.empty? _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Empty path; saving CURRENT ROOT for scope #{scope.inspect}" }) unless current_root_for_scope.is_a?(Hash) || current_root_for_scope.is_a?(Array) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "save_proxy_changes: Root not a container; initializing {} for scope #{scope.inspect}" }) current_root_for_scope = {} @settings_cache[cache_key] = current_root_for_scope end if proxy.respond_to?(:detached?) && proxy.detached? _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Proxy is detached (view); persisting current root without copying view target." }) save_to_database(current_root_for_scope, scope, script_name: script_name) sync_cache.call(current_root_for_scope) return nil end # Root identity drift: sync proxy.target into cached root if different objects (same-type containers). if !current_root_for_scope.equal?(proxy.target) if proxy.target.is_a?(Hash) && current_root_for_scope.is_a?(Hash) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Root identity mismatch (cache #{current_root_for_scope.object_id} vs proxy #{proxy.target.object_id}); copying via Hash#replace" }) current_root_for_scope.replace(proxy.target) elsif proxy.target.is_a?(Array) && current_root_for_scope.is_a?(Array) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Root identity mismatch (Array); copying elements" }) current_root_for_scope.clear current_root_for_scope.concat(proxy.target) else _log(LOG_LEVEL_WARN, @@log_prefix, -> { "save_proxy_changes: Root/target type mismatch; persisting current root only (root=#{current_root_for_scope.class}, target=#{proxy.target.class})." }) end end save_to_database(current_root_for_scope, scope, script_name: script_name) sync_cache.call(current_root_for_scope) return nil end _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: script_name: #{script_name.inspect}, cache_key: #{cache_key}" }) # From here on, we’re saving into a nested path. Ensure root is a container. unless current_root_for_scope.is_a?(Hash) || current_root_for_scope.is_a?(Array) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "save_proxy_changes: Root not a container; initializing {} for scope #{scope.inspect}" }) current_root_for_scope = {} @settings_cache[cache_key] = current_root_for_scope end parent_path = path[0...-1] leaf_key = path.last # Pre-navigation diagnostics _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Navigation preflight — parent_path=#{parent_path.inspect} (#{parent_path.map { |s| s.class }.inspect}), leaf_key=#{leaf_key.inspect} (#{leaf_key.class}), root_class=#{current_root_for_scope.class}" }) # Navigate **within the freshly-loaded root** (do NOT re-fetch via PathNavigator here). begin parent = current_root_for_scope parent_path.each_with_index do |seg, idx| next_seg = parent_path[idx + 1] if parent.is_a?(Hash) unless parent.key?(seg) parent[seg] = next_seg.is_a?(Integer) ? [] : {} _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Created #{parent[seg].class} at #{parent_path[0..idx].inspect} for scope #{scope.inspect}" }) end parent = parent[seg] elsif parent.is_a?(Array) unless seg.is_a?(Integer) && seg >= 0 _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_proxy_changes: Non-integer or negative index #{seg.inspect} for Array at #{parent_path[0..idx].inspect} in scope #{scope.inspect}" }) return nil end if seg >= parent.length (parent.length..seg).each { parent << nil } _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Extended Array to index #{seg} at #{parent_path[0..idx].inspect} for scope #{scope.inspect}" }) end parent[seg] ||= (next_seg.is_a?(Integer) ? [] : {}) parent = parent[seg] else _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_proxy_changes: Parent is not a container at #{parent_path[0..idx].inspect} (#{parent.class}) for scope #{scope.inspect}" }) return nil end end rescue => e _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_proxy_changes: Local navigation raised #{e.class}: #{e.}. "\ "scope=#{scope.inspect}, script_name=#{script_name.inspect}, cache_key=#{cache_key}, "\ "parent_path=#{parent_path.inspect}, leaf_key=#{leaf_key.inspect}" }) bt = (e.backtrace || [])[0, 5] _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_proxy_changes: Backtrace (top 5): #{bt.join(' | ')}" }) return nil end _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Navigated/created parent (object_id: #{parent.object_id}, class=#{parent.class}): #{parent.inspect}" }) if parent.is_a?(Hash) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Setting Hash key #{leaf_key.inspect} with proxy.target (object_id: #{proxy.target.object_id})" }) parent[leaf_key] = proxy.target elsif parent.is_a?(Array) && leaf_key.is_a?(Integer) if leaf_key < 0 _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_proxy_changes: Negative array index #{leaf_key} not supported at path #{path.inspect} in scope #{scope.inspect}" }) return nil end if leaf_key >= parent.length (parent.length..leaf_key).each { parent << nil } _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Extended Array to index #{leaf_key} for parent at path #{parent_path.inspect}" }) end parent[leaf_key] = proxy.target _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: Set Array index #{leaf_key} with proxy.target (object_id: #{proxy.target.object_id})" }) else _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_proxy_changes: Cannot set value at path #{path.inspect} in scope #{scope.inspect}; "\ "parent_class=#{parent.class}, leaf_key_class=#{leaf_key.class}" }) return nil end _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_proxy_changes: root after update (object_id: #{current_root_for_scope.object_id}): #{current_root_for_scope.inspect}" }) save_to_database(current_root_for_scope, scope, script_name: script_name) sync_cache.call(current_root_for_scope) end |
.save_to_database(data_to_save, scope = DEFAULT_SCOPE, script_name: nil) ⇒ void
This method returns an undefined value.
Saves the specified data to the database for the given script and scope.
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'documented/common/settings.rb', line 378 def self.save_to_database(data_to_save, scope = DEFAULT_SCOPE, script_name: nil) # Use provided script_name or fall back to Script.current.name script_name = script_name || Script.current.name if script_name.nil? || script_name.empty? _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_to_database: Aborting save. script_name is nil or empty. Scope: #{scope.inspect}. Data will NOT be persisted." }) return nil # Explicitly return nil end cache_key = "#{script_name}::#{scope}" # script_name is guaranteed to be non-nil/non-empty here _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_to_database: Saving for script: '#{script_name}', scope: #{scope.inspect}, cache_key: #{cache_key}" }) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_to_database: Data BEFORE unwrap_proxies (object_id: #{data_to_save.object_id}): #{data_to_save.inspect}" }) unwrapped_settings = unwrap_proxies(data_to_save) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "save_to_database: Data AFTER unwrap_proxies (object_id: #{unwrapped_settings.object_id}): #{unwrapped_settings.inspect}" }) @db_adapter.save_settings(script_name, unwrapped_settings, scope) _log(LOG_LEVEL_INFO, @@log_prefix, -> { "save_to_database: Data saved to DB for script '#{script_name}'." }) @settings_cache[cache_key] = unwrapped_settings _log(LOG_LEVEL_INFO, @@log_prefix, -> { "save_to_database: Cache updated for #{cache_key} with saved data (object_id: #{@settings_cache[cache_key].object_id})." }) end |
.set_log_level(level) ⇒ void
This method returns an undefined value.
Sets the logging level for the Settings module.
39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'documented/common/settings.rb', line 39 def self.set_log_level(level) numeric_level = case level when :none, LOG_LEVEL_NONE then LOG_LEVEL_NONE when :error, LOG_LEVEL_ERROR then LOG_LEVEL_ERROR when :info, LOG_LEVEL_INFO then LOG_LEVEL_INFO when :debug, LOG_LEVEL_DEBUG then LOG_LEVEL_DEBUG else _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "Invalid log level specified: #{level.inspect}. Defaulting to NONE." }) LOG_LEVEL_NONE end @@log_level = numeric_level end |
.set_script_settings(scope = DEFAULT_SCOPE, name, value, script_name: nil) ⇒ void
This method returns an undefined value.
Sets a specific setting for the current script and scope.
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'documented/common/settings.rb', line 455 def self.set_script_settings(scope = DEFAULT_SCOPE, name, value, script_name: nil) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "set_script_settings: scope: #{scope.inspect}, name: #{name.inspect}, value: #{value.inspect}, script_name: #{script_name.inspect}, current_path: #{@path_navigator.path.inspect}" }) unwrapped_value = unwrap_proxies(value) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "set_script_settings: unwrapped_value: #{unwrapped_value.inspect}" }) current_root = current_script_settings(scope, script_name: script_name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "set_script_settings: current_root (DUP) for scope #{scope.inspect} (object_id: #{current_root.object_id}): #{current_root.inspect}" }) if @path_navigator.path.empty? _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "set_script_settings: Path is empty. Setting '#{name}' on current_root." }) current_root[name] = unwrapped_value save_to_database(current_root, scope, script_name: script_name) else if !@path_navigator.path.empty? _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "set_script_settings: WARNING: Called with non-empty path_navigator path: #{@path_navigator.path.inspect}. This is unusual for Char/GameSettings direct assignment." }) end if current_root.is_a?(Hash) current_root[name] = unwrapped_value save_to_database(current_root, scope, script_name: script_name) else _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "set_script_settings: current_root for scope #{scope.inspect} is not a Hash. Cannot set key '#{name}'. Root class: #{current_root.class}" }) end end reset_path_and_return(value) end |
.to_h(scope = DEFAULT_SCOPE) ⇒ Object
Added scope to match to_hash for consistency if used directly
594 595 596 597 |
# File 'documented/common/settings.rb', line 594 def self.to_h(scope = DEFAULT_SCOPE) # Added scope to match to_hash for consistency if used directly _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.to_h called (legacy, aliasing to to_hash)." }) self.to_hash(scope) end |
.to_hash(scope = DEFAULT_SCOPE) ⇒ Hash
Converts the current settings for the specified scope to a Hash.
576 577 578 579 580 581 582 |
# File 'documented/common/settings.rb', line 576 def self.to_hash(scope = DEFAULT_SCOPE) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "to_hash: scope: #{scope.inspect}" }) data = current_script_settings(scope) unwrapped_data = unwrap_proxies(data) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "to_hash: Returning unwrapped data (snapshot): #{unwrapped_data.inspect}" }) return unwrapped_data end |
.wrap_value_if_container(value, scope, path_array, script_name: nil) ⇒ Object
Wraps a value in a proxy if it is a container (Hash or Array).
561 562 563 564 565 566 567 568 569 570 |
# File 'documented/common/settings.rb', line 561 def self.wrap_value_if_container(value, scope, path_array, script_name: nil) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "wrap_value_if_container: value_class: #{value.class}, scope: #{scope.inspect}, path: #{path_array.inspect}, script_name: #{script_name.inspect}" }) if container?(value) proxy = SettingsProxy.new(self, scope, path_array, value, script_name: script_name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "wrap_value_if_container: Wrapped in proxy: #{proxy.inspect}" }) return proxy else return value end end |