Module: Lich::Common::Settings

Defined in:
lib/common/settings.rb

Defined Under Namespace

Classes: CircularReferenceError

Class Method Summary collapse

Class Method Details

.[](name) ⇒ Object?

Retrieves a value from the script settings.

Examples:

Settings[:setting_name]

Parameters:

  • name (String)

    the name of the setting to retrieve

Returns:

  • (Object, nil)

    the value of the setting or nil if not found



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
# File 'lib/common/settings.rb', line 257

def self.[](name)
  if @path_navigator.path.empty?
    # Top-level access
    value = current_script_settings[name]

    if value.nil?
      # For nil values, return nil but set up for safe navigation
      # This allows Settings[:non_existent][:deeper] to return nil without errors
      @safe_navigation_active = true
      return nil
    else
      @safe_navigation_active = false
      return wrap_value_if_container(value, [name])
    end
  else
    # Check if we're in safe navigation mode (previous access returned nil)
    if @safe_navigation_active
      @path_navigator.reset_path
      return nil
    end

    # Normal nested access
    target, _ = navigate_to_path(false) # Don't create missing paths

    # If target is nil, return nil and activate safe navigation
    if target.nil?
      @path_navigator.reset_path
      @safe_navigation_active = true
      return nil
    end

    # Access the requested key
    if container?(target)
      value = get_value_from_container(target, name)

      if value.nil?
        @path_navigator.reset_path
        @safe_navigation_active = true
        return nil
      else
        @safe_navigation_active = false
        new_path = @path_navigator.path.dup
        new_path << name
        @path_navigator.reset_path
        return wrap_value_if_container(value, new_path)
      end
    else
      # Reset path if target is not a container
      @path_navigator.reset_path
      @safe_navigation_active = true
      return nil
    end
  end
end

.[]=(name, value) ⇒ void

This method returns an undefined value.

Sets a value in the script settings.

Examples:

Settings[:setting_name] = "value"

Parameters:

  • name (String)

    the name of the setting to set

  • value (Object)

    the value to assign to the setting



351
352
353
# File 'lib/common/settings.rb', line 351

def self.[]=(name, value)
  set_script_settings(name, value)
end

.autoNilClass

Deprecated.

This method is no longer applicable.

Marks the method as deprecated and logs a warning.

Examples:

Settings.auto

Returns:



555
556
557
558
# File 'lib/common/settings.rb', line 555

def Settings.auto
  Lich.deprecated('Settings.auto', 'not using, not applicable,', caller[0], fe_log: true)
  nil
end

.auto=(_val) ⇒ NilClass

Deprecated.

This method is no longer applicable.

Marks the method as deprecated and logs a warning.

Examples:

Settings.auto = true

Parameters:

  • _val (Object)

    the value to set (not used).

Returns:

  • (NilClass)

    does not return a value.



545
546
547
# File 'lib/common/settings.rb', line 545

def Settings.auto=(_val)
  Lich.deprecated('Settings.auto=(val)', 'not using, not applicable,', caller[0], fe_log: true)
end

.autoloadNilClass

Deprecated.

This method is no longer applicable.

Marks the method as deprecated and logs a warning.

Examples:

Settings.autoload

Returns:



566
567
568
569
# File 'lib/common/settings.rb', line 566

def Settings.autoload
  Lich.deprecated('Settings.autoload', 'not using, not applicable,', caller[0], fe_log: true)
  nil
end

.clearNilClass

Deprecated.

This method is no longer applicable.

Marks the method as deprecated and logs a warning.

Examples:

Settings.clear

Returns:



533
534
535
536
# File 'lib/common/settings.rb', line 533

def Settings.clear
  Lich.deprecated('Settings.clear', 'not using, not applicable,', caller[0], fe_log: true)
  nil
end

.container?(value) ⇒ Boolean

Checks if a value is a container (Hash or Array).

Examples:

Settings.container?([]) # => true

Parameters:

  • value (Object)

    the value to check

Returns:

  • (Boolean)

    true if the value is a container, false otherwise



33
34
35
# File 'lib/common/settings.rb', line 33

def self.container?(value)
  value.is_a?(Hash) || value.is_a?(Array)
end

.current_script_settings(scope = ":") ⇒ Hash

Retrieves the current script settings from the database or cache.

Examples:

Settings.current_script_settings

Parameters:

  • scope (String) (defaults to: ":")

    the scope of the settings (default: “:”)

Returns:

  • (Hash)

    the current script settings



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/common/settings.rb', line 120

def self.current_script_settings(scope = ":")
  script_name = Script.current.name
  cache_key = "#{script_name}::#{scope}"

  # Check cache first
  return @settings_cache[cache_key] if @settings_cache[cache_key]

  # Get from database and update cache
  settings = @db_adapter.get_settings(script_name, scope)
  @settings_cache[cache_key] = settings
  settings
end

.empty?Boolean

Checks if the settings are empty.

Examples:

Settings.empty?

Returns:

  • (Boolean)

    true if settings are empty, false otherwise



479
480
481
482
483
484
485
486
# File 'lib/common/settings.rb', line 479

def self.empty?
  return false if @path_navigator.path.empty?

  target, _ = navigate_to_path(false)
  return reset_path_and_return(true) if target.nil?

  reset_path_and_return(target.empty?)
end

.get_value_from_container(container, key) ⇒ Object?

Helper method to get a value from a container.

Examples:

Settings.get_value_from_container(some_hash, :key)

Parameters:

  • container (Hash, Array)

    the container to retrieve the value from

  • key (String, Integer)

    the key or index of the value to retrieve

Returns:

  • (Object, nil)

    the value if found, nil otherwise



319
320
321
322
323
324
325
326
327
# File 'lib/common/settings.rb', line 319

def self.get_value_from_container(container, key)
  if container.is_a?(Hash) && container.key?(key)
    container[key]
  elsif container.is_a?(Array) && key.is_a?(Integer) && key < container.length
    container[key]
  else
    nil
  end
end

.handle_method_result(result) ⇒ SettingsProxy, Object

Helper method to handle results of destructive methods.

Examples:

Settings.handle_method_result(some_result)

Parameters:

  • result (Object)

    the result of the method call

Returns:

  • (SettingsProxy, Object)

    wrapped result if it’s a container, otherwise the original result



419
420
421
422
423
424
425
426
427
428
429
# File 'lib/common/settings.rb', line 419

def self.handle_method_result(result)
  path = @path_navigator.path.dup
  @path_navigator.reset_path

  if result.is_a?(Hash) || result.is_a?(Array)
    # For container results, wrap in a new proxy with current path
    SettingsProxy.new(self, path, result)
  else
    result
  end
end

.handle_non_destructive_result(result) ⇒ SettingsProxy, Object

Helper method to handle results of non-destructive methods.

Examples:

Settings.handle_non_destructive_result(some_result)

Parameters:

  • result (Object)

    the result of the method call

Returns:

  • (SettingsProxy, Object)

    wrapped result if it’s a container, otherwise the original result



401
402
403
404
405
406
407
408
409
410
411
# File 'lib/common/settings.rb', line 401

def self.handle_non_destructive_result(result)
  # Reset path immediately
  @path_navigator.reset_path

  if result.is_a?(Hash) || result.is_a?(Array)
    # For container results, wrap in a new proxy with empty path
    SettingsProxy.new(self, [], result)
  else
    result
  end
end

.include?(item) ⇒ Boolean

Checks if an item is included in the settings.

Examples:

Settings.include?(some_item)

Parameters:

  • item (Object)

    the item to check for inclusion

Returns:

  • (Boolean)

    true if the item is included, false otherwise



494
495
496
497
498
499
500
501
502
503
# File 'lib/common/settings.rb', line 494

def self.include?(item)
  return false if @path_navigator.path.empty?

  target, _ = navigate_to_path(false)
  return reset_path_and_return(false) if target.nil?

  # Unwrap item before checking inclusion
  unwrapped_item = unwrap_proxies(item)
  reset_path_and_return(target.is_a?(Array) ? target.include?(unwrapped_item) : false)
end

.loadHash

Loads the settings, aliased to refresh_data for backwards compatibility.

Examples:

Settings.load

Returns:

  • (Hash)

    the refreshed settings



510
511
512
# File 'lib/common/settings.rb', line 510

def self.load # pulled from Deprecated calls to alias to refresh_data()
  refresh_data()
end

.method_missing(method, *args, &block) ⇒ Object

Handles missing methods in the context of path navigation.

Examples:

Settings.some_missing_method

Parameters:

  • method (Symbol)

    the name of the missing method

  • args (Array)

    the arguments passed to the method

  • block (Proc)

    an optional block

Returns:

  • (Object)

    the result of the method call or raises NoMethodError



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/common/settings.rb', line 363

def self.method_missing(method, *args, &block)
  # Only handle method_missing if we're in a path context
  return super if @path_navigator.path.empty?

  # Navigate to the current path
  target, current = navigate_to_path(true)

  # Handle the method call
  if target.respond_to?(method)
    # For non-destructive methods, operate on a duplicate to avoid modifying original
    if SettingsProxy::NON_DESTRUCTIVE_METHODS.include?(method)
      # Create a duplicate of the target for non-destructive operations
      target_dup = target.dup
      result = target_dup.send(method, *args, &block)

      # Return the result without saving changes
      return handle_non_destructive_result(result)
    else
      # For destructive methods, operate on the original and save changes
      # Unwrap arguments if they are SettingsProxy instances
      unwrapped_args = args.map { |arg| unwrap_proxies(arg) }
      result = target.send(method, *unwrapped_args, &block)
      save_to_database(current)
      return handle_method_result(result)
    end
  else
    # Method not supported
    reset_path_and_return(nil)
    super
  end
end

Navigates to a specified path in the settings.

Examples:

Settings.navigate_to_path

Parameters:

  • create_missing (Boolean) (defaults to: true)

    whether to create missing paths (default: true)

  • scope (String) (defaults to: ":")

    the scope of the settings (default: “:”)

Returns:

  • (Array)

    an array containing the target and root settings



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
# File 'lib/common/settings.rb', line 187

def self.navigate_to_path(create_missing = true, scope = ":")
  script_name = Script.current.name
  cache_key = "#{script_name}::#{scope}"

  # Use cached settings if available
  if @settings_cache[cache_key]
    root = @settings_cache[cache_key]
    return [root, root] if @path_navigator.path.empty?

    target = root
    @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 < target.length
        target = target[key]
      elsif create_missing
        # Path doesn't exist yet, create it
        target[key] = key.is_a?(Integer) ? [] : {}
        target = target[key]
      else
        # Path doesn't exist and we're not creating it
        return [nil, root]
      end
    end

    [target, root]
  else
    # If not in cache, use the PathNavigator's method which reads from DB
    @path_navigator.navigate_to_path(script_name, create_missing, scope)
  end
end

.refresh_data(scope = ":") ⇒ Hash

Refreshes the settings data for the current script.

Examples:

Settings.refresh_data

Parameters:

  • scope (String) (defaults to: ":")

    the scope of the settings (default: “:”)

Returns:

  • (Hash)

    the refreshed settings



160
161
162
163
164
165
166
167
168
# File 'lib/common/settings.rb', line 160

def self.refresh_data(scope = ":")
  # Requests made directly to this method want a refreshed set of data.
  # Aliased to Settings.load for backwards compatibility.
  script_name = Script.current.name
  cache_key = "#{script_name}::#{scope}"
  @settings_cache.delete(cache_key) if @settings_cache.has_key?(cache_key)

  current_script_settings(scope)
end

.reset_path_and_return(value) ⇒ Object

Resets the path navigator and returns the given value.

Examples:

Settings.reset_path_and_return(some_value)

Parameters:

  • value (Object)

    the value to return

Returns:

  • (Object)

    the original value



176
177
178
# File 'lib/common/settings.rb', line 176

def self.reset_path_and_return(value)
  @path_navigator.reset_path_and_return(value)
end

.respond_to_missing?(method, include_private = false) ⇒ Boolean

Checks if a method is missing and responds accordingly.

Examples:

Settings.respond_to_missing?(:some_method)

Parameters:

  • method (Symbol)

    the name of the method

  • include_private (Boolean) (defaults to: false)

    whether to include private methods (default: false)

Returns:

  • (Boolean)

    true if the method is handled, false otherwise



438
439
440
441
442
# File 'lib/common/settings.rb', line 438

def self.respond_to_missing?(method, include_private = false)
  return true if !@path_navigator.path.empty?

  super
end

.savevoid

This method returns an undefined value.

No operation for saving settings.

Examples:

Settings.save


470
471
472
# File 'lib/common/settings.rb', line 470

def self.save
  # :noop
end

.save_allNilClass

Deprecated.

This method is no longer applicable.

Marks the method as deprecated and logs a warning.

Examples:

Settings.save_all

Returns:



522
523
524
525
# File 'lib/common/settings.rb', line 522

def Settings.save_all
  Lich.deprecated('Settings.save_all', 'not using, not applicable,', caller[0], fe_log: true)
  nil
