ultra_settings 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 35efcdf391347182451bb26d3740e3d18c6e8c5bb4ff4d0100d0f418bb2eef7c
4
- data.tar.gz: ed5721d08d451893fbce63317d92d024a7f2cff1e0c68616455b3343b62eb7a6
3
+ metadata.gz: 2a9fddef64ed0564d8ead06ae79465a37fdb50a55407139cacc121714475ac6f
4
+ data.tar.gz: a26020c7d81154d3e000bba327e3a6cb53ab00ae9847ed2f96999ff18d431241
5
5
  SHA512:
6
- metadata.gz: 4636930f294c4c28389da0ed2e627554b1128a92f7fd622a0ac6eb905e6295ae46c513c449a3552e548b840a24d1c05ea106d32a8896a603ce98c596b0d72313
7
- data.tar.gz: f8aa279a14715cc6a5a170d43dbaa1e38a55104b92dd25c403fb3b24b4066048bdb69159bf54cf9a3fb44a4280ccd91dba524ddbdca783eabca678ac5f28460e
6
+ metadata.gz: 066b922fcc7232a33eee6e6b52924d1efc51d3a4e500f4680f8e03a1feb7a6414a22b55f9b5672e35db7aeb958566fc1685ba39fed8a9f568840af2b60a7b3e2
7
+ data.tar.gz: b95942f92c5010decb55da413fdd2c9f070ebafef2815ab5bd4f781f803b744c502b98598fc14b4e5338ec7026fd7a1c5b11011136afe4507a0eeb436dac632b
data/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ 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.3.0
8
+
9
+ ### Added
10
+
11
+ - Added logic for parsing arrays from environment variables. Array fields can now be set as comma delimited strings in an environment variable.
12
+
13
+ ### Fixed
14
+
15
+ - Mixed case boolean values are now handled properly so that "False" is interpreted as `false` and "True" is interpreted as `true`.
16
+
17
+ ## 2.2.0
18
+
19
+ ### Added
20
+
21
+ - Added option for `UltraSettings.runtime_settings_url` to allow configuring a link for editing runtime settings from the web UI.
22
+
7
23
  ## 2.1.0
8
24
 
9
25
  ### Added
data/README.md CHANGED
@@ -10,6 +10,24 @@ UltraSettings is a Ruby gem designed for managing application settings from vari
10
10
 
