ultra_settings 2.7.0 → 2.8.1

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: f70521135220d74fe5ef4fbc6261c9d5b8a93f200f898af0fead2ebb7d31cbfd
4
- data.tar.gz: 42330a1a294bee7ab53c61f37b56cd0b5b6f309326f1809bfd012cbfcd67fa58
3
+ metadata.gz: d6db7d9c5a607d4c0318190ba8357f0577f9d542d08fbc77e6253e3776372476
4
+ data.tar.gz: 52171f7843724be2e867f2cf3547b934a530249173f8f2c583548f442f9ceb22
5
5
  SHA512:
6
- metadata.gz: aab2c06121a67e227f1628d1dd3a22590e0bb174f132506840112278a5c09484e57dd6bc176ddbb35d576388c29bb5f6da462473f1c5ebf76724fe8677312ed2
7
- data.tar.gz: f7e9bd08a2958def30780e0ef966d8b90321d3f9c1bf469677b7c51ab8b512e867bf0e1fc1d910d72f24c9f71818a34050b3a2dbfdc2c5adb9afa4a3e048948a
6
+ metadata.gz: 660219db715918d464d403064ac96920dde3acf7be57e85b3de921ecc7fe4b3ef641f9ef8b386ea931a603ad7cfab5f4f9ddd9cbc4f151226e4d3359d5a25dce
7
+ data.tar.gz: b4b40f05c524a3bd75fbb237c631e4643f229c50134fadf819acd1dded92855ebb3070eeaa8ee508a36bdbb13ec759a3fd6eb9c140db90d092ebd649a88bb8cf
data/AGENTS.md ADDED
@@ -0,0 +1,191 @@
1
+ # UltraSettings AI Coding Agent Instructions
2
+
3
+ ## Project Overview
4
+ UltraSettings is a Ruby gem for managing application configuration from multiple sources (environment variables, runtime settings, YAML files) with built-in type safety, validation, and a web UI for documentation.
5
+
6
+ ## Core Architecture
7
+
8
+ ### Configuration Classes (Singleton Pattern)
9
+ - All configuration classes extend `UltraSettings::Configuration` and use Ruby's `Singleton` module
10
+ - Each configuration class has one instance accessible via `.instance`
11
+ - Register configurations globally: `UltraSettings.add(:test)` creates `UltraSettings.test` accessor
12
+ - Configuration classes support inheritance; subclasses maintain separate singleton instances
13
+
14
+ ### Field Definition DSL
15
+ Fields are defined using the `field` class method with extensive options:
16
+ ```ruby
17
+ field :timeout, type: :float, default: 1.0,
18
+ default_if: ->(val) { val <= 0 },
19
+ description: "Network timeout in seconds",
20
+ secret: false
21
+ ```
22
+
23
+ ### Multi-Source Value Resolution (Precedence Order)
24
+ 1. **Environment Variables** - Highest priority (e.g., `MY_SERVICE_TIMEOUT`)
25
+ 2. **Runtime Settings** - Dynamic configuration from external stores (e.g., Redis, `super_settings` gem)
26
+ 3. **YAML Files** - File-based defaults with ERB support
27
+ 4. **Default Values** - Fallback specified in field definition
28
+
29
+ ### Type System
30
+ Supported types: `:string` (default), `:symbol`, `:integer`, `:float`, `:boolean`, `:datetime`, `:array`
31
+ - Boolean fields auto-generate `?` predicate methods (e.g., `enabled?`)
32
+ - Array type parses CSV strings from env vars, supports proper arrays from YAML
33
+ - Empty strings coerce to `nil` across all sources
34
+
35
+ ## Key Conventions
36
+
37
+ ### Naming Patterns
38
+ - **Configuration class names**: Must end in `Configuration` (e.g., `MyServiceConfiguration`)
39
+ - **Field names**: Match `/\A[a-z_][a-zA-Z0-9_]*\z/` pattern (snake_case)
40
+ - **Environment variable defaults**: `MY_SERVICE_FOO` for `MyServiceConfiguration#foo`
41
+ - **Runtime setting defaults**: `my_service.foo` (lowercase with dots)
42
+ - **YAML keys**: Match field name by default
43
+
44
+ ### Customization Attributes
45
+ Set on configuration classes to override defaults:
46
+ - `env_var_prefix`, `env_var_delimiter`, `env_var_upcase` - Control environment variable naming
47
+ - `runtime_setting_prefix`, `runtime_setting_delimiter`, `runtime_setting_upcase` - Control runtime setting naming
48
+ - `configuration_file` - Specify explicit YAML file path
49
+ - `fields_secret_by_default` - Default secret status (true by default for security)
50
+ - Disable sources entirely: `environment_variables_disabled`, `runtime_settings_disabled`, `yaml_config_disabled`
51
+
52
+ ### Thread Safety
53
+ - All memoized values protected by `Mutex`
54
+ - Override values are thread-local (keyed by `Thread.current.object_id`)
55
+ - Static fields are cached permanently after first access
56
+
57
+ ## Testing Patterns
58
+
59
+ ### Test Setup (spec/spec_helper.rb)
60
+ ```ruby
61
+ # Configure YAML path and environment
62
+ UltraSettings.yaml_config_path = Pathname.new(__dir__) + "config"
63
+ UltraSettings.yaml_config_env = "test"
64
+
65
+ # Register configurations
66
+ UltraSettings.add(:test)
67
+ ```
68
+
69
+ ### Overriding Configuration Values
70
+ Use `override!` method for temporary value changes in tests:
71
+ ```ruby
72
+ # Via UltraSettings module
73
+ UltraSettings.override!(test: {foo: "bar"}) { ... }
74
+
75
+ # Via configuration class
76
+ TestConfiguration.override!(foo: "bar") { ... }
77
+
78
+ # Via configuration instance
79
+ TestConfiguration.instance.override!(foo: "bar") { ... }
80
+ ```
81
+
82
+ ### RSpec Integration Example
83
+ ```ruby
84
+ RSpec.configure do |config|
85
+ config.around do |example|
86
+ if example.metadata[:ultra_settings]
87
+ UltraSettings.override!(example.metadata[:ultra_settings]) do
88
+ example.run
89
+ end
90
+ end
91
+ end
92
+ end
93
+ ```
94
+
95
+ ### Climate Control for Environment Variables
96
+ Uses `climate_control` gem to safely modify environment variables in tests:
97
+ ```ruby
98
+ RSpec.describe "config", env: {TIMEOUT: "5"} do
99
+ # Test with environment variable set
100
+ end
101
+ ```
102
+
103
+ ## Web UI Features
104
+
105
+ ### Mounting the Rack App
106
+ ```ruby
107
+ # config.ru or Rails routes
108
+ mount UltraSettings::RackApp.new(color_scheme: :system), at: "/settings"
109
+ ```
110
+
111
+ ### Key Capabilities
112
+ - Displays all registered configurations with descriptions
113
+ - Shows field metadata: type, description, sources, but **NOT actual values**
114
+ - Respects secret field marking (values hidden)
115
+ - Includes links to edit runtime settings via `UltraSettings.runtime_settings_url`
116
+ - Views in `lib/ultra_settings/*_view.rb` use ERB templates from `app/` directory
117
+
118
+ ## Common Development Workflows
119
+
120
+ ### Running Tests
121
+ ```bash
122
+ bundle exec rake spec # Default task runs all specs
123
+ bundle exec rspec spec/ultra_settings/configuration_spec.rb # Specific file
124
+ ```
125
+
126
+ ### Checking Code Style
127
+ Uses Standard Ruby style guide (testdouble/standard):
128
+ ```bash
129
+ bundle exec standardrb --fix
130
+ ```
131
+
132
+ ### Release Process
133
+ - Can only release from `main` branch (enforced in Rakefile)
134
+ - Version stored in `VERSION` file (no hardcoded version in gemspec)
135
+
136
+ ## Special Features
137
+
138
+ ### Static Fields
139
+ - Marked with `static: true`, values never change after first access
140
+ - Cannot be set from runtime settings (initialization-time only)
141
+ - Use for settings referenced during app boot
142
+
143
+ ### Secret Fields
144
+ - All fields secret by default unless `fields_secret_by_default = false`
145
+ - Masked in `__to_hash__` output
146
+ - Runtime settings disabled on secret fields unless `UltraSettings.runtime_settings_secure = true`
147
+ - Secret status can be dynamic via Proc
148
+
149
+ ### Conditional Defaults
150
+ Use `default_if` with Proc or method name to apply defaults based on loaded value:
151
+ ```ruby
152
+ field :timeout, default: 1.0, default_if: ->(val) { val <= 0 }
153
+ ```
154
+
155
+ ### Introspection Methods
156
+ - `__source__(name)` - Returns which source provided the value (`:env`, `:runtime`, `:yaml`, `:default`)
157
+ - `__value_from_source__(name, source)` - Fetch value from specific source
158
+ - `__to_hash__` - Serialize current configuration as hash (secrets masked)
159
+
160
+ ## Important Implementation Details
161
+
162
+ ### Dynamic Method Generation
163
+ Fields create getter methods via `class_eval` for performance (avoids `method_missing`)
164
+
165
+ ### YAML Configuration
166
+ - Supports environment-specific sections (e.g., `development`, `test`, `production`)
167
+ - Special `shared` section merged with environment-specific config
168
+ - ERB templates evaluated before YAML parsing
169
+ - Files searched in `UltraSettings.yaml_config_path`
170
+
171
+ ### Runtime Settings Integration
172
+ - Must implement `[]` method accepting string argument
173
+ - Optional `array` method for native array support
174
+ - Use `UninitializedRuntimeSettings` during boot to catch premature access
175
+ - `super_settings` gem is recommended companion implementation
176
+
177
+ ## File Organization
178
+
179
+ - `lib/ultra_settings/configuration.rb` - Core configuration class (630 lines)
180
+ - `lib/ultra_settings/field.rb` - Field metadata and resolution logic
181
+ - `lib/ultra_settings/coerce.rb` - Type coercion utilities
182
+ - `lib/ultra_settings/*.rb` - Supporting classes for web UI, YAML, helpers
183
+ - `spec/test_configs/` - Example configuration classes for testing
184
+ - `spec/config/` - YAML configuration files for tests
185
+ - `app/` - ERB templates and assets for web UI
186
+
187
+ ## Key Dependencies
188
+ - Ruby >= 2.5
189
+ - Development: `rspec`, `climate_control`, `nokogiri`, `bundler`
190
+ - Optional: `super_settings` gem for runtime settings store
191
+ - Rails integration via `railtie.rb` when Rails is detected
data/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ 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.8.1
8
+
9
+ ### Added
10
+
11
+ - Improved web UI indication for which data sources on fields have been overridden by data sources with higher precedence.
12
+ - Added rake tasks for auditing configuration data sources.
13
+ - A companion gem `yard-ultra_settings` is now available to provide YARD integration for documenting UltraSettings configuration classes. See the [yard-ultra_settings](https://github.com/bdurand/ultra_settings/yard_plugin) gem for more information.
14
+
15
+ ## 2.8.0
16
+
17
+ ### Added
18
+
19
+ - Added support for setting a description on configuration classes. The class description can serve to document the purpose of the configuration and will be shown in the web UI.
20
+ - Improved menu for selecting configuration classes in the web UI by adding a search box to filter the list of configurations.
21
+
22
+ ### Fixed
23
+
24
+ - Fixed filtering of data sources for configuration classes in the web UI.
25
+ - The YAML source will not be shown if the configuration file is set to nil or false.
26
+ - The runtime settings source will not be shown if the runtime settings engine is not set up.
27
+ - Fields that override the class default for a data source will now correctly show that source.
28
+
7
29
  ## 2.7.0
8
30
 
9
31
  ### Added
data/MIT-LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2023 Brian Durand
1
+ Copyright 2026 Brian Durand
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -424,7 +424,7 @@ If you prefer to embed the settings view directly into your own admin tools or d
424
424
  ```erb
425
425
  <h1>Configuration</h1>
426
426
 
427
- <%= UltraSettings::ApplicationView.new.render(select_class: "form-select") %>
427
+ <%= UltraSettings::ApplicationView.new.render %>
428
428
  ```
429
429
 
430
430
  This approach allows for seamless integration of the settings UI into your application's admin interface, leveraging your existing authentication and authorization mechanisms. The settings are rendered with navigation handled by an HTML select element. You can specify the CSS classes for the select element to match your own application styles..
@@ -520,6 +520,74 @@ class MyServiceConfiguration < UltraSettings::Configuration
520
520
  end
521
521
  ```
522
522
 
523
+ ### Rails Tasks
524
+
525
+ You can audit your configuration data sources with rake tasks to identify potential optimizations with these rake tasks:
526
+
527
+ ```bash
528
+ # Output showing environment variables that are set, but which do not need to be set
529
+ # since they match the default values.
530
+ bundle exec rails ultra_settings:unnecessary_env_vars
531
+
532
+ # Output showing runtime settings that are set, but which do not need to be set
533
+ # since they match the default values.
534
+ bundle exec rails ultra_settings:unnecessary_runtime_settings
535
+
536
+ # Output showing environment variables that are set but which could be loaded from
537
+ # runtime settings instead.
538
+ bundle exec rails ultra_settings:env_vars_can_be_runtime_setting
539
+
540
+ # Output showing environment variables that are set that could be candidates for
541
+ # adding default values to the configuration fields.
542
+ bundle exec rails ultra_settings:env_vars_without_default
543
+ ```
544
+
545
+ ## Generating Documentation
546
+
547
+ ### YARD Plugin
548
+
549
+ For projects using [YARD](https://yardoc.org/) for documentation, there is a companion plugin gem that automatically generates documentation for configuration fields.
550
+
551
+ Add to your Gemfile:
552
+
553
+ ```ruby
554
+ group :development, :test do
555
+ gem 'yard-ultra_settings'
556
+ end
557
+ ```
558
+
559
+ Then add the following to your `.yardopts` file:
560
+
561
+ ```
562
+ --plugin ultra_settings
563
+ ```
564
+
565
+ Once configured, the plugin automatically enhances YARD documentation for any classes that inherit from `UltraSettings::Configuration`. Field definitions will automatically generate method documentation with proper types and return values.
566
+
567
+ See the [yard-ultra_settings](https://github.com/bdurand/ultra_settings/tree/main/yard_plugin) gem for more details.
568
+
569
+ ### Using YARD Macros
570
+
571
+ If you prefer not to install the plugin gem, you can use YARD's built-in macro feature by adding documentation comments before field definitions:
572
+
573
+ ```ruby
574
+ class MyServiceConfiguration < UltraSettings::Configuration
575
+ self.fields_secret_by_default = false
576
+
577
+ # @!method host
578
+ # The hostname for the service
579
+ # @return [String, nil]
580
+ field :host, type: :string
581
+
582
+ # @!method port
583
+ # The port for the service
584
+ # @return [Integer]
585
+ field :port, type: :integer, default: 80
586
+ end
587
+ ```
588
+
589
+ This approach gives you full control over the documentation but requires more manual effort for each field.
590
+
523
591
  ## Installation
524
592
 
525
593
  Add this line to your application's Gemfile:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.7.0
1
+ 2.8.1
@@ -0,0 +1,30 @@
1
+ <% config_class = configuration.class
2
+ has_config_file = config_class.configuration_file.is_a?(Pathname) && config_class.fields.any?(&:yaml_key) %>
3
+
4
+ <% if config_class.description || has_config_file %>
5
+ <div class="ultra-settings-description-container">
6
+ <% if config_class.description %>
7
+ <div class="ultra-settings-description">
8
+ <%= html_escape(config_class.description).gsub("\n", "<br>") %>
9
+ </div>
10
+ <% end %>
11
+
12
+ <% if has_config_file %>
13
+ <div class="ultra-settings-description">
14
+ <span class="ultra-settings-config-file-label">
15
+ Configuration file:
16
+ </span>
17
+
18
+ <code class="ultra-settings-config-file-path">
19
+ <%= html_escape(relative_path(configuration.class.configuration_file)) %>
20
+ </code>
21
+
22
+ <% unless configuration.class.configuration_file&.exist? %>
23
+ <span class="ultra-settings-file-not-found">
24
+ (file does not exist)
25
+ </span>
26
+ <% end %>
27
+ </div>
28
+ <% end %>
29
+ </div>
30
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <div class="ultra-settings-configuration-list" style="display:none;">
2
+ <% configurations.each do |configuration| %>
3
+ <div class="ultra-settings-configuration-summary">
4
+ <a
5
+ href="#<%= html_escape(configuration.class.name) %>"
6
+ class="ultra-settings-configuration-title"
7
+ >
8
+ <%= html_escape(configuration.class.name) %>
9
+ </a>
10
+
11
+ <p><%= html_escape(configuration.class.description) %></p>
12
+ </div>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1,39 @@
1
+ <% source_value = configuration.__value_from_source__(field.name, source) %>
2
+ <% overridden_by = source_overridden_by(source, current_source) %>
3
+ <div class="ultra-settings-source <%= 'ultra-settings-source-active' if current_source == source %> <%= 'ultra-settings-source-overridden' if overridden_by && !source_value.nil? %>">
4
+ <span class="ultra-settings-source-type">
5
+ <%= source_type %>
6
+ </span>
7
+
8
+ <div class="ultra-settings-source-value-container">
9
+ <% if source_name %>
10
+ <code class="ultra-settings-source-value">
11
+ <%= source_name %>
12
+ </code>
13
+ <% end %>
14
+
15
+ <%= show_defined_value(source_name, source_value, field.secret?) %>
16
+ </div>
17
+
18
+ <% if current_source == source %>
19
+ <span class="ultra-settings-source-indicator">
20
+ Currently active
21
+ </span>
22
+ <% elsif overridden_by && !source_value.nil? %>
23
+ <%= override_indicator(overridden_by) %>
24
+ <% end %>
25
+
26
+ <% if source == :settings %>
27
+ <% edit_url = UltraSettings.runtime_settings_url(name: field.runtime_setting, type: field.type, description: field.description) %>
28
+
29
+ <% if edit_url %>
30
+ <a
31
+ href="<%= html_escape(edit_url) %>"
32
+ class="ultra-settings-edit-link"
33
+ title="Edit <%= html_escape(field.runtime_setting) %>"
34
+ >
35
+ <%= edit_icon %>
36
+ </a>
37
+ <% end %>
38
+ <% end %>
39
+ </div>
@@ -0,0 +1,53 @@
1
+ <div class="ultra-settings-dropdown" id="config-dropdown">
2
+ <button
3
+ type="button"
4
+ class="ultra-settings-dropdown-button"
5
+ id="config-dropdown-button"
6
+ >
7
+ Select Configuration
8
+ </button>
9
+
10
+ <div
11
+ class="ultra-settings-dropdown-menu"
12
+ id="config-dropdown-menu"
13
+ style="display: none;"
14
+ >
15
+ <div class="ultra-settings-search-container">
16
+ <input
17
+ type="text"
18
+ id="config-search"
19
+ placeholder="Search..."
20
+ autocomplete="off"
21
+ >
22
+ </div>
23
+
24
+ <ul class="ultra-settings-dropdown-list" id="config-list">
25
+ <% configurations.each do |config| %>
26
+ <% config_text = [
27
+ config.class.name,
28
+ config.class.description
29
+ ]
30
+ config.class.fields.each do |field|
31
+ config_text << field.name
32
+ config_text << field.description
33
+ config_text << field.env_var
34
+ config_text << field.runtime_setting
35
+ end
36
+ search_text = config_text.compact.join(" ").downcase %>
37
+
38
+ <li
39
+ class="ultra-settings-dropdown-item"
40
+ data-value="config-<%= html_escape(config.class.name) %>"
41
+ data-label="<%= html_escape(config.class.name) %>"
42
+ data-search="<%= html_escape(search_text) %>"
43
+ >
44
+ <span class="ultra-settings-check-icon"></span>
45
+
46
+ <span class="ultra-settings-config-name">
47
+ <%= html_escape(config.class.name) %>
48
+ </span>
49
+ </li>
50
+ <% end %>
51
+ </ul>
52
+ </div>
53
+ </div>