end

.save_proxy_changes(proxy) ⇒ void

This method returns an undefined value.

Save changes from a proxy back to the database.

Examples:

Settings.save_proxy_changes(some_proxy)

Parameters:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/common/settings.rb', line 86

def self.save_proxy_changes(proxy)
  path = proxy.path
  return if path.empty?

  # Get the root settings hash from cache or database
  script_name = Script.current.name
  scope = ":"
  cache_key = "#{script_name}::#{scope}"

  # Use cached settings if available
  current = @settings_cache[cache_key] || current_script_settings(scope)

  # Navigate to the parent container
  parent = current
  parent_path = path[0...-1]

  parent_path.each do |key|
    parent = parent[key]
  end

  # Update the value at the final location with the unwrapped target
  parent[path.last] = proxy.target

  # Update cache and save to database (save_to_database will handle further unwrapping if needed)
  @settings_cache[cache_key] = current
  save_to_database(current, scope)
end

.save_to_database(current, scope = ":") ⇒ void

This method returns an undefined value.

Saves the current settings to the database.

Examples:

Settings.save_to_database(current_settings)

Parameters:

  • current (Hash)

    the current settings to save

  • scope (String) (defaults to: ":")

    the scope of the settings (default: “:”)



140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/common/settings.rb', line 140

def self.save_to_database(current, scope = ":")
  script_name = Script.current.name
  cache_key = "#{script_name}::#{scope}"

  # Recursively unwrap any SettingsProxy instances before saving
  unwrapped_settings = unwrap_proxies(current)

  # Pass the unwrapped settings to the database adapter
  @db_adapter.save_settings(script_name, unwrapped_settings, scope)

  # Expire cache key to force reload of saved values
  @settings_cache.delete(cache_key) if @settings_cache.has_key?(cache_key)
end

.set_script_settings(scope = ":", name, value) ⇒ Object

Sets a value in the script settings.

Examples:

Settings.set_script_settings(":", "setting_name", "value")

Parameters:

  • scope (String) (defaults to: ":")

    the scope of the settings (default: “:”)

  • name (String)

    the name of the setting to set

  • value (Object)

    the value to assign to the setting

Returns:

  • (Object)

    the original value/proxy as per convention



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/common/settings.rb', line 227

def self.set_script_settings(scope = ":", name, value)
  # Unwrap the value before assigning it to prevent proxies from entering the structure
  unwrapped_value = unwrap_proxies(value)

  if @path_navigator.path.empty?
    # Direct assignment to top-level key
    current = current_script_settings(scope)
    current[name] = unwrapped_value
    save_to_database(current, scope)
  else
    # Navigate to the correct location in the hash
    target, current = navigate_to_path(true, scope)

    # Set the unwrapped value at the final location
    target[name] = unwrapped_value

    # Save the updated hash to the database
    save_to_database(current, scope)
  end

  # Reset path after setting
  reset_path_and_return(value) # Return original value/proxy as per convention
end

.to_hHash

Returns the unwrapped hash of current script settings.

Examples:

Settings.to_h

Returns:

  • (Hash)

    the unwrapped settings



449
450
451
452
# File 'lib/common/settings.rb', line 449

def self.to_h
  # Return unwrapped hash
  unwrap_proxies(current_script_settings)
end

.to_hash(scope = ":") ⇒ Hash

Returns the unwrapped hash of current script settings for a given scope.

Examples:

Settings.to_hash(":")

Parameters:

  • scope (String) (defaults to: ":")

    the scope of the settings (default: “:”)

Returns:

  • (Hash)

    the unwrapped settings



460
461
462
463
# File 'lib/common/settings.rb', line 460

def self.to_hash(scope = ":")
  # Return unwrapped hash
  unwrap_proxies(current_script_settings(scope))
end

.wrap_value_if_container(value, path) ⇒ SettingsProxy, Object

Helper method to wrap a value in a proxy if it’s a container.

Examples:

Settings.wrap_value_if_container(some_value, some_path)

Parameters:

  • value (Object)

    the value to wrap

  • path (Array)

    the path to the value

Returns:

  • (SettingsProxy, Object)

    the wrapped value if it’s a container, otherwise the original value



336
337
338
339
340
341
342
# File 'lib/common/settings.rb', line 336

def self.wrap_value_if_container(value, path)
  if container?(value)
    SettingsProxy.new(self, path, value)
  else
    value
  end
end