ultra_settings 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec11cc8661b191dbb102deec0798f47ef11495feb8675f2f5b83e3e5d752855f
4
- data.tar.gz: e3f729687eaad31be948aa8f7b4818f25fb842cec8261058aa1a9f62616639c6
3
+ metadata.gz: f70521135220d74fe5ef4fbc6261c9d5b8a93f200f898af0fead2ebb7d31cbfd
4
+ data.tar.gz: 42330a1a294bee7ab53c61f37b56cd0b5b6f309326f1809bfd012cbfcd67fa58
5
5
  SHA512:
6
- metadata.gz: 455632dc476fc7ed73a70d784d97de612db55c1c2650e31b0896727f9747c8e4ddc3a2bfcdf9c2b190180c4f3d157aca427b75abf68c6e5f312b1251d9f24fc5
7
- data.tar.gz: 83e7686584d1ea62747d693c17f9f43908d70a5f5b45541d9f129b2e33e92521b0ce0c87d4c8ec10e3416afd186fefee93f281a6bfa0871c8b56e741d1fc44f6
6
+ metadata.gz: aab2c06121a67e227f1628d1dd3a22590e0bb174f132506840112278a5c09484e57dd6bc176ddbb35d576388c29bb5f6da462473f1c5ebf76724fe8677312ed2
7
+ data.tar.gz: f7e9bd08a2958def30780e0ef966d8b90321d3f9c1bf469677b7c51ab8b512e867bf0e1fc1d910d72f24c9f71818a34050b3a2dbfdc2c5adb9afa4a3e048948a
data/CHANGELOG.md CHANGED
@@ -4,17 +4,24 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.7.0
8
+
9
+ ### Added
10
+
11
+ - Added new setting to indicate if the runtime settings engine is secure. If the engine is marked as not secure, then runtime settings will be disabled for all fields marked as secret. This protects sensitive information from being exposed in the web UI or through the API. The default value is `true` to maintain backwards compatibility.
12
+
7
13
  ## 2.6.1
8
14
 
9
15
  ### Added
10
16
 
11
17
  - Show icons on the web UI that open a dialog with the current value for each data source.
18
+ - Added support for passing the field description in `UltraSettings.runtime_settings_url` using the `${description}` placeholder in the URL.
12
19
 
13
20
  ## 2.6.0
14
21
 
15
22
  ### Added
16
23
 
17
- - Added support for passing the type in `UltraSettings.runtime_settings_url` as `${type}` in the URL.
24
+ - Added support for passing the type in `UltraSettings.runtime_settings_url` using the `${type}` placeholder in the URL.
18
25
 
19
26
  ### Changed
20
27
 
data/README.md CHANGED
@@ -81,7 +81,6 @@ class MyServiceConfiguration < UltraSettings::Configuration
81
81
  field :auth_token,
82
82
  type: :string,
83
83
  env_var: "MY_SERVICE_TOKEN",
84
- runtime_setting: false,
85
84
  yaml_key: false,
86
85
  description: "Bearer token for accessing the service",
87
86
  secret: true
@@ -114,7 +113,7 @@ You can customize the behavior of each field using various options:
114
113
 
115
114
  - `:default_if` - Provides a condition for when the default should be used. This should be a Proc or the name of a method within the class. Useful for ensuring values meet specific constraints. This can provide protection from misconfiguration that can break the application. In the above example, the default value for `timeout` will be used if the value is less than or equal to 0.
116
115
 
117
- - `:secret` - Marks the field as secret. Secret fields are not displayed in the web UI. By default, all fields are considered secret to avoid accidentally exposing sensitive values. You can change this default behavior by setting `fields_secret_by_default` to `false` either globally or per configuration.
116
+ - `:secret` - Marks the field as secret. Secret fields are not displayed in the web UI. By default, all fields are considered secret to avoid accidentally exposing sensitive values. You can change this default behavior by setting `fields_secret_by_default` to `false` either globally or per configuration. Additionaly, if you set `UltraSettings.runtime_settings_secure` to false, then runtime settings will be disabled on secret fields.
118
117
 
119
118
  - `:env_var` - Overrides the environment variable name used to populate the field. This is useful if the variable name does not follow the conventional pattern. Set this to `false` to disable loading the field from an environment variable.
120
119
 
@@ -172,6 +171,9 @@ UltraSettings.runtime_settings = RedisRuntimeSettings.new
172
171
 
173
172
  The runtime settings implementation may also define an `array` method that takes a single parameter to return an array value. If this method is not implemented, then array values must be returned as single line CSV strings.
