super_settings 2.4.0 → 2.4.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +4 -2
- data/VERSION +1 -1
- data/lib/super_settings/application/helper.rb +14 -0
- data/lib/super_settings/application/index.html.erb +1 -1
- data/lib/super_settings/application/layout.html.erb +1 -1
- data/lib/super_settings/application/scripts.js +6 -6
- data/lib/super_settings/application.rb +1 -1
- data/lib/super_settings/coerce.rb +1 -0
- data/lib/super_settings/context/current.rb +23 -0
- data/lib/super_settings/context.rb +3 -0
- data/lib/super_settings/local_cache.rb +14 -0
- data/lib/super_settings/rack_application.rb +3 -1
- data/lib/super_settings/rest_api.rb +22 -3
- data/lib/super_settings/setting.rb +4 -0
- data/lib/super_settings/storage/http_storage.rb +2 -4
- data/lib/super_settings/storage/redis_storage.rb +3 -0
- data/lib/super_settings/storage/test_storage.rb +1 -0
- data/lib/super_settings/time_precision.rb +18 -0
- data/lib/super_settings.rb +3 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e851110a7e8ebb10348b536a94121aab3c6657ef7c8115f3de6fd7adcd5f1a3b
|
4
|
+
data.tar.gz: 3aa309b399ee775cf5427d5e14a0b3f143e3ba0b441da4bcf41708f7806e2c17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79ba25dcd135459a8a5332aac5c4807fb04a183e573ae761ecd76b4b079d1772895fc2917ac2fe2c26e723193020eb43dbbb50d672dfb4f9d5e89ecd4d3a3c29
|
7
|
+
data.tar.gz: 6190dca1e68e103c982696ab7075a20e39a32e4ab5cf68d213cb679e552c475feb61d4ed111d151392a1f37010c42e3b69810ebeb7d6de5ee9a46239ca1ffbdf
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,12 @@ 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.4.1
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Incoming links to the web UI now support specifying a description for the setting being edited by passing `description=description_text` in the URL hash. For example, `#edit=port&type=integer&description=Port%20number%20for%20the%20server` will open the setting with the key `port` for editing as an integer and pre-fill the description field.
|
12
|
+
|
7
13
|
## 2.4.0
|
8
14
|
|
9
15
|
### Added
|
data/README.md
CHANGED
@@ -181,10 +181,12 @@ Then go to http://localhost:3000/settings in your browser.
|
|
181
181
|
|
182
182
|
It is not required to use the bundled Web UI. You can implement your own UI using the `SuperSettings::Setting` model.
|
183
183
|
|
184
|
-
You can link directly to editing a setting by passing `#edit=key` in the URL hash. This will open the Web UI with the setting with the key `key` selected for editing.
|
184
|
+
You can link directly to editing a setting by passing `#edit=key` in the URL hash. This will open the Web UI with the setting with the key `key` selected for editing.
|
185
|
+
|
186
|
+
You can also add `type` and `description` as parameters in the URL hash to specify the values to pre-fill for the setting if it doesn't already exist. For example, `#edit=port&type=integer&description=Server+port+number` will open the setting with the key `port` for editing as an integer with the description filled in if it doesn't already exist. If the setting does exist, then the type and description will be ignored.
|
185
187
|
|
186
188
|
```html
|
187
|
-
<a href="/settings#edit=port&type=integer">Edit Port Setting</a>
|
189
|
+
<a href="/settings#edit=port&type=integer&description=Server+port+number">Edit Port Setting</a>
|
188
190
|
```
|
189
191
|
|
190
192
|
#### REST API
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.4.
|
1
|
+
2.4.1
|
@@ -76,6 +76,10 @@ module SuperSettings
|
|
76
76
|
|
77
77
|
# Render an image tag for one of the SVG images in the images directory. If the :color option
|
78
78
|
# is specified, it will be applied to the SVG image.
|
79
|
+
#
|
80
|
+
# @param name [String] the name of the icon to render
|
81
|
+
# @param options [Hash] options for the icon (style, color, etc.)
|
82
|
+
# @return [String] the HTML for the icon
|
79
83
|
def icon_image(name, options = {})
|
80
84
|
svg = ICON_SVG[name.to_s]
|
81
85
|
style = (options[:style] || {})
|
@@ -91,6 +95,16 @@ module SuperSettings
|
|
91
95
|
end
|
92
96
|
|
93
97
|
# Render an icon image as a link tag.
|
98
|
+
#
|
99
|
+
# @param icon [String] the name of the icon to render
|
100
|
+
# @param title [String] the title/tooltip for the button
|
101
|
+
# @param color [String] the color for the icon
|
102
|
+
# @param js_class [String] CSS class for JavaScript behavior
|
103
|
+
# @param url [String] the URL for the link (optional)
|
104
|
+
# @param disabled [Boolean] whether the button is disabled
|
105
|
+
# @param style [Hash] CSS styles for the icon
|
106
|
+
# @param link_style [String] CSS styles for the link
|
107
|
+
# @return [String] the HTML for the icon button
|
94
108
|
def icon_button(icon, title:, color:, js_class:, url: nil, disabled: false, style: {}, link_style: nil)
|
95
109
|
url = "#" if Coerce.blank?(url)
|
96
110
|
image = icon_image(icon, alt: title, style: ICON_BUTTON_STYLE.merge(style).merge(color: color))
|
@@ -43,7 +43,7 @@
|
|
43
43
|
</div>
|
44
44
|
</form>
|
45
45
|
|
46
|
-
<div id="super-settings-modal" class="super-settings-modal js-close-modal" aria-hidden="true"
|
46
|
+
<div id="super-settings-modal" class="super-settings-modal js-close-modal" aria-hidden="true" role="dialog">
|
47
47
|
<div class="super-settings-modal-dialog">
|
48
48
|
<button type="button" title="Close Dialog" class="super-settings-modal-close super-settings-btn-no-chrome js-close-modal">×</button>
|
49
49
|
<div class="super-settings-modal-content">
|
@@ -4,7 +4,7 @@
|
|
4
4
|
<title><%= application_name %> Settings</title>
|
5
5
|
<meta charset="utf-8">
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
-
<meta name="pinterest" content="nopin"
|
7
|
+
<meta name="pinterest" content="nopin">
|
8
8
|
<meta name="format-detection" content="telephone=no email=no date=no address=no">
|
9
9
|
<meta name="robots" content="noindex, nofollow">
|
10
10
|
<meta name="referrer" content="no-referrer-when-downgrade">
|
@@ -232,7 +232,7 @@
|
|
232
232
|
}
|
233
233
|
|
234
234
|
// Create a card with form elements for creating a new setting.
|
235
|
-
function newSettingCard(key, valueType) {
|
235
|
+
function newSettingCard(key, valueType, description) {
|
236
236
|
if (!key) {
|
237
237
|
key = "";
|
238
238
|
}
|
@@ -240,7 +240,7 @@
|
|
240
240
|
valueType = "string";
|
241
241
|
}
|
242
242
|
const randomId = "new" + Math.floor((Math.random() * 0xFFFFFFFFFFFFFF)).toString(16);
|
243
|
-
const setting = {id: randomId, key: key, value: "", value_type: valueType, new_record: true}
|
243
|
+
const setting = {id: randomId, key: key, value: "", value_type: valueType, description: description, new_record: true}
|
244
244
|
card = editSettingCard(setting);
|
245
245
|
return card;
|
246
246
|
}
|
@@ -489,8 +489,8 @@
|
|
489
489
|
}
|
490
490
|
|
491
491
|
// Add a new setting.
|
492
|
-
function addSetting(key, valueType) {
|
493
|
-
const card = addCardToContainer(newSettingCard(key, valueType));
|
492
|
+
function addSetting(key, valueType, description) {
|
493
|
+
const card = addCardToContainer(newSettingCard(key, valueType, description));
|
494
494
|
card.querySelector(".super-settings-card-key input").focus();
|
495
495
|
}
|
496
496
|
|
@@ -722,7 +722,7 @@
|
|
722
722
|
hashParams.split('&').forEach(function(param) {
|
723
723
|
const [key, value] = param.split('=', 2);
|
724
724
|
if (key && value) {
|
725
|
-
params[decodeURIComponent(key)] = decodeURIComponent(value);
|
725
|
+
params[decodeURIComponent(key)] = decodeURIComponent(value.replace(/\+/g, ' '));
|
726
726
|
}
|
727
727
|
});
|
728
728
|
|
@@ -886,7 +886,7 @@
|
|
886
886
|
if (setting) {
|
887
887
|
editSetting(setting);
|
888
888
|
} else {
|
889
|
-
addSetting(hashParams.edit, hashParams.type);
|
889
|
+
addSetting(hashParams.edit, hashParams.type, hashParams.description);
|
890
890
|
}
|
891
891
|
}
|
892
892
|
enableSaveButton();
|
@@ -29,7 +29,7 @@ module SuperSettings
|
|
29
29
|
|
30
30
|
# Render the web UI application HTML.
|
31
31
|
#
|
32
|
-
# @return [
|
32
|
+
# @return [String] the rendered HTML
|
33
33
|
def render
|
34
34
|
template = ERB.new(File.read(File.expand_path(File.join("application", "index.html.erb"), __dir__)))
|
35
35
|
html = template.result(binding)
|
@@ -5,6 +5,7 @@ require "set"
|
|
5
5
|
module SuperSettings
|
6
6
|
# Utility functions for coercing values to other data types.
|
7
7
|
class Coerce
|
8
|
+
# Set of values that should be considered false when converting to boolean.
|
8
9
|
# rubocop:disable Lint/BooleanSymbol
|
9
10
|
FALSE_VALUES = Set.new([
|
10
11
|
"0", :"0",
|
@@ -2,28 +2,51 @@
|
|
2
2
|
|
3
3
|
module SuperSettings
|
4
4
|
module Context
|
5
|
+
# Current context for maintaining consistent setting values and random numbers
|
6
|
+
# within a specific execution context.
|
5
7
|
class Current
|
6
8
|
def initialize
|
7
9
|
@context = {}
|
8
10
|
@seed = nil
|
9
11
|
end
|
10
12
|
|
13
|
+
# Check if the context includes a specific key.
|
14
|
+
#
|
15
|
+
# @param key [String] the key to check
|
16
|
+
# @return [Boolean] true if the key exists in the context
|
11
17
|
def include?(key)
|
12
18
|
@context.include?(key)
|
13
19
|
end
|
14
20
|
|
21
|
+
# Get a value from the context.
|
22
|
+
#
|
23
|
+
# @param key [String] the key to retrieve
|
24
|
+
# @return [Object] the value associated with the key
|
15
25
|
def [](key)
|
16
26
|
@context[key]
|
17
27
|
end
|
18
28
|
|
29
|
+
# Set a value in the context.
|
30
|
+
#
|
31
|
+
# @param key [String] the key to set
|
32
|
+
# @param value [Object] the value to store
|
33
|
+
# @return [Object] the stored value
|
19
34
|
def []=(key, value)
|
20
35
|
@context[key] = value
|
21
36
|
end
|
22
37
|
|
38
|
+
# Delete a value from the context.
|
39
|
+
#
|
40
|
+
# @param key [String] the key to delete
|
41
|
+
# @return [Object] the deleted value
|
23
42
|
def delete(key)
|
24
43
|
@context.delete(key)
|
25
44
|
end
|
26
45
|
|
46
|
+
# Generate a consistent random number for this context.
|
47
|
+
#
|
48
|
+
# @param max [Integer, Float, Range] the maximum value or range
|
49
|
+
# @return [Integer, Float] the random number
|
27
50
|
def rand(max = nil)
|
28
51
|
@seed ||= Random.new_seed
|
29
52
|
Random.new(@seed).rand(max || 1.0)
|
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SuperSettings
|
4
|
+
# Context classes for maintaining consistent state during execution blocks.
|
5
|
+
# These contexts ensure that settings and random values remain constant
|
6
|
+
# within a specific execution scope.
|
4
7
|
module Context
|
5
8
|
autoload :Current, File.join(__dir__, "context/current")
|
6
9
|
autoload :RackMiddleware, File.join(__dir__, "context/rack_middleware")
|
@@ -60,6 +60,7 @@ module SuperSettings
|
|
60
60
|
end
|
61
61
|
|
62
62
|
return nil if value == NOT_DEFINED
|
63
|
+
|
63
64
|
value
|
64
65
|
end
|
65
66
|
|
@@ -105,11 +106,15 @@ module SuperSettings
|
|
105
106
|
end
|
106
107
|
|
107
108
|
# Load all the settings from the database into the cache.
|
109
|
+
#
|
110
|
+
# @param asynchronous [Boolean] whether to load settings in a background thread
|
111
|
+
# @return [void]
|
108
112
|
def load_settings(asynchronous = false)
|
109
113
|
return if @refreshing
|
110
114
|
|
111
115
|
@lock.synchronize do
|
112
116
|
return if @refreshing
|
117
|
+
|
113
118
|
@refreshing = true
|
114
119
|
@next_check_at = Time.now + @refresh_interval
|
115
120
|
end
|
@@ -135,6 +140,9 @@ module SuperSettings
|
|
135
140
|
end
|
136
141
|
|
137
142
|
# Load only settings that have changed since the last load.
|
143
|
+
#
|
144
|
+
# @param asynchronous [Boolean] whether to refresh settings in a background thread
|
145
|
+
# @return [void]
|
138
146
|
def refresh(asynchronous = false)
|
139
147
|
last_refresh_time = @last_refreshed
|
140
148
|
return if last_refresh_time.nil?
|
@@ -142,8 +150,10 @@ module SuperSettings
|
|
142
150
|
|
143
151
|
@lock.synchronize do
|
144
152
|
return if @refreshing
|
153
|
+
|
145
154
|
@next_check_at = Time.now + @refresh_interval
|
146
155
|
return if @cache.empty?
|
156
|
+
|
147
157
|
@refreshing = true
|
148
158
|
end
|
149
159
|
|
@@ -166,6 +176,8 @@ module SuperSettings
|
|
166
176
|
end
|
167
177
|
|
168
178
|
# Reset the cache to an unloaded state.
|
179
|
+
#
|
180
|
+
# @return [void]
|
169
181
|
def reset
|
170
182
|
@lock.synchronize do
|
171
183
|
@cache = {}.freeze
|
@@ -189,6 +201,7 @@ module SuperSettings
|
|
189
201
|
# @api private
|
190
202
|
def update_setting(setting)
|
191
203
|
return if Coerce.blank?(setting.key)
|
204
|
+
|
192
205
|
@lock.synchronize do
|
193
206
|
@cache = @cache.merge(setting.key => setting.value)
|
194
207
|
end
|
@@ -199,6 +212,7 @@ module SuperSettings
|
|
199
212
|
def wait_for_load
|
200
213
|
loop do
|
201
214
|
return unless @refreshing
|
215
|
+
|
202
216
|
sleep(0.001)
|
203
217
|
end
|
204
218
|
end
|
@@ -11,7 +11,7 @@ module SuperSettings
|
|
11
11
|
#
|
12
12
|
# You are also responsible for implementing any CSRF protection if your authentication method
|
13
13
|
# uses stateful requests (i.e. cookies or Basic auth where browser automatically include the
|
14
|
-
# credentials on every
|
14
|
+
# credentials on every request). There are other gems available that can be integrated into
|
15
15
|
# your middleware stack to provide this feature. If you need to inject meta elements into
|
16
16
|
# the page, you can do so with the +add_to_head+ method.
|
17
17
|
class RackApplication
|
@@ -225,8 +225,10 @@ module SuperSettings
|
|
225
225
|
def check_authorization(request, write_required: false)
|
226
226
|
user = current_user(request)
|
227
227
|
return json_response(401, error: "Authentiation required") unless authenticated?(user)
|
228
|
+
|
228
229
|
allowed = (write_required ? allow_write?(user) : allow_read?(user))
|
229
230
|
return json_response(403, error: "Access denied") unless allowed
|
231
|
+
|
230
232
|
yield(user)
|
231
233
|
end
|
232
234
|
|
@@ -16,12 +16,14 @@ module SuperSettings
|
|
16
16
|
# key: string,
|
17
17
|
# value: object,
|
18
18
|
# value_type: string,
|
19
|
-
# description string,
|
19
|
+
# description: string,
|
20
20
|
# created_at: iso8601 string,
|
21
21
|
# updated_at: iso8601 string
|
22
22
|
# },
|
23
23
|
# ...
|
24
24
|
# ]
|
25
|
+
#
|
26
|
+
# @return [Hash] hash with settings array
|
25
27
|
def index
|
26
28
|
settings = Setting.active.reject(&:deleted?).sort_by(&:key)
|
27
29
|
{settings: settings.collect(&:as_json)}
|
@@ -29,6 +31,7 @@ module SuperSettings
|
|
29
31
|
|
30
32
|
# Get a setting by id.
|
31
33
|
#
|
34
|
+
# @param key [String] setting key
|
32
35
|
# @example
|
33
36
|
# GET /setting
|
34
37
|
#
|
@@ -41,10 +44,12 @@ module SuperSettings
|
|
41
44
|
# key: string,
|
42
45
|
# value: object,
|
43
46
|
# value_type: string,
|
44
|
-
# description string,
|
47
|
+
# description: string,
|
45
48
|
# created_at: iso8601 string,
|
46
49
|
# updated_at: iso8601 string
|
47
50
|
# }
|
51
|
+
#
|
52
|
+
# @return [Hash, nil] setting hash or nil if not found
|
48
53
|
def show(key)
|
49
54
|
setting = Setting.find_by_key(key)
|
50
55
|
setting.as_json if setting && !setting.deleted?
|
@@ -52,6 +57,8 @@ module SuperSettings
|
|
52
57
|
|
53
58
|
# The update operation uses a transaction to atomically update all settings.
|
54
59
|
#
|
60
|
+
# @param settings_params [Array] array of setting parameter hashes
|
61
|
+
# @param changed_by [String] identifier for who made the changes
|
55
62
|
# @example
|
56
63
|
# POST /settings
|
57
64
|
#
|
@@ -81,6 +88,8 @@ module SuperSettings
|
|
81
88
|
# or
|
82
89
|
#
|
83
90
|
# {success: false, errors: {key => [string], ...}}
|
91
|
+
#
|
92
|
+
# @return [Hash] result hash with success status and any errors
|
84
93
|
def update(settings_params, changed_by = nil)
|
85
94
|
all_valid, settings = Setting.bulk_update(Array(settings_params), changed_by)
|
86
95
|
if all_valid
|
@@ -98,6 +107,9 @@ module SuperSettings
|
|
98
107
|
|
99
108
|
# Return the history of the setting.
|
100
109
|
#
|
110
|
+
# @param key [String] setting key
|
111
|
+
# @param limit [Integer] number of history items to return
|
112
|
+
# @param offset [Integer] index to start fetching items from (most recent items are first)
|
101
113
|
# @example
|
102
114
|
# GET /setting/history
|
103
115
|
#
|
@@ -121,6 +133,8 @@ module SuperSettings
|
|
121
133
|
# previous_page_params: hash,
|
122
134
|
# next_page_params: hash
|
123
135
|
# }
|
136
|
+
#
|
137
|
+
# @return [Hash, nil] history hash or nil if setting not found
|
124
138
|
def history(key, limit: nil, offset: 0)
|
125
139
|
setting = Setting.find_by_key(key)
|
126
140
|
return nil unless setting
|
@@ -162,12 +176,15 @@ module SuperSettings
|
|
162
176
|
# {
|
163
177
|
# last_updated_at: iso8601 string
|
164
178
|
# }
|
179
|
+
#
|
180
|
+
# @return [Hash] hash with the last updated timestamp
|
165
181
|
def last_updated_at
|
166
182
|
{last_updated_at: Setting.last_updated_at.utc.iso8601(6)}
|
167
183
|
end
|
168
184
|
|
169
185
|
# Return settings that have been updated since a specified timestamp.
|
170
186
|
#
|
187
|
+
# @param time [Time, String] timestamp to check for updates since
|
171
188
|
# @example
|
172
189
|
# GET /updated_since
|
173
190
|
#
|
@@ -181,12 +198,14 @@ module SuperSettings
|
|
181
198
|
# key: string,
|
182
199
|
# value: object,
|
183
200
|
# value_type: string,
|
184
|
-
# description string,
|
201
|
+
# description: string,
|
185
202
|
# created_at: iso8601 string,
|
186
203
|
# updated_at: iso8601 string
|
187
204
|
# },
|
188
205
|
# ...
|
189
206
|
# ]
|
207
|
+
#
|
208
|
+
# @return [Hash] hash with settings array
|
190
209
|
def updated_since(time)
|
191
210
|
time = Coerce.time(time)
|
192
211
|
settings = Setting.updated_since(time).reject(&:deleted?)
|
@@ -9,6 +9,7 @@ module SuperSettings
|
|
9
9
|
# ships with storage engines for ActiveRecord, Redis, and HTTP (microservice). See the SuperSettings::Storage
|
10
10
|
# class for more details.
|
11
11
|
class Setting
|
12
|
+
# Cache key used for storing the last updated timestamp.
|
12
13
|
LAST_UPDATED_CACHE_KEY = "SuperSettings.last_updated_at"
|
13
14
|
|
14
15
|
STRING = "string"
|
@@ -266,6 +267,7 @@ module SuperSettings
|
|
266
267
|
setting = changed[key] || Setting.find_by_key(key)
|
267
268
|
unless setting
|
268
269
|
next if Coerce.present?(setting_params["delete"])
|
270
|
+
|
269
271
|
setting = Setting.new(key: setting_params["key"])
|
270
272
|
end
|
271
273
|
|
@@ -545,6 +547,7 @@ module SuperSettings
|
|
545
547
|
|
546
548
|
# Serialize to a hash that is used for rendering JSON responses.
|
547
549
|
#
|
550
|
+
# @param options [Hash] options for JSON serialization (unused but maintained for compatibility)
|
548
551
|
# @return [Hash]
|
549
552
|
def as_json(options = nil)
|
550
553
|
attributes = {
|
@@ -561,6 +564,7 @@ module SuperSettings
|
|
561
564
|
|
562
565
|
# Serialize to a JSON string.
|
563
566
|
#
|
567
|
+
# @param options [Hash] options to pass to JSON generation
|
564
568
|
# @return [String]
|
565
569
|
def to_json(options = nil)
|
566
570
|
as_json.to_json(options)
|
@@ -35,15 +35,13 @@ module SuperSettings
|
|
35
35
|
# Add headers to this hash to add them to all requests to the SuperSettings REST API.
|
36
36
|
#
|
37
37
|
# @example
|
38
|
-
#
|
39
|
-
# SuperSettings::HttpStorage.headers["Authorization"] = "Bearer 12345"
|
38
|
+
# SuperSettings::HttpStorage.headers["Authorization"] = "Bearer 12345"
|
40
39
|
attr_reader :headers
|
41
40
|
|
42
41
|
# Add query parameters to this hash to add them to all requests to the SuperSettings REST API.
|
43
42
|
#
|
44
43
|
# @example
|
45
|
-
#
|
46
|
-
# SuperSettings::HttpStorage.query_params["access_token"] = "12345"
|
44
|
+
# SuperSettings::HttpStorage.query_params["access_token"] = "12345"
|
47
45
|
attr_reader :query_params
|
48
46
|
|
49
47
|
def all
|
@@ -35,6 +35,7 @@ module SuperSettings
|
|
35
35
|
def find_all_by_key(key:, offset: 0, limit: nil)
|
36
36
|
end_index = (limit.nil? ? -1 : offset + limit - 1)
|
37
37
|
return [] unless end_index >= -1
|
38
|
+
|
38
39
|
payloads = RedisStorage.with_redis { |redis| redis.lrange("#{HISTORY_KEY_PREFIX}.#{key}", offset, end_index) }
|
39
40
|
payloads.collect do |json|
|
40
41
|
record = new(JSON.parse(json))
|
@@ -112,6 +113,7 @@ module SuperSettings
|
|
112
113
|
def find_by_key(key)
|
113
114
|
json = with_redis { |redis| redis.hget(SETTINGS_KEY, key) }
|
114
115
|
return nil unless json
|
116
|
+
|
115
117
|
record = load_from_json(json)
|
116
118
|
record unless record.deleted?
|
117
119
|
end
|
@@ -123,6 +125,7 @@ module SuperSettings
|
|
123
125
|
def last_updated_at
|
124
126
|
result = with_redis { |redis| redis.zrevrange(UPDATED_KEY, 0, 1, withscores: true).first }
|
125
127
|
return nil unless result
|
128
|
+
|
126
129
|
time_at_microseconds(result[1])
|
127
130
|
end
|
128
131
|
|
@@ -4,24 +4,42 @@ module SuperSettings
|
|
4
4
|
# Helper class for truncating timestamps to a specific precision. This is used by storage engines
|
5
5
|
# to ensure that timestamps are stored and compared with the same precision.
|
6
6
|
class TimePrecision
|
7
|
+
# The time value with applied precision.
|
7
8
|
attr_reader :time
|
8
9
|
|
10
|
+
# Create a new TimePrecision object.
|
11
|
+
#
|
12
|
+
# @param time [Time, Numeric] the time to apply precision to
|
13
|
+
# @param precision [Symbol] the precision level (:microsecond or :millisecond)
|
14
|
+
# @raise [ArgumentError] if precision is not valid
|
9
15
|
def initialize(time, precision = :microsecond)
|
10
16
|
raise ArgumentError.new("Invalid precision: #{precision}") unless valid_precision?(precision)
|
11
17
|
|
12
18
|
@time = time_with_precision(time.to_f, precision) if time
|
13
19
|
end
|
14
20
|
|
21
|
+
# Convert the time to a float.
|
22
|
+
#
|
23
|
+
# @return [Float] the time as a floating point number
|
15
24
|
def to_f
|
16
25
|
@time.to_f
|
17
26
|
end
|
18
27
|
|
19
28
|
private
|
20
29
|
|
30
|
+
# Check if the precision value is valid.
|
31
|
+
#
|
32
|
+
# @param precision [Symbol] the precision to validate
|
33
|
+
# @return [Boolean] true if precision is valid
|
21
34
|
def valid_precision?(precision)
|
22
35
|
[:microsecond, :millisecond].include?(precision)
|
23
36
|
end
|
24
37
|
|
38
|
+
# Apply the specified precision to a timestamp.
|
39
|
+
#
|
40
|
+
# @param timestamp [Float] the timestamp to apply precision to
|
41
|
+
# @param precision [Symbol] the precision level
|
42
|
+
# @return [Time] the time with applied precision
|
25
43
|
def time_with_precision(timestamp, precision)
|
26
44
|
usec = (timestamp % 1) * 1_000_000.0
|
27
45
|
if precision == :millisecond
|
data/lib/super_settings.rb
CHANGED
@@ -23,6 +23,7 @@ module SuperSettings
|
|
23
23
|
autoload :HttpClient, "super_settings/http_client"
|
24
24
|
autoload :VERSION, "super_settings/version"
|
25
25
|
|
26
|
+
# Default number of seconds between cache refresh checks.
|
26
27
|
DEFAULT_REFRESH_INTERVAL = 5.0
|
27
28
|
|
28
29
|
@local_cache = LocalCache.new(refresh_interval: DEFAULT_REFRESH_INTERVAL)
|
@@ -41,7 +42,7 @@ module SuperSettings
|
|
41
42
|
Coerce.string(val)
|
42
43
|
end
|
43
44
|
|
44
|
-
# Alias for {
|
45
|
+
# Alias for {.get} to allow using the [] operator to get a setting value.
|
45
46
|
#
|
46
47
|
# @param key [String, Symbol]
|
47
48
|
# @return [String]
|
@@ -107,6 +108,7 @@ module SuperSettings
|
|
107
108
|
val = context_setting(key)
|
108
109
|
val = default if val.nil?
|
109
110
|
return nil if val.nil?
|
111
|
+
|
110
112
|
Array(val).collect { |v| v&.to_s }
|
111
113
|
end
|
112
114
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: super_settings
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.4.
|
4
|
+
version: 2.4.1
|
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-
|
11
|
+
date: 2025-09-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|