11
11
  UltraSettings emphasizes well-documented configuration. You can include documentation directly in the configuration code. The gem also includes a [web UI](#web-ui) that can be mounted as a Rack app or embedded in other views allowing admin users to easily view configuration settings and documentation.
12
12
 
13
+ ## Table Of Contents
14
+
15
+ - [Key Features](#key-features)
16
+ - [Usage](#usage)
17
+ - [Defining Configurations](#defining-configurations)
18
+ - [Field Options](#field-options)
19
+ - [Environment Variables](#environment-variables)
20
+ - [Runtime Settings](#runtime-settings)
21
+ - [YAML Files](#yaml-files)
22
+ - [Removing The Hierarchy](#removing-the-hierarchy)
23
+ - [Accessing Settings](#accessing-settings)
24
+ - [Web UI](#web-ui)
25
+ - [Testing With UltraSettings](#testing-with-ultrasettings)
26
+ - [Rollout Percentages](#rollout-percentages)
27
+ - [Installation](#installation)
28
+ - [Contributing](#contributing)
29
+ - [License](#license)
30
+
13
31
  ## Key Features
14
32
 
15
33
  This gem supports a three-layer hierarchy for defining configuration sources:
@@ -82,13 +100,13 @@ You can customize the behavior of each field using various options:
82
100
 
83
101
  - `:type` - Specifies the type of the field. The value of the setting will be cast to this type. If the value in the data source cannot be cast to the data type, then it will not be used. Supported types are:
84
102
 
85
- - `:string` (the default)
103
+ - `:string` - This is the default type.
86
104
  - `:integer`
87
105
  - `:float`
88
- - `:boolean` (will accept case insensitive strings "true", "false", "1", "0", "t", "f", "yes", "no", "y", "n")
89
- - `:datetime`
106
+ - `:boolean` - Will accept case insensitive strings "true", "false", "1", "0", "t", "f", "yes", "no", "y", "n".
107
+ - `:datetime` - Values should be specified in ISO 8601 format.
90
108
  - `:symbol`
91
- - `:array` (of strings)
109
+ - `:array` - The array type will return an array of strings. If the raw value is a string (i.e. from an environment variable), it will be iterpreted as a comma separated list of values. You can use double quotes to group values that contain commas and backslashes to escape values. Leading and trailing whitespace will be stripped from each value.
92
110
 
93
111
  - `:description` - Provides a description of the field. This is used for documentation purposes.
94
112
 
@@ -176,6 +194,8 @@ You can customize the behavior of runtime setting names with the following optio
176
194
 
177
195
  - **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`.
178
196
 
197
+ - **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 placeholder `${name}` in the URL which will be replaced with the name of the runtime setting. If you are using the `super_settings` gem for runtime settings, then you can target a setting by adding `#edit=${name}` to the root URL where `super_settings` is mounted.
198
+
179
199
  If a setting value cannot be loaded from the runtime settings, then it's value will attempt to be loaded from a YAML file.
180
200
 
181
201
  ### YAML Files
@@ -480,7 +500,7 @@ bundle exec rackup
480
500
  You can test with some setting set by setting environment variable used in the test configuration.
481
501
 
482
502
  ```bash
483
- MY_SERVICE_HOST=host.example.com MY_SERVICE_TOKEN=secret bundle exec rspec
503
+ MY_SERVICE_HOST=host.example.com MY_SERVICE_TOKEN=secret bundle exec rackup
484
504
  ```
485
505
 
486
506
  You can test dark mode by setting the `COLOR_SCHEME` environment variable.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.1.0
1
+ 2.3.0
@@ -52,14 +52,14 @@
52
52
  <% end %>
53
53
  </td>
54
54
 
55
- <td>
55
+ <td style="word-wrap: break-word;">
56
56
  <% unless field.description.to_s.empty? %>
57
57
  <div>
58
58
  <%= html_escape(field.description) %>
59
59
  </div>
60
60
  <% end %>
61
61
 
62
- <ul style="margin: 0; padding: 0;list-style-type: disc; list-style-position: inside;">
62
+ <ul style="margin: 0 0 0 1rem; padding: 0; list-style-type: disc; list-style-position: outside;">
63
63
  <% if field.env_var && !configuration.class.environment_variables_disabled? %>
64
64
  <li>
65
65
  <% if source == :env %>
@@ -68,9 +68,8 @@
68
68
  <% else %>
69
69
  Can be
70
70
  <% end %>
71
- set with the
71
+ set with the environment variable
72
72
  <code><%= show_defined_value(field.env_var, configuration.__value_from_source__(field.name, :env), field.secret?) %></code>
73
- environment variable.
74
73
  <% if source == :env %>
75
74
  </strong>
76
75
  <% end %>
@@ -84,12 +83,21 @@
84
83
  <% else %>
85
84
  Can be
86
85
  <% end %>
87
- set with the
86
+ set with the runtime setting
88
87
  <code><%= show_defined_value(field.runtime_setting, configuration.__value_from_source__(field.name, :settings), field.secret?) %></code>
89
- runtime setting.
90
88
  <% if source == :settings %>
91
89
  </strong>
92
90
  <% end %>
91
+
92
+ <% edit_url = UltraSettings.runtime_settings_url(field.runtime_setting) %>
93
+ <% if edit_url %>
94
+ <a href="<%= html_escape(edit_url) %>" title="Edit <%= html_escape(field.runtime_setting) %>" style="text-decoration: none; color: inherit; vertical-align: middle;">
95
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pencil-square" viewBox="0 0 16 16">
96
+ <path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/>
97
+ <path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5z"/>
98
+ </svg>
99
+ </a>
100
+ <% end %>
93
101
  </li>
94
102
  <% end %>
95
103
  <% if field.yaml_key && !configuration.class.yaml_config_disabled? %>
@@ -100,9 +108,8 @@
100
108
  <% else %>
101
109
  Can be
102
110
  <% end %>
103
- set with the
111
+ set with the configuration file key
104
112
  <code><%= show_defined_value(field.yaml_key, configuration.__value_from_source__(field.name, :yaml), field.secret?) %></code>
105
- key in the configuration file.
106
113
  <% if source == :yaml %>
107
114
  </strong>
108
115
  <% end %>
@@ -10,11 +10,8 @@ module UltraSettings
10
10
  false, 0,
11
11
  "0", :"0",
12
12
  "f", :f,
13
- "F", :F,
14
13
  "false", :false,
15
- "FALSE", :FALSE,
16
- "off", :off,
17
- "OFF", :OFF
14
+ "off", :off
18
15
  ]).freeze
19
16
  # rubocop:enable Lint/BooleanSymbol
20
17
 
@@ -39,7 +36,7 @@ module UltraSettings
39
36
  when :datetime
40
37
  time(value)
41
38
  when :array
42
- Array(value).map(&:to_s)
39
+ array(value).map(&:to_s)
43
40
  when :symbol
44
41
  value.to_s.to_sym
45
42
  when :rollout
@@ -53,14 +50,26 @@ module UltraSettings
53
50
  end
54
51
  end
55
52
 
53
+ # Cast value of array
54
+ #
55
+ # @param value [Object]
56
+ # @return [Array]
57
+ def array(value)
58
+ return [] if blank?(value)
59
+ return value.collect(&:to_s) if value.is_a?(Array)
60
+
61
+ parse_csv_line(value.to_s)
62
+ end
63
+
56
64
  # Cast variations of booleans (i.e. "true", "false", 1, 0, etc.) to actual boolean objects.
57
65
  #
58
66
  # @param value [Object]
59
67
  # @return [Boolean]
60
68
  def boolean(value)
61
69
  return nil if blank?(value)
70
+ return false if value == false
62
71
 
63
- !FALSE_VALUES.include?(value)
72
+ !FALSE_VALUES.include?(value.to_s.downcase)
64
73
  end
65
74
 
66
75
  # Cast a value to a Time object.
@@ -104,6 +113,43 @@ module UltraSettings
104
113
  def present?(value)
105
114
  !blank?(value)
106
115
  end
116
+
117
+ private
118
+
119
+ # Parse a line of CSV data to an array of strings. Elements are separated by commas and
120
+ # characters can be escaped with a backslash.
121
+ def parse_csv_line(line)
122
+ values = []
123
+ current_value = +""
124
+ in_quotes = false
125
+
126
+ i = 0
127
+ while i < line.length
128
+ char = line[i]
129
+
130
+ if char == "\\"
131
+ if i + 1 < line.length
132
+ current_value << line[i + 1]
133
+ i += 1
134
+ else
135
+ current_value << "\\"
136
+ end
137
+ elsif char == '"'
138
+ in_quotes = !in_quotes
139
+ elsif char == "," && !in_quotes
140
+ values << current_value.strip
141
+ current_value = +""
142
+ else
143
+ current_value << char
144
+ end
145
+
146
+ i += 1
147
+ end
148
+
149
+ values << current_value.strip unless current_value.empty?
150
+
151
+ values
152
+ end
107
153
  end
108
154
  end
109
155
  end
@@ -7,6 +7,9 @@ module UltraSettings
7
7
  ALLOWED_NAME_PATTERN = /\A[a-z_][a-zA-Z0-9_]*\z/
8
8
  ALLOWED_TYPES = [:string, :symbol, :integer, :float, :boolean, :datetime, :array].freeze
9
9
 
10
+ @env_var_prefix = nil
11
+ @runtime_setting_prefix = nil
12
+
10
13
  class << self
11
14
  # Define a field on the configuration. This will create a getter method for the field.
12
15
  # The field value will be read from the environment, runtime settings, or a YAML file
@@ -435,10 +438,10 @@ module UltraSettings
435
438
  end
436
439
 
437
440
  def initialize
438
- @mutex = Mutex.new
439
- @memoized_values = {}
440
- @override_values = {}
441
- @yaml_config = nil
441
+ @ultra_settings_mutex = Mutex.new
442
+ @ultra_settings_memoized_values = {}
443
+ @ultra_settings_override_values = {}
444
+ @ultra_settings_yaml_config = nil
442
445
  end
443
446
 
444
447
  def [](name)
@@ -450,7 +453,7 @@ module UltraSettings
450
453
  end
451
454
 
452
455
  def override!(values, &block)
453
- save_val = @override_values[Thread.current.object_id]
456
+ save_val = @ultra_settings_override_values[Thread.current.object_id]
454
457
 
455
458
  temp_values = (save_val || {}).dup
456
459
  values.each do |key, value|
@@ -458,13 +461,13 @@ module UltraSettings
458
461
  end
459
462
 
460
463
  begin
461
- @mutex.synchronize do
462
- @override_values[Thread.current.object_id] = temp_values
464
+ @ultra_settings_mutex.synchronize do
465
+ @ultra_settings_override_values[Thread.current.object_id] = temp_values
463
466
  end
464
467
  yield
465
468
  ensure
466
- @mutex.synchronize do
467
- @override_values[Thread.current.object_id] = save_val
469
+ @ultra_settings_mutex.synchronize do
470
+ @ultra_settings_override_values[Thread.current.object_id] = save_val
468
471
  end
469
472
  end
470
473
  end
@@ -530,12 +533,12 @@ module UltraSettings
530
533
  field = self.class.send(:defined_fields)[name]
531
534
  return nil unless field
532
535
 
533
- if field.static? && @memoized_values.include?(name)
534
- return @memoized_values[name]
536
+ if field.static? && @ultra_settings_memoized_values.include?(name)
537
+ return @ultra_settings_memoized_values[name]
535
538
  end
536
539
 
537
- if @override_values[Thread.current.object_id]&.include?(name)
538
- value = field.coerce(@override_values[Thread.current.object_id][name])
540
+ if @ultra_settings_override_values[Thread.current.object_id]&.include?(name)
541
+ value = field.coerce(@ultra_settings_override_values[Thread.current.object_id][name])
539
542
  else
540
543
  env = ENV if field.env_var
541
544
  settings = UltraSettings.__runtime_settings__ if field.runtime_setting
@@ -549,11 +552,11 @@ module UltraSettings
549
552
  end
550
553
 
551
554
  if field.static?
552
- @mutex.synchronize do
553
- if @memoized_values.include?(name)
554
- value = @memoized_values[name]
555
+ @ultra_settings_mutex.synchronize do
556
+ if @ultra_settings_memoized_values.include?(name)
557
+ value = @ultra_settings_memoized_values[name]
555
558
  else
556
- @memoized_values[name] = value
559
+ @ultra_settings_memoized_values[name] = value
557
560
  end
558
561
  end
559
562
  end
@@ -578,7 +581,7 @@ module UltraSettings
578
581
  end
579
582
 
580
583
  def __yaml_config__
581
- @yaml_config ||= self.class.load_yaml_config || {}
584
+ @ultra_settings_yaml_config ||= self.class.load_yaml_config || {}
582
585
  end
583
586
  end
584
587
  end
@@ -6,6 +6,7 @@ require "time"
6
6
  require "pathname"
7
7
  require "singleton"
8
8
  require "digest"
9
+ require "uri"
9
10
 
10
11
  require_relative "ultra_settings/configuration"
11
12
  require_relative "ultra_settings/coerce"
@@ -33,6 +34,7 @@ module UltraSettings
33
34
  @configurations = {}
34
35
  @mutex = Mutex.new
35
36
  @runtime_settings = nil
37
+ @runtime_settings_url = nil
36
38
 
37
39
  class << self
38
40
  # Adds a configuration to the root namespace. The configuration will be
@@ -167,6 +169,24 @@ module UltraSettings
167
169
  @runtime_settings
168
170
  end
169
171
 
172
+ # Set the URL for changing runtime settings. If this is set, then a link to the
173
+ # URL will be displayed in the web view for fields that support runtime settings.
174
+ # The URL may contain a `${name}` placeholder that will be replaced with the name
175
+ # of the setting.
176
+ attr_writer :runtime_settings_url
177
+
178
+ # Get the URL for changing runtime settings.
179
+ #
180
+ # @param name [String] The name of the setting.
181
+ # @return [String, nil]
182
+ # @api private
183
+ def runtime_settings_url(name)
184
+ url = @runtime_settings_url.to_s
185
+ return nil if url.empty?
186
+
187
+ url.gsub("${name}", URI.encode_www_form_component(name))
188
+ end
189
+
170
190
  def fields_secret_by_default=(value)
171
191
  Configuration.fields_secret_by_default = value
172
192
  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.1.0
4
+ version: 2.3.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: 2024-10-12 00:00:00.000000000 Z
11
+ date: 2024-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler