Module: Lich::Common::Settings
- Defined in:
- documented/common/settings.rb
Overview
Provides a configuration management system for the Lich project. This module handles settings storage, retrieval, and logging.
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
Retrieves a setting by name for the current script and scope.
-
.[]=(name, value) ⇒ void
Sets a value for a specific setting by name for the current script and scope.
-
._log(level, prefix, message_proc) ⇒ void
Internal logging method message_proc is a lambda/proc to delay string construction Internal logging method for the Settings module.
-
._reattach_live!(proxy) ⇒ Boolean
Internal non-destructive correction method (issue from Lich 5.12.6) non-destructive methods (.sort) cannot own a proxy that requires update (.push) Reattaches a proxy to a live object based on the current script context.
-
.auto ⇒ nil
Deprecated no-op method for getting auto configuration.
-
.auto=(_val) ⇒ nil
Deprecated no-op method for setting auto configuration.
-
.autoload ⇒ nil
Deprecated no-op method for autoloading settings.
-
.clear ⇒ nil
Deprecated no-op method for clearing settings.
-
.container?(value) ⇒ Boolean
Checks if the given value is a container (Hash or Array).
-
.current_script_settings(scope = DEFAULT_SCOPE) ⇒ Object
Retrieves the current settings for the active script and scope.
-
.get_log_level ⇒ Integer
Retrieves the current logging level for the Settings module.
-
.get_scoped_setting(scope_string, key_name) ⇒ Object
Retrieves a setting value for a specific scope and key.
-
.get_value_from_container(container, key) ⇒ Object
Retrieves a value from a container (Hash or Array) based on the key.
-
.load ⇒ void
Legacy method for loading settings; aliases to refresh_data.
-
.method_missing(method, *args, &block) ⇒ Object
Handles missing methods by delegating to the path navigator.
-
.navigate_to_path(create_missing = true, scope = DEFAULT_SCOPE) ⇒ Array
Navigates to a specific path within the current settings structure.
-
.refresh_data(scope = DEFAULT_SCOPE) ⇒ Object
Refreshes the settings data for the specified scope, clearing the cache.
-
.reset_path_and_return(value) ⇒ Object
Resets the path navigator and returns the specified value.
-
.respond_to_missing?(method_name, include_private = false) ⇒ Boolean
Checks if the Settings module responds to a missing method.
-
.root_proxy_for(scope, script_name: Script.current.name) ⇒ SettingsProxy
Creates a root-level SettingsProxy for the given scope.
-
.save ⇒ Symbol
Legacy Support Methods Legacy method for saving settings; does nothing.
-
.save_all ⇒ nil
Deprecated No-Op Methods from Original Deprecated no-op method for saving all settings.
-
.save_proxy_changes(proxy) ⇒ void
Saves changes made to a SettingsProxy back to the database.
-
.save_to_database(data_to_save, scope = DEFAULT_SCOPE) ⇒ void
Saves the provided data to the database for the current script and scope.
-
.set_log_level(level) ⇒ Integer
Sets the logging level for the Settings module.
-
.set_script_settings(scope = DEFAULT_SCOPE, name, value) ⇒ void
Sets a specific setting for the current script and scope.
-
.to_h(scope = DEFAULT_SCOPE) ⇒ Hash
Legacy method for converting settings to hash; aliases to to_hash.
-
.to_hash(scope = DEFAULT_SCOPE) ⇒ Hash
Converts the current settings to a hash representation.
-
.wrap_value_if_container(value, scope, path_array) ⇒ Object
Wraps a value in a SettingsProxy if it is a container (Hash or Array).
Class Method Details
.[](name) ⇒ Object
Retrieves a setting by name for the current script and scope.
522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
# File 'documented/common/settings.rb', line 522 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) ⇒ void
This method returns an undefined value.
Sets a value for a specific setting by name for the current script and scope.
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
# File 'documented/common/settings.rb', line 556 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) ⇒ void
This method is intended for internal use only.
This method returns an undefined value.
Internal logging method message_proc is a lambda/proc to delay string construction Internal logging method for the Settings module.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'documented/common/settings.rb', line 72 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
Internal non-destructive correction method (issue from Lich 5.12.6) non-destructive methods (.sort) cannot own a proxy that requires update (.push) Reattaches a proxy to a live object based on the current script context.
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'documented/common/settings.rb', line 98 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 ⇒ nil
Deprecated no-op method for getting auto configuration.
696 697 698 699 700 |
# File 'documented/common/settings.rb', line 696 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) ⇒ nil
Deprecated no-op method for setting auto configuration.
687 688 689 690 |
# File 'documented/common/settings.rb', line 687 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 ⇒ nil
Deprecated no-op method for autoloading settings.
706 707 708 709 710 |
# File 'documented/common/settings.rb', line 706 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 ⇒ nil
Deprecated no-op method for clearing settings.
676 677 678 679 680 |
# File 'documented/common/settings.rb', line 676 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).
131 132 133 |
# File 'documented/common/settings.rb', line 131 def self.container?(value) value.is_a?(Hash) || value.is_a?(Array) end |
.current_script_settings(scope = DEFAULT_SCOPE) ⇒ Object
Retrieves the current settings for the active script and scope.
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'documented/common/settings.rb', line 383 def self.current_script_settings(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_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.
60 61 62 |
# File 'documented/common/settings.rb', line 60 def self.get_log_level @@log_level end |
.get_scoped_setting(scope_string, key_name) ⇒ Object
Retrieves a setting value for a specific scope and key.
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 |
# File 'documented/common/settings.rb', line 583 def self.get_scoped_setting(scope_string, key_name) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "get_scoped_setting: scope: #{scope_string.inspect}, key: #{key_name.inspect}" }) data_for_scope = current_script_settings(scope_string) _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] : []) end |
.get_value_from_container(container, key) ⇒ Object
Retrieves a value from a container (Hash or Array) based on the key.
747 748 749 750 751 752 753 754 755 756 757 758 |
# File 'documented/common/settings.rb', line 747 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 ⇒ void
This method returns an undefined value.
Legacy method for loading settings; aliases to refresh_data.
646 647 648 649 |
# File 'documented/common/settings.rb', line 646 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
Handles missing methods by delegating to the path navigator.
719 720 721 722 723 724 725 726 727 728 729 730 731 |
# File 'documented/common/settings.rb', line 719 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
Navigates to a specific path within the current settings structure.
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 |
# File 'documented/common/settings.rb', line 458 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) ⇒ Object
Refreshes the settings data for the specified scope, clearing the cache.
435 436 437 438 439 440 441 |
# File 'documented/common/settings.rb', line 435 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
Resets the path navigator and returns the specified value.
448 449 450 |
# File 'documented/common/settings.rb', line 448 def self.reset_path_and_return(value) @path_navigator.reset_path_and_return(value) end |
.respond_to_missing?(method_name, include_private = false) ⇒ Boolean
Checks if the Settings module responds to a missing method.
737 738 739 |
# File 'documented/common/settings.rb', line 737 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
Creates a root-level SettingsProxy for the given scope.
This factory ensures the proxy directly targets the cached root object for the (script_name, scope) pair. By using the cached root, it avoids “identity drift” bugs where the proxy’s @target differs from the object being persisted by save_proxy_changes.
191 192 193 194 195 196 197 198 199 |
# File 'documented/common/settings.rb', line 191 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) end |
.save ⇒ Symbol
Legacy Support Methods Legacy method for saving settings; does nothing.
637 638 639 640 |
# File 'documented/common/settings.rb', line 637 def self.save _log(LOG_LEVEL_INFO, @@log_prefix, -> { "Settings.save called (legacy no-op)." }) :noop end |
.save_all ⇒ nil
Deprecated No-Op Methods from Original Deprecated no-op method for saving all settings.
666 667 668 669 670 |
# File 'documented/common/settings.rb', line 666 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 a SettingsProxy back to the database.
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 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 |
# File 'documented/common/settings.rb', line 206 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 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.object_id != root_obj.object_id 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 --- fresh_root = @db_adapter.get_settings(script_name, scope) cached = @settings_cache[cache_key] if cached if 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 refreshed from DB 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) 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.object_id != proxy.target.object_id 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) 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) sync_cache.call(current_root_for_scope) end |
.save_to_database(data_to_save, scope = DEFAULT_SCOPE) ⇒ void
This method returns an undefined value.
Saves the provided data to the database for the current script and scope.
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 |
# File 'documented/common/settings.rb', line 408 def self.save_to_database(data_to_save, scope = DEFAULT_SCOPE) script_name = Script.current.name if script_name.nil? || script_name.empty? _log(LOG_LEVEL_ERROR, @@log_prefix, -> { "save_to_database: Aborting save. Script.current.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) ⇒ Integer
Sets the logging level for the Settings module.
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'documented/common/settings.rb', line 43 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) ⇒ void
This method returns an undefined value.
Sets a specific setting for the current script and scope.
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 |
# File 'documented/common/settings.rb', line 491 def self.set_script_settings(scope = DEFAULT_SCOPE, name, value) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "set_script_settings: scope: #{scope.inspect}, name: #{name.inspect}, value: #{value.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) _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) 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) 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) ⇒ Hash
Legacy method for converting settings to hash; aliases to to_hash.
656 657 658 659 |
# File 'documented/common/settings.rb', line 656 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 to a hash representation.
624 625 626 627 628 629 630 |
# File 'documented/common/settings.rb', line 624 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) ⇒ Object
Wraps a value in a SettingsProxy if it is a container (Hash or Array).
608 609 610 611 612 613 614 615 616 617 |
# File 'documented/common/settings.rb', line 608 def self.wrap_value_if_container(value, scope, path_array) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "wrap_value_if_container: value_class: #{value.class}, scope: #{scope.inspect}, path: #{path_array.inspect}" }) if container?(value) proxy = SettingsProxy.new(self, scope, path_array, value) _log(LOG_LEVEL_DEBUG, @@log_prefix, -> { "wrap_value_if_container: Wrapped in proxy: #{proxy.inspect}" }) return proxy else return value end end |