174
173
 
174
+ > [!TIP]
175
+ > If your runtime settings implementation does not securely store values, you should set `UltraSettings.runtime_settings_secure` to `false`. This will disable runtime settings on fields marked as secret to prevent leaking sensitive information.
176
+
175
177
  #### Using the `super_settings` gem
176
178
 
177
179
  There is a companion gem [super_settings](https://github.com/bdurand/super_settings) that can be used as a drop in implementation for the runtime settings. You just need to set the runtime settings to the `SuperSettings` object.
@@ -211,7 +213,7 @@ You can customize the behavior of runtime setting names with the following optio
211
213
 
212
214
  - **Disabling Runtime Settings:** You can disable runtime settings as a default source for fields by setting `runtime_settings_disabled` to `true` in your configuration class. You can disable runtime settings on individual fields by setting `runtime_setting` on the field to `false`.
213
215
 
214
- - **Editing Links** You can specify a URL for editing runtime settings from the web UI by setting `UltraSettings.runtime_settings_url` to the desired URL. This will add links to the runtime settings in the web UI. You can use the placeholders `${name}` and `${type}` in the URL which will be replaced with the name and type of the runtime setting, respectively. If you are using the `super_settings` gem for runtime settings, then you can target a setting by adding `#edit=${name}&type=${type}` to the root URL where `super_settings` is mounted.
216
+ - **Editing Links** You can specify a URL for editing runtime settings from the web UI by setting `UltraSettings.runtime_settings_url` to the desired URL. This will add links to the runtime settings in the web UI. You can use the placeholders `${name}`, `${type}`, and `${description}` in the URL which will be replaced with the name, type, and description of the field respectively. If you are using the `super_settings` gem for runtime settings, then you can target a setting by adding `#edit=${name}&type=${type}&description=${description}` to the root URL where `super_settings` is mounted.
215
217
 
216
218
  If a setting value cannot be loaded from the runtime settings, then it's value will attempt to be loaded from a YAML file.
217
219
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.6.1
1
+ 2.7.0
@@ -73,7 +73,7 @@
73
73
  <% if source == :settings %>
74
74
  <span class="ultra-settings-source-indicator">Currently active</span>
75
75
  <% end %>
76
- <% edit_url = UltraSettings.runtime_settings_url(field.runtime_setting, field.type) %>
76
+ <% edit_url = UltraSettings.runtime_settings_url(name: field.runtime_setting, type: field.type, description: field.description) %>
77
77
  <% if edit_url %>
78
78
  <a href="<%= html_escape(edit_url) %>" class="ultra-settings-edit-link" title="Edit <%= html_escape(field.runtime_setting) %>">
79
79
  <%= edit_icon %>
@@ -15,23 +15,37 @@ module UltraSettings
15
15
  class ApplicationView
16
16
  attr_reader :css
17
17
 
18
+ # Initialize the application view with a color scheme.
19
+ #
20
+ # @param color_scheme [Symbol] The color scheme to use (:light, :dark, or :system).
18
21
  def initialize(color_scheme: :light)
19
22
  @css = application_css(color_scheme)
20
23
  @css = @css.html_safe if @css.respond_to?(:html_safe)
21
24
  end
22
25
 
26
+ # Render the HTML for the configuration settings UI.
27
+ #
28
+ # @param select_class [String] CSS class for the select element.
29
+ # @param table_class [String] CSS class for the table element (for backwards compatibility).
30
+ # @return [String] The rendered HTML.
23
31
  def render(select_class: "ultra-settings-select", table_class: "")
24
32
  html = ViewHelper.erb_template("index.html.erb").result(binding)
25
33
  html = html.html_safe if html.respond_to?(:html_safe)
26
34
  html
27
35
  end
28
36
 
37
+ # Generate an HTML style tag with the CSS for the view.
38
+ #
39
+ # @return [String] The HTML style tag with CSS.
29
40
  def style_tag
30
41
  tag = "<style type=\"text/css\">\n#{css}\n</style>"
31
42
  tag = tag.html_safe if tag.respond_to?(:html_safe)
32
43
  tag
33
44
  end
34
45
 
46
+ # Convert the view to a string by rendering it.
47
+ #
48
+ # @return [String] The rendered HTML.
35
49
  def to_s
36
50
  render
37
51
  end
@@ -93,11 +93,13 @@ module UltraSettings
93
93
  time
94
94
  end
95
95
 
96
+ # @param value [Object] The value to check.
96
97
  # @return [Boolean] true if the value is a numeric type or a string representing a number.
97
98
  def numeric?(value)
98
99
  value.is_a?(Numeric) || (value.is_a?(String) && value.to_s.match?(NUMERIC_REGEX))
99
100
  end
100
101
 
102
+ # @param value [Object] The value to check.
101
103
  # @return [Boolean] true if the value is nil or empty.
102
104
  def blank?(value)
103
105
  return true if value.nil?
@@ -109,6 +111,7 @@ module UltraSettings
109
111
  end
110
112
  end
111
113
 
114
+ # @param value [Object] The value to check.
112
115
  # @return [Boolean] true if the value is not nil and not empty.
113
116
  def present?(value)
114
117
  !blank?(value)
@@ -1,16 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UltraSettings
4
- # Helper module for setting up a class to use the config methods
4
+ # Helper module for setting up a class to use the config methods.
5
5
  #
6
- # Usage:
7
- # class TestClass
8
- # extend UltraSettings::ConfigHelper
9
- # configuration_class TestConfiguration
10
- # end
11
- # TestClass.config => TestConfiguration.instance
12
- # TestClass.new.config => TestConfiguration.instance
6
+ # @example
7
+ # class TestClass
8
+ # extend UltraSettings::ConfigHelper
9
+ # configuration_class TestConfiguration
10
+ # end
11
+ # TestClass.config # => TestConfiguration.instance
12
+ # TestClass.new.config # => TestConfiguration.instance
13
13
  module ConfigHelper
14
+ # Define the configuration class and create config methods.
15
+ #
16
+ # @param config_class [Class] The configuration class to use.
17
+ # @return [void]
14
18
  def configuration_class(config_class)
15
19
  define_singleton_method :config do
16
20
  config_class.instance
@@ -61,7 +61,7 @@ module UltraSettings
61
61
  default: default,
62
62
  default_if: default_if,
63
63
  env_var: construct_env_var(name, env_var),
64
- runtime_setting: (static ? nil : construct_runtime_setting(name, runtime_setting)),
64
+ runtime_setting: construct_runtime_setting(name, runtime_setting),
65
65
  yaml_key: construct_yaml_key(name, yaml_key),
66
66
  static: static,
67
67
  secret: secret
@@ -11,10 +11,17 @@ module UltraSettings
11
11
  # <h1>Service Configuration</h1>
12
12
  # <%= UltraSettings::ConfigurationView.new(ServiceConfiguration.instance).render %>
13
13
  class ConfigurationView
14
+ # Initialize the configuration view with a configuration instance.
15
+ #
16
+ # @param configuration [UltraSettings::Configuration] The configuration instance to display.
14
17
  def initialize(configuration)
15
18
  @configuration = configuration
16
19
  end
17
20
 
21
+ # Render the HTML for the configuration view.
22
+ #
23
+ # @param table_class [String] CSS class for the table element (maintained for backwards compatibility).
24
+ # @return [String] The rendered HTML.
18
25
  def render(table_class: "")
19
26
  configuration = @configuration
20
27
  html = ViewHelper.erb_template("configuration.html.erb").result(binding)
@@ -22,6 +29,9 @@ module UltraSettings
22
29
  html
23
30
  end
24
31
 
32
+ # Convert the view to a string by rendering it.
33
+ #
34
+ # @return [String] The rendered HTML.
25
35
  def to_s
26
36
  render
27
37
  end
@@ -118,6 +118,7 @@ module UltraSettings
118
118
  end
119
119
 
120
120
  def runtime_setting_value(settings)
121
+ return nil if static? || (secret? && !UltraSettings.runtime_settings_secure?)
121
122
  return nil unless settings && runtime_setting
122
123
 
123
124
  if type == :array && settings.respond_to?(:array)
@@ -5,11 +5,18 @@ module UltraSettings
5
5
  # No setting values are displayed, but you should still add some
6
6
  # sort of authentication if you want to use this in production.
7
7
  class RackApp
8
+ # Initialize a new Rack application for displaying settings.
9
+ #
10
+ # @param color_scheme [Symbol, nil] The color scheme to use in the UI (:light, :dark, or :system).
8
11
  def initialize(color_scheme: nil)
9
12
  @webview = nil
10
13
  @color_scheme = color_scheme
11
14
  end
12
15
 
16
+ # Handle Rack requests and return the settings HTML page.
17
+ #
18
+ # @param env [Hash] The Rack environment.
19
+ # @return [Array] A Rack response array [status, headers, body].
13
20
  def call(env)
14
21
  [200, {"content-type" => "text/html; charset=utf8"}, [webview.render_settings]]
15
22
  end
@@ -3,26 +3,31 @@
3
3
  module UltraSettings
4
4
  # This class is used to represent runtime settings that have not been initialized yet.
5
5
  # You can use this to protect your application from accidentally accessing runtime settings
6
- # before they are initialized. Doing this can cquse unexpected behavior if the runtime settings
7
- # engine has not yet been initialized. For instance, if your runtime settings enging reads from
6
+ # before they are initialized. Doing this can cause unexpected behavior if the runtime settings
7
+ # engine has not yet been initialized. For instance, if your runtime settings engine reads from
8
8
  # a database it would not be available until the database connection is established.
9
9
  #
10
- # The intention of this class is to set it a the runtime settings at the beginning of initialization
10
+ # The intention of this class is to set it as the runtime settings at the beginning of initialization
11
11
  # and then set the actual runtime settings engine after the initialization is complete. It will
12
12
  # act as a guard to prevent invalid runtime settings backed configurations from being used during
13
13
  # initialization.
14
14
  #
15
15
  # @example
16
16
  #
17
- # UltraSettings.runtime_settings = UltraSettings::UninitializedRuntimeSettings
18
- # ActiveSupport.on_load(:active_record) do
19
- # UltraSettings.runtime_settings = SuperSettings
20
- # end
17
+ # UltraSettings.runtime_settings = UltraSettings::UninitializedRuntimeSettings
18
+ # ActiveSupport.on_load(:active_record) do
19
+ # UltraSettings.runtime_settings = SuperSettings
20
+ # end
21
21
  class UninitializedRuntimeSettings
22
22
  class Error < StandardError
23
23
  end
24
24
 
25
25
  class << self
26
+ # Raises an error when attempting to access runtime settings during initialization.
27
+ #
28
+ # @param key [String] The key being accessed.
29
+ # @return [void]
30
+ # @raise [Error] Always raises an error to prevent access during initialization.
26
31
  def [](key)
27
32
  raise Error.new("Attempt to call runtime setting #{key} during initialization")
28
33
  end
@@ -6,14 +6,25 @@ module UltraSettings
6
6
  @cache = {}
7
7
 
8
8
  class << self
9
+ # Get an ERB template for rendering.
10
+ #
11
+ # @param path [String] The path to the template file.
12
+ # @return [ERB] The compiled ERB template.
9
13
  def erb_template(path)
10
14
  @cache["erb:#{path}"] ||= ERB.new(read_app_file(path))
11
15
  end
12
16
 
17
+ # Read a file from the app directory.
18
+ #
19
+ # @param path [String] The path to the file relative to the app directory.
20
+ # @return [String] The contents of the file.
13
21
  def read_app_file(path)
14
22
  @cache["file:#{path}"] ||= File.read(File.join(app_dir, path))
15
23
  end
16
24
 
25
+ # Get the app directory path.
26
+ #
27
+ # @return [String] The absolute path to the app directory.
17
28
  def app_dir
18
29
  File.expand_path(File.join("..", "..", "app"), __dir__)
19
30
  end
@@ -5,18 +5,26 @@ module UltraSettings
5
5
  class WebView
6
6
  attr_reader :layout_css
7
7
 
8
+ # Initialize a new WebView with the specified color scheme.
9
+ #
8
10
  # @param color_scheme [Symbol] The color scheme to use in the UI. This can be `:light`,
9
- # `:dark`, or `:system`. The default is `:light`.
11
+ # `:dark`, or `:system`. The default is `:light`.
10
12
  def initialize(color_scheme: :light)
11
13
  @color_scheme = (color_scheme || :light).to_sym
12
14
  @layout_template = ViewHelper.erb_template("layout.html.erb")
13
15
  @layout_css = scheme_layout_css(@color_scheme)
14
16
  end
15
17
 
18
+ # Render the complete settings page HTML.
19
+ #
20
+ # @return [String] The rendered HTML page.
16
21
  def render_settings
17
22
  @layout_template.result(binding)
18
23
  end
19
24
 
25
+ # Get the content for the settings page.
26
+ #
27
+ # @return [String] The HTML content for the settings.
20
28
  def content
21
29
  UltraSettings::ApplicationView.new(color_scheme: @color_scheme).render
22
30
  end
@@ -33,11 +33,18 @@ module UltraSettings
33
33
  # In addition, the keys are flattened into a one level deep hash with dots
34
34
  # separating the keys.
35
35
  class YamlConfig
36
+ # Initialize a YAML configuration loader.
37
+ #
38
+ # @param path [String, Pathname] The path to the YAML configuration file.
39
+ # @param environment [String] The environment section to load from the YAML file.
36
40
  def initialize(path, environment)
37
41
  yaml = load_yaml(path)
38
42
  @config = environment_config(yaml, environment)
39
43
  end
40
44
 
45
+ # Convert the loaded configuration to a hash.
46
+ #
47
+ # @return [Hash] The flattened configuration hash.
41
48
  def to_h
42
49
  @config
43
50
  end
@@ -38,6 +38,7 @@ module UltraSettings
38
38
  @mutex = Mutex.new
39
39
  @runtime_settings = nil
40
40
  @runtime_settings_url = nil
41
+ @runtime_settings_secure = true
41
42
 
42
43
  class << self
43
44
  # Adds a configuration to the root namespace. The configuration will be
@@ -111,6 +112,7 @@ module UltraSettings
111
112
  # Defaults to "development".
112
113
  #
113
114
  # @param value [String] The environment name to use.
115
+ # @return [void]
114
116
  def yaml_config_env=(value)
115
117
  Configuration.yaml_config_env = value
116
118
  end
@@ -128,6 +130,7 @@ module UltraSettings
128
130
  # this is a period.
129
131
  #
130
132
  # @param value [String] The delimiter to use.
133
+ # @return [void]
131
134
  def runtime_setting_delimiter=(value)
132
135
  Configuration.runtime_setting_delimiter = value.to_s
133
136
  end
@@ -162,6 +165,9 @@ module UltraSettings
162
165
  # Set the object to use for runtime settings. This can be any object that
163
166
  # responds to the [] method. If you are using the `super_settings` gem,
164
167
  # you can set this to `SuperSettings`.
168
+ #
169
+ # @param value [#[]] The object to use for runtime settings.
170
+ # @return [void]
165
171
  attr_writer :runtime_settings
166
172
 
167
173
  # Get the object to use for runtime settings.
@@ -176,21 +182,46 @@ module UltraSettings
176
182
  # URL will be displayed in the web view for fields that support runtime settings.
177
183
  # The URL may contain a `${name}` placeholder that will be replaced with the name
178
184
  # of the setting.
185
+ #
186
+ # @param value [String] The URL for changing runtime settings.
187
+ # @return [void]
179
188
  attr_writer :runtime_settings_url
180
189
 
181
190
  # Get the URL for changing runtime settings.
182
191
  #
183
192
  # @param name [String] The name of the setting.
193
+ # @param type [String] The type of the setting.
194
+ # @param description [String] The description of the setting.
184
195
  # @return [String, nil]
185
196
  # @api private
186
- def runtime_settings_url(name, type)
197
+ def runtime_settings_url(name: nil, type: nil, description: nil)
187
198
  url = @runtime_settings_url.to_s
188
199
  return nil if url.empty?
189
200
 
190
- url = url.gsub("${name}", URI.encode_www_form_component(name.to_s))
191
- url.gsub("${type}", URI.encode_www_form_component(type.to_s))
201
+ url.gsub("${name}", URI.encode_www_form_component(name.to_s))
202
+ .gsub("${type}", URI.encode_www_form_component(type.to_s))
203
+ .gsub("${description}", URI.encode_www_form_component(description.to_s))
204
+ end
205
+
206
+ # Set whether or not the runtime settings engine is considered secure. If this is set
207
+ # to false, then runtime settings will be disabled for all fields marked as secret.
208
+ # The default value is true.
209
+ #
210
+ # @param value [Boolean] Whether the runtime settings engine is secure.
211
+ # @return [void]
212
+ attr_writer :runtime_settings_secure
213
+
214
+ # Check if the runtime settings engine is considered secure.
215
+ #
216
+ # @return [Boolean]
217
+ def runtime_settings_secure?
218
+ @runtime_settings_secure
192
219
  end
193
220
 
221
+ # Set whether fields should be considered secret by default.
222
+ #
223
+ # @param value [Boolean] Whether fields should be secret by default.
224
+ # @return [void]
194
225
  def fields_secret_by_default=(value)
195
226
  Configuration.fields_secret_by_default = value
196
227
  end
@@ -245,6 +276,7 @@ module UltraSettings
245
276
  unless klass < Configuration
246
277
  raise TypeError.new("Configuration class #{class_name} does not inherit from UltraSettings::Configuration")
247
278
  end
279
+
248
280
  @configurations[name] = klass
249
281
  end
250
282
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ultra_settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.1
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-05 00:00:00.000000000 Z
11
+ date: 2025-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler