Class: UltraSettings::Configuration

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/ultra_settings/configuration.rb

Constant Summary collapse

ALLOWED_NAME_PATTERN =
/\A[a-z_][a-zA-Z0-9_]*\z/
ALLOWED_TYPES =
[:string, :symbol, :integer, :float, :boolean, :datetime, :array].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeConfiguration

Returns a new instance of Configuration.



493
494
495
496
497
498
# File 'lib/ultra_settings/configuration.rb', line 493

def initialize
  @ultra_settings_mutex = Mutex.new
  @ultra_settings_memoized_values = {}
  @ultra_settings_override_values = {}
  @ultra_settings_yaml_config = nil
end

Class Method Details

.configuration_filePathname?

Get the YAML file path.

Returns:

  • (Pathname, nil)


181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/ultra_settings/configuration.rb', line 181

def configuration_file
  unless defined?(@configuration_file)
    default_file = default_configuration_file
    return nil if default_file.nil?

    @configuration_file = default_configuration_file
  end
  return nil unless @configuration_file

  path = @configuration_file
  if path.relative? && yaml_config_path
    path = yaml_config_path.join(path)
  end
  path.expand_path
end

.configuration_file=(value) ⇒ void

This method returns an undefined value.

Override the default YAML config path. By default this will be the file matching the underscored name of the class in the configuration directory (i.e. MyServiceConfiguration has a default config path of "my_service.yml").

Parameters:

  • value (String, Pathname, false, nil)


172
173
174
175
176
# File 'lib/ultra_settings/configuration.rb', line 172

def configuration_file=(value)
  value = nil if value == false
  value = Pathname.new(value) if value.is_a?(String)
  @configuration_file = value
end

.descendant_configurationsArray<Class>

Get all descendant configuration classes (subclasses and their subclasses, recursively).

Note that in Rails development mode this list can include stale classes from previous code reloads; callers that display the results should filter out classes that no longer resolve to a defined constant (see UltraSettings.configurations).

Returns:

  • (Array<Class>)

    All classes that inherit from this class.



385
386
387
388
# File 'lib/ultra_settings/configuration.rb', line 385

def descendant_configurations
  descendants = DESCENDANTS_MUTEX.synchronize { (@descendants || []).dup }
  descendants.flat_map { |subclass| [subclass] + subclass.descendant_configurations }
end

.description(text = nil) ⇒ void

This method returns an undefined value.

Set a description for the configuration. This is optional. It will be displayed in the web UI if provided. On large projects with many configurations, this can help identify the purpose of each configuration.

Parameters:

  • text (String) (defaults to: nil)

    The description text.



25
26
27
28
# File 'lib/ultra_settings/configuration.rb', line 25

def description(text = nil)
  @description = text.to_s.strip unless text.nil?
  @description
end

.env_var_delimiterString

Get the environment variable delimiter.

Returns:

  • (String)


253
254
255
# File 'lib/ultra_settings/configuration.rb', line 253

def env_var_delimiter
  get_inheritable_class_attribute(:@env_var_delimiter, "_")
end

.env_var_delimiter=(value) ⇒ Object

Set the environment variable delimiter used to construct the environment variable name for a field. By default this is an underscore.

Parameters:

  • value (String)


246
247
248
# File 'lib/ultra_settings/configuration.rb', line 246

def env_var_delimiter=(value)
  set_inheritable_class_attribute(:@env_var_delimiter, value.to_s)
end

.env_var_prefixString

Get the environment variable prefix.

Returns:

  • (String)


138
139
140
141
142
143
# File 'lib/ultra_settings/configuration.rb', line 138

def env_var_prefix
  unless defined?(@env_var_prefix)
    @env_var_prefix = default_env_var_prefix
  end
  @env_var_prefix
end

.env_var_prefix=(value) ⇒ void

This method returns an undefined value.

Override the default environment variable prefix. By default this wil be the underscored name of the class plus an underscore (i.e. MyServiceConfiguration has a prefix of "MY_SERVICE_").

Parameters:

  • value (String)


131
132
133
# File 'lib/ultra_settings/configuration.rb', line 131

def env_var_prefix=(value)
  @env_var_prefix = value&.to_s
end

.env_var_upcase=(value) ⇒ void

This method returns an undefined value.

Set to true to upcase the environment variable name for a field. This is true by default.

Parameters:

  • value (Boolean)


278
279
280
# File 'lib/ultra_settings/configuration.rb', line 278

def env_var_upcase=(value)
  set_inheritable_class_attribute(:@env_var_upcase, !!value)
end

.env_var_upcase?Boolean

Check if the environment variable name for a field should be upcased.

Returns:

  • (Boolean)


285
286
287
# File 'lib/ultra_settings/configuration.rb', line 285

def env_var_upcase?
  get_inheritable_class_attribute(:@env_var_upcase, true)
end

.environment_variables_disabled=(value) ⇒ void

This method returns an undefined value.

Set to true to disable loading configuration from environment variables.

Parameters:

  • value (Boolean)


201
202
203
# File 'lib/ultra_settings/configuration.rb', line 201

def environment_variables_disabled=(value)
  set_inheritable_class_attribute(:@environment_variables_disabled, !!value)
end

.environment_variables_disabled?Boolean

Check if loading configuration from environment variables is disabled.

Returns:

  • (Boolean)


208
209
210
# File 'lib/ultra_settings/configuration.rb', line 208

def environment_variables_disabled?
  get_inheritable_class_attribute(:@environment_variables_disabled, false)
end

.field(name, type: :string, description: nil, default: nil, default_if: nil, static: nil, secret: nil, runtime_setting: nil, env_var: nil, yaml_key: nil) ⇒ void

This method returns an undefined value.

Define a field on the configuration. This will create a getter method for the field. The field value will be read from the environment, runtime settings, or a YAML file and coerced to the specified type. Empty strings will be converted to nil.

Parameters:

  • name (Symbol, String)

    The name of the field.

  • type (Symbol) (defaults to: :string)

    The type of the field. Valid types are :string, :symbol, :integer, :float, :boolean, :datetime, and :array. The default type is :string. The :array type will return an array of strings.

  • description (String) (defaults to: nil)

    A description of the field.

  • default (Object) (defaults to: nil)

    The default value of the field.

  • default_if (Proc, Symbol) (defaults to: nil)

    A proc that returns true if the default value should be used. By default, the default value will be used if the field evaluates to nil. You can also set this to a symbol with the name of an instance method to call.

  • static (Boolean) (defaults to: nil)

    If true, the field value should never be changed. This is useful for fields that are used at startup to set static values in the application. Static field cannot be read from runtime settings.

  • secret (Boolean, Proc) (defaults to: nil)

    If true, the field value will be obscured in the output of to_hash. If a proc is provided, it will be called to determine if the field is secret.

  • runtime_setting (String, Symbol, Boolean) (defaults to: nil)

    The name of the runtime setting to use for the field. By default this will be the underscored name of the class plus a dot plus the field name (i.e. MyServiceConfiguration#foo becomes "my_service.foo"). If set to false, runtime settings will be ignored for this field. This can be set to true to use the default name.

  • env_var (String, Symbol, Boolean) (defaults to: nil)

    The name of the environment variable to use for the field. By default this will be the underscored name of the class plus an underscore plus the field name all in uppercase (i.e. MyServiceConfiguration#foo becomes "MY_SERVICE_FOO"). If set to false, environment variables will be ignored for this field. This can be set to true to use the default name.

  • yaml_key (String, Symbol, Boolean) (defaults to: nil)

    The name of the YAML key to use for the field. By default this is the name of the field. If set to false, YAML configuration will be ignored for this field. This can be set to true to use the default name.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ultra_settings/configuration.rb', line 60

def field(name, type: :string, description: nil, default: nil, default_if: nil, static: nil, secret: nil, runtime_setting: nil, env_var: nil, yaml_key: nil)
  name = name.to_s
  type = type.to_sym
  static = !!static
  secret = lambda { fields_secret_by_default? } if secret.nil?

  unless name.match?(ALLOWED_NAME_PATTERN)
    raise ArgumentError.new("Invalid name: #{name.inspect}")
  end

  unless ALLOWED_TYPES.include?(type)
    raise ArgumentError.new("Invalid type: #{type.inspect}")
  end

  unless default_if.nil? || default_if.is_a?(Proc) || default_if.is_a?(Symbol)
    raise ArgumentError.new("default_if must be a Proc or Symbol")
  end

  defined_fields[name] = Field.new(
    name: name,
    type: type,
    description: description,
    default: default,
    default_if: default_if,
    env_var: construct_env_var(name, env_var),
    runtime_setting: construct_runtime_setting(name, runtime_setting),
    yaml_key: construct_yaml_key(name, yaml_key),
    static: static,
    secret: secret
  )

  caller_location = caller_locations(1, 1).first
  class_eval <<~RUBY, caller_location.path, caller_location.lineno # rubocop:disable Security/Eval, Style/EvalWithLocation
    def #{name}
      __get_value__(#{name.inspect})
    end
  RUBY

  if type == :boolean
    alias_method :"#{name}?", name
  end
end

.fieldsArray<UltraSettings::Field>

List of the defined fields for the configuration.

Returns:



106
107
108
# File 'lib/ultra_settings/configuration.rb', line 106

def fields
  defined_fields.values
end

.fields_secret_by_default=(value) ⇒ void

This method returns an undefined value.

Sets the default value for the secret property of fields. Individual fields can still override this value by explicitly setting the secret property. By default, fields are considered secret.

Parameters:

  • value (Boolean)


346
347
348
# File 'lib/ultra_settings/configuration.rb', line 346

def fields_secret_by_default=(value)
  set_inheritable_class_attribute(:@fields_secret_by_default, !!value)
end

.fields_secret_by_default?Boolean

Check if fields are considered secret by default.

Returns:

  • (Boolean)


353
354
355
# File 'lib/ultra_settings/configuration.rb', line 353

def fields_secret_by_default?
  get_inheritable_class_attribute(:@fields_secret_by_default, true)
end

.include_field?(name) ⇒ Boolean

Check if the field is defined on the configuration.

Parameters:

  • name (Symbol, String)

    The name of the field.

Returns:

  • (Boolean)


114
115
116
117
118
119
120
121
122
123
# File 'lib/ultra_settings/configuration.rb', line 114

def include_field?(name)
  name = name.to_s
  return true if defined_fields.include?(name)

  if superclass < Configuration
    superclass.include_field?(name)
  else
    false
  end
end

.load_yaml_configHash

Load the YAML file for this configuration and return the values for the current environment.

Returns:

  • (Hash)


370
371
372
373
374
375
# File 'lib/ultra_settings/configuration.rb', line 370

def load_yaml_config
  return nil unless configuration_file
  return nil unless configuration_file.exist? && configuration_file.file?

  YamlConfig.new(configuration_file, yaml_config_env).to_h
end

.override!(values, &block) ⇒ Object

Override field values within a block.

Parameters:

  • values (Hash<Symbol, Object>)

    ] List of fields with the values they should return within the block.

Returns:

  • (Object)

    The value returned by the block.



362
363
364
# File 'lib/ultra_settings/configuration.rb', line 362

def override!(values, &block)
  instance.override!(values, &block)
end

.runtime_setting_delimiterString

Get the runtime setting delimiter.

Returns:

  • (String)


269
270
271
# File 'lib/ultra_settings/configuration.rb', line 269

def runtime_setting_delimiter
  get_inheritable_class_attribute(:@runtime_setting_delimiter, ".")
end

.runtime_setting_delimiter=(value) ⇒ void

This method returns an undefined value.

Set the runtime setting delimiter used to construct the runtime setting name for a field. By default this is a dot.

Parameters:

  • value (String)


262
263
264
# File 'lib/ultra_settings/configuration.rb', line 262

def runtime_setting_delimiter=(value)
  set_inheritable_class_attribute(:@runtime_setting_delimiter, value.to_s)
end

.runtime_setting_prefixString

Get the runtime setting prefix.

Returns:

  • (String)


158
159
160
161
162
163
# File 'lib/ultra_settings/configuration.rb', line 158

def runtime_setting_prefix
  unless defined?(@runtime_setting_prefix)
    @runtime_setting_prefix = default_runtime_setting_prefix
  end
  @runtime_setting_prefix
end

.runtime_setting_prefix=(value) ⇒ void

This method returns an undefined value.

Override the default runtime setting prefix. By default this wil be the underscored name of the class plus a dot (i.e. MyServiceConfiguration has a prefix of "my_service.").

Parameters:

  • value (String)


151
152
153
# File 'lib/ultra_settings/configuration.rb', line 151

def runtime_setting_prefix=(value)
  @runtime_setting_prefix = value&.to_s
end

.runtime_setting_upcase=(value) ⇒ void

This method returns an undefined value.

Set to true to upcase the runtime setting name for a field. This is false by default.

Parameters:

  • value (Boolean)


294
295
296
# File 'lib/ultra_settings/configuration.rb', line 294

def runtime_setting_upcase=(value)
  set_inheritable_class_attribute(:@runtime_setting_upcase, !!value)
end

.runtime_setting_upcase?Boolean

Check if the runtime setting name for a field should be upcased.

Returns:

  • (Boolean)


301
302
303
# File 'lib/ultra_settings/configuration.rb', line 301

def runtime_setting_upcase?
  get_inheritable_class_attribute(:@runtime_setting_upcase, false)
end

.runtime_settings_disabled=(value) ⇒ void

This method returns an undefined value.

Set to true to disable loading configuration from runtime settings.

Parameters:

  • value (Boolean)


216
217
218
# File 'lib/ultra_settings/configuration.rb', line 216

def runtime_settings_disabled=(value)
  set_inheritable_class_attribute(:@runtime_settings_disabled, !!value)
end

.runtime_settings_disabled?Boolean

Check if loading configuration from runtime settings is disabled.

Returns:

  • (Boolean)


223
224
225
# File 'lib/ultra_settings/configuration.rb', line 223

def runtime_settings_disabled?
  get_inheritable_class_attribute(:@runtime_settings_disabled, false)
end

.yaml_config_disabled=(value) ⇒ void

This method returns an undefined value.

Set to true to disable loading configuration from YAML files.

Parameters:

  • value (Boolean)


231
232
233
# File 'lib/ultra_settings/configuration.rb', line 231

def yaml_config_disabled=(value)
  set_inheritable_class_attribute(:@yaml_config_disabled, !!value)
end

.yaml_config_disabled?Boolean

Check if loading configuration from YAML files is disabled.

Returns:

  • (Boolean)


238
239
240
# File 'lib/ultra_settings/configuration.rb', line 238

def yaml_config_disabled?
  get_inheritable_class_attribute(:@yaml_config_disabled, false) || configuration_file.nil?
end

.yaml_config_envString

Get the environment namespace used in YAML file name.

Returns:

  • (String)


336
337
338
# File 'lib/ultra_settings/configuration.rb', line 336

def yaml_config_env
  get_inheritable_class_attribute(:@yaml_config_env, "development")
end

.yaml_config_env=(value) ⇒ void

This method returns an undefined value.

Set the environment namespace used in YAML file name. By default this is "development". Settings from the specific environment hash in the YAML file will be merged with base settings specified in the "shared" hash.

Parameters:

  • value (String)


329
330
331
# File 'lib/ultra_settings/configuration.rb', line 329

def yaml_config_env=(value)
  set_inheritable_class_attribute(:@yaml_config_env, value)
end

.yaml_config_pathPathname?

Get the directory where YAML files will be loaded from.

Returns:

  • (Pathname, nil)


319
320
321
# File 'lib/ultra_settings/configuration.rb', line 319

def yaml_config_path
  get_inheritable_class_attribute(:@yaml_config_path, nil)
end

.yaml_config_path=(value) ⇒ void

This method returns an undefined value.

Set the directory where YAML files will be loaded from. By default this is the current working directory.

Parameters:

  • value (String, Pathname)


310
311
312
313
314
# File 'lib/ultra_settings/configuration.rb', line 310

def yaml_config_path=(value)
  value = Pathname.new(value) if value.is_a?(String)
  value = value.expand_path if value&.relative?
  set_inheritable_class_attribute(:@yaml_config_path, value)
end

Instance Method Details

#[](name) ⇒ Object



500
501
502
# File 'lib/ultra_settings/configuration.rb', line 500

def [](name)
  send(name.to_s) if include?(name)
end

#__available_sources__(name) ⇒ Array<Symbol>

Returns an array of the available data sources for the field.

Parameters:

  • name (String, Symbol)

    the name of the field.

Returns:

  • (Array<Symbol>)

    The available sources (:env, :settings, :yaml, :default).

Raises:

  • (ArgumentError)


574
575
576
577
578
579
580
581
582
583
584
# File 'lib/ultra_settings/configuration.rb', line 574

def __available_sources__(name)
  field = self.class.send(:defined_fields)[name.to_s]
  raise ArgumentError.new("Unknown field: #{name.inspect}") unless field

  sources = []
  sources << :env if field.env_var
  sources << :settings if __runtime_setting_allowed?(field)
  sources << :yaml if field.yaml_key && self.class.configuration_file
  sources << :default unless field.default.nil?
  sources
end

#__source__(name) ⇒ Symbol?

Get the current source for the field.

Parameters:

  • name (String, Symbol)

    the name of the field.

Returns:

  • (Symbol, nil)

    The source of the value (:env, :settings, :yaml, or :default).

Raises:

  • (ArgumentError)


539
540
541
542
543
544
545
# File 'lib/ultra_settings/configuration.rb', line 539

def __source__(name)
  field = self.class.send(:defined_fields)[name.to_s]
  raise ArgumentError.new("Unknown field: #{name.inspect}") unless field

  source = field.source(env: ENV, settings: UltraSettings.__runtime_settings__, yaml_config: __yaml_config__)
  source || :default
end

#__to_hash__Hash

Output the current state of the configuration as a hash. If the field is marked as a secret, then the value will be a secure hash of the value instead of the value itself.

The intent of this method is to provide a serializable value that captures the current state of the configuration without exposing any secrets. You could, for instance, use the output to compare the configuration of you application between two different environments.

Returns:

  • (Hash)


594
595
596
597
598
599
600
601
602
603
604
# File 'lib/ultra_settings/configuration.rb', line 594

def __to_hash__
  payload = {}
  self.class.fields.each do |field|
    value = self[field.name]
    if field.secret? && !value.nil?
      value = "securehash:#{Digest::MD5.hexdigest(Digest::SHA256.hexdigest(value.to_s))}"
    end
    payload[field.name] = value
  end
  payload
end

#__value_from_source__(name, source) ⇒ Object

Get the value of the field from the specified source.

Parameters:

  • name (String, Symbol)

    the name of the field.

  • source (Symbol)

    the source of the value (:env, :settings, :yaml, or :default).

Returns:

  • (Object)

    The value of the field.

Raises:

  • (ArgumentError)


552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/ultra_settings/configuration.rb', line 552

def __value_from_source__(name, source)
  field = self.class.send(:defined_fields)[name.to_s]
  raise ArgumentError.new("Unknown field: #{name.inspect}") unless field

  case source
  when :env
    field.value(env: ENV)
  when :settings
    field.value(settings: UltraSettings.__runtime_settings__)
  when :yaml
    field.value(yaml_config: __yaml_config__)
  when :default
    field.default
  else
    raise ArgumentError.new("Unknown source: #{source.inspect}")
  end
end

#include?(name) ⇒ Boolean

Returns:

  • (Boolean)


504
505
506
# File 'lib/ultra_settings/configuration.rb', line 504

def include?(name)
  self.class.include_field?(name.to_s)
end

#override!(values, &block) ⇒ Object



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# File 'lib/ultra_settings/configuration.rb', line 508

def override!(values, &block)
  thread_id = Thread.current.object_id
  save_val = @ultra_settings_mutex.synchronize { @ultra_settings_override_values[thread_id] }

  temp_values = (save_val || {}).dup
  values.each do |key, value|
    temp_values[key.to_s] = value
  end

  begin
    @ultra_settings_mutex.synchronize do
      @ultra_settings_override_values[thread_id] = temp_values
    end
    yield
  ensure
    @ultra_settings_mutex.synchronize do
      if save_val.nil?
        # Remove the key entirely so the hash doesn't accumulate an entry
        # for every thread that has ever used override!.
        @ultra_settings_override_values.delete(thread_id)
      else
        @ultra_settings_override_values[thread_id] = save_val
      end
    end
  end
end