super_settings 0.0.1.rc3 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +56 -65
- data/VERSION +1 -1
- data/app/helpers/super_settings/settings_helper.rb +1 -1
- data/lib/super_settings/application/api.js +11 -6
- data/lib/super_settings/configuration.rb +43 -13
- data/lib/super_settings/context/rack_middleware.rb +21 -0
- data/lib/super_settings/context/sidekiq_middleware.rb +41 -0
- data/lib/super_settings/context.rb +6 -0
- data/lib/super_settings/engine.rb +24 -0
- data/lib/super_settings/history_item.rb +19 -15
- data/lib/super_settings/local_cache.rb +1 -52
- data/lib/super_settings/rack_application.rb +7 -2
- data/lib/super_settings/setting.rb +40 -5
- data/lib/super_settings/storage/http_storage.rb +19 -9
- data/lib/super_settings/storage/redis_storage.rb +14 -3
- data/lib/super_settings/storage/test_storage.rb +18 -6
- data/lib/super_settings/storage.rb +3 -1
- data/lib/super_settings.rb +41 -32
- metadata +8 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f58b7aa08a4a6082168ff9fa1487f79ff84bf9374b9463d9ba3ebe4e188b6a47
|
4
|
+
data.tar.gz: b4bf73f87885acacc091e3f455a8a33a8b0baa770dd00db42a1fac6b468e5427
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 973a1ac38488a2cdc4d4cc639f9057d8a1cfd24e216cd7b8cfe4b034af5085b3e0245999b918f3aac7a95416d47fcba46f099d8312afaa2406f63bd5a17f1d25
|
7
|
+
data.tar.gz: e1922327a1c8965dd72d8b11826f398b7dd0cd4f3a3f27be7cc3aa5a81f44da925a159ae2321a122267a9605dd7fbb5fa6af909d4daeeae3cf371087b4df7a7f
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,11 @@ 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
|
+
## [1.0.1]
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- Optimize object shapes for the Ruby interpreter by declaring instance variables in constructors.
|
11
|
+
|
7
12
|
## [1.0.0]
|
8
13
|
|
9
14
|
### Added
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# SuperSettings
|
2
2
|
|
3
3
|
[![Continuous Integration](https://github.com/bdurand/super_settings/actions/workflows/continuous_integration.yml/badge.svg)](https://github.com/bdurand/super_settings/actions/workflows/continuous_integration.yml)
|
4
|
+
[![Regression Test](https://github.com/bdurand/super_settings/actions/workflows/regression_test.yml/badge.svg)](https://github.com/bdurand/super_settings/actions/workflows/regression_test.yml)
|
4
5
|
[![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
|
5
6
|
|
6
7
|
This gem provides a framework for maintaining runtime application settings. Settings are persisted in a database but cached in memory for quick, efficient access. The settings are designed so they can be updated dynamically without requiring code deployment or restarting processes. The code scales very well and can easily handle very high throughput environments.
|
@@ -19,6 +20,10 @@ SuperSettings provides a simple interface for accessing settings backed by a thr
|
|
19
20
|
|
20
21
|
There is also an out of the box Web UI and REST API for managing settings. You can specify data types for your settings (string, integer, float, boolean, datetime, or array) to ensure that values will be valid. You can also supply documentation for each setting so that it's clear what each one does and how it is used.
|
21
22
|
|
23
|
+
Changes to settings are stored whenever a setting is changed to give you an audit trail if you need it for compliance reasons. In addition, you can provide your own callbacks to execute whenever a setting is changed.
|
24
|
+
|
25
|
+
There is a companion gem [ultra_settings](https://github.com/bdurand/ultra_settings) that can be used to integrate SuperSettings into a combined configuration system alongside YAML files and environment variables.
|
26
|
+
|
22
27
|
## Usage
|
23
28
|
|
24
29
|
- [Getting Value](#getting-values)
|
@@ -51,65 +56,6 @@ SuperSettings.datetime("key") # -> returns a `Time` object
|
|
51
56
|
SuperSettings.array("key") # -> returns an array of strings
|
52
57
|
```
|
53
58
|
|
54
|
-
#### Hashes
|
55
|
-
There is also a method to get multiple settings at once structured as a hash.
|
56
|
-
|
57
|
-
```ruby
|
58
|
-
SuperSettings.structured("parent") # -> returns an hash
|
59
|
-
```
|
60
|
-
|
61
|
-
The key provided to the `SuperSettings.structured` method indicates the key prefix and constructs the hash from settings that have keys beginning with that prefix. Keys are also broken down by a delimiter so you can create nested hashes. The delimiter defaults to `"."`, but you can specify a different one with the `delimiter` keyword argument.
|
62
|
-
|
63
|
-
You can also set a maximum depth to the returned hash with the `max_depth` keyword argument.
|
64
|
-
|
65
|
-
So, if you have the following settings:
|
66
|
-
|
67
|
-
```
|
68
|
-
vendors.company_1.path = "/co1"
|
69
|
-
vendors.company_1.timeout = 5
|
70
|
-
vendors.company_2.path = "/co2"
|
71
|
-
page_size = 20
|
72
|
-
```
|
73
|
-
|
74
|
-
You would get these results:
|
75
|
-
|
76
|
-
```ruby
|
77
|
-
SuperSettings.structured("vendors")
|
78
|
-
# {
|
79
|
-
# "company_1" => {
|
80
|
-
# "path" => "/co1",
|
81
|
-
# "timeout" => 5
|
82
|
-
# },
|
83
|
-
# "company_2" => {
|
84
|
-
# "path" => "/co2"
|
85
|
-
# }
|
86
|
-
# }
|
87
|
-
|
88
|
-
SuperSettings.structured("vendors.company_1")
|
89
|
-
# {"path" => "/co1", "timeout" => 5}
|
90
|
-
|
91
|
-
SuperSettings.structured("vendors.company_2")
|
92
|
-
# {"path" => "/co2"}
|
93
|
-
|
94
|
-
# Get all the settings by omitting the key
|
95
|
-
SuperSettings.structured
|
96
|
-
# {
|
97
|
-
# "vendors" => {
|
98
|
-
# "company_1" => {"path" => "/co1", "timeout" => 5},
|
99
|
-
# "company_2" => {"path" => "/co2"}
|
100
|
-
# },
|
101
|
-
# "page_size" => 20
|
102
|
-
# }
|
103
|
-
|
104
|
-
# Limit the nesting depth of the returned hash to one level
|
105
|
-
SuperSettings.structured(max_depth: 1)
|
106
|
-
# {
|
107
|
-
# "vendors.company_1.path => "/co1",
|
108
|
-
# "vendors.company_1.timeout" => 5,
|
109
|
-
# "vendors.company_2.path" => "/co2",
|
110
|
-
# "page_size" => 20
|
111
|
-
# }
|
112
|
-
```
|
113
59
|
|
114
60
|
#### Defaults
|
115
61
|
|
@@ -132,13 +78,38 @@ SuperSettings.enabled?("enabled_users.#{id}")
|
|
132
78
|
|
133
79
|
# GOOD: use an array if there are a limited number of values
|
134
80
|
SuperSettings.array("enabled_users", []).include?(id)
|
135
|
-
|
136
|
-
# GOOD: use a hash if you need to scale to any number of values
|
137
|
-
SuperSettings.structured("enabled_users", {})["id"]
|
138
81
|
```
|
139
82
|
|
140
83
|
The cache will scale without issue to handle hundreds of settings. However, you should avoid creating thousands of settings. Because all settings are read into memory, having too many settings records can lead to performance or memory issues.
|
141
84
|
|
85
|
+
#### Request Context
|
86
|
+
|
87
|
+
You can ensure that settings won't change in a block of code by surrounding it with a `SuperSettings.context` block. Inside a `context` block, a setting will always return the same value. This can prevent race conditions where you code may branch based on a setting value.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
# This code could be unsafe since the value of the "threshold" setting could
|
91
|
+
# change after the if statement is checked.
|
92
|
+
if SuperSettings.integer("threshold") > 0
|
93
|
+
do_something(SuperSettings.integer("threshold"))
|
94
|
+
end
|
95
|
+
|
96
|
+
# With a context block, the value for the "threshold setting will always
|
97
|
+
# return the same value
|
98
|
+
SuperSettings.context do
|
99
|
+
if SuperSettings.integer("threshold") > 0
|
100
|
+
do_something(SuperSettings.integer("threshold"))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
It's a good idea to add a `context` block around your main unit of work:
|
106
|
+
|
107
|
+
- Rack application: add `SuperSettings::Context::RackMiddleware` to your middleware stack
|
108
|
+
- Sidekiq: add `SuperSettings::Context::SidekiqMiddleware` to your server middleware
|
109
|
+
- ActiveJob: add an `around_perform` callback that calls `SuperSettings.context`
|
110
|
+
|
111
|
+
In a Rails application all of these will be done automatically.
|
112
|
+
|
142
113
|
### Data Model
|
143
114
|
|
144
115
|
Each setting has a key, value, value type, and optional description. The key must be unique. The value type can be one of "string", "integer", "float", "boolean", "datetime", or "array". The array value type will always return an array of strings.
|
@@ -149,6 +120,16 @@ It is not possible to store an empty string in a setting; empty strings will be
|
|
149
120
|
|
150
121
|
A history of all settings changes is updated every time the value is changed in the `histories` association. You can also record who made the changes.
|
151
122
|
|
123
|
+
#### Callbacks
|
124
|
+
|
125
|
+
You can define custom callbacks on the `SuperSettings::Setting` model that will be called whenever a setting is changed. For example, if you needed to log all changes to you settings in your application logs, you could do something like this:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
SuperSettings::Setting.after_save do |setting|
|
129
|
+
Application.logger.info("Setting #{setting.key} changed: #{setting.changes.inspect})
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
152
133
|
#### Storage Engines
|
153
134
|
|
154
135
|
This gem abstracts out the storage engine and can support multiple storage mechanisms. It has built in support for ActiveRecord, Redis, and HTTP storage.
|
@@ -220,7 +201,7 @@ The gem ships with a Rails engine that provides easy integration with a Rails ap
|
|
220
201
|
The default storage engine for a Rails application will be the ActiveRecord storage. You need to install the database migrations first with:
|
221
202
|
|
222
203
|
```bash
|
223
|
-
rails
|
204
|
+
rails super_settings:install:migrations
|
224
205
|
```
|
225
206
|
|
226
207
|
You also need to mount the engine routes in your application's `config/routes.rb` file. The routes can be mounted under any prefix you'd like.
|
@@ -270,10 +251,15 @@ SuperSettings.configure do |config|
|
|
270
251
|
end
|
271
252
|
end
|
272
253
|
|
273
|
-
# Define a
|
254
|
+
# Define a block that returns the value that will be stored in the settings history in
|
274
255
|
# the `changed_by` column.
|
275
256
|
config.controller.define_changed_by do
|
276
|
-
current_user.
|
257
|
+
current_user.id
|
258
|
+
end
|
259
|
+
|
260
|
+
# Define a block that determines how to display the `changed_by`` value in the setting history.
|
261
|
+
config.model.define_changed_by_display do |changed_by_id|
|
262
|
+
User.find_by(id: changed_by_id)&.name
|
277
263
|
end
|
278
264
|
|
279
265
|
# You can define the storage engine for the model. This can be either done either with a Class
|
@@ -284,6 +270,11 @@ SuperSettings.configure do |config|
|
|
284
270
|
# You can also specify a cache implementation to use to cache the last updated timestamp
|
285
271
|
# for model changes. By default this will use `Rails.cache`.
|
286
272
|
# config.model.cache = Rails.cache
|
273
|
+
|
274
|
+
# You can define after_save callbacks for the model.
|
275
|
+
# config.model.after_save do |setting|
|
276
|
+
# Rail.logger.info("Setting #{setting.key} changed to #{setting.value.inspect}")
|
277
|
+
# end
|
287
278
|
end
|
288
279
|
```
|
289
280
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
1.0.1
|
@@ -19,7 +19,7 @@ module SuperSettings
|
|
19
19
|
def super_settings_application_header
|
20
20
|
config = Configuration.instance.controller
|
21
21
|
content = "#{super_settings_application_name} Settings"
|
22
|
-
if config.application_logo
|
22
|
+
if Coerce.present?(config.application_logo)
|
23
23
|
content = image_tag(config.application_logo, alt: "").concat(content)
|
24
24
|
end
|
25
25
|
if config.application_link
|
@@ -35,13 +35,19 @@
|
|
35
35
|
|
36
36
|
params = options.params
|
37
37
|
let queryParams = null;
|
38
|
-
const
|
38
|
+
const headers = new Headers();
|
39
|
+
const fetchOptions = {credentials: "same-origin", headers: headers};
|
39
40
|
const accessToken = window.sessionStorage.getItem("super_settings_access_token");
|
40
|
-
|
41
|
+
|
42
|
+
headers.set("Accept", "application/json");
|
41
43
|
if (accessToken) {
|
42
|
-
headers
|
44
|
+
headers.set("Authorization", "Bearer " + accessToken);
|
43
45
|
}
|
44
|
-
Object.
|
46
|
+
Object.entries(SuperSettingsAPI.headers).forEach(function(entry) {
|
47
|
+
const [key, value] = entry;
|
48
|
+
headers.set(key, value);
|
49
|
+
});
|
50
|
+
|
45
51
|
if (method === "POST") {
|
46
52
|
queryParams = Object.assign({}, SuperSettingsAPI.queryParams);
|
47
53
|
csrfParam = document.querySelector("meta[name=csrf-param]");
|
@@ -52,11 +58,10 @@
|
|
52
58
|
}
|
53
59
|
fetchOptions["method"] = "POST";
|
54
60
|
fetchOptions["body"] = JSON.stringify(params);
|
55
|
-
headers
|
61
|
+
headers.set("Content-Type", "application/json");
|
56
62
|
} else {
|
57
63
|
queryParams = Object.assign({}, SuperSettingsAPI.queryParams, params);
|
58
64
|
}
|
59
|
-
fetchOptions["headers"] = new Headers(headers);
|
60
65
|
const url = apiURL(path, queryParams);
|
61
66
|
|
62
67
|
fetch(url, fetchOptions)
|
@@ -22,8 +22,14 @@ module SuperSettings
|
|
22
22
|
# since it will be compatible with class reloading in a development environment.
|
23
23
|
attr_writer :superclass
|
24
24
|
|
25
|
+
def initialize
|
26
|
+
@superclass = nil
|
27
|
+
@web_ui_enabled = true
|
28
|
+
@changed_by_block = nil
|
29
|
+
end
|
30
|
+
|
25
31
|
def superclass
|
26
|
-
if
|
32
|
+
if @superclass.is_a?(String)
|
27
33
|
@superclass.constantize
|
28
34
|
else
|
29
35
|
@superclass
|
@@ -54,9 +60,6 @@ module SuperSettings
|
|
54
60
|
attr_writer :web_ui_enabled
|
55
61
|
|
56
62
|
def web_ui_enabled?
|
57
|
-
unless defined?(@web_ui_enabled)
|
58
|
-
@web_ui_enabled = true
|
59
|
-
end
|
60
63
|
!!@web_ui_enabled
|
61
64
|
end
|
62
65
|
|
@@ -87,7 +90,7 @@ module SuperSettings
|
|
87
90
|
#
|
88
91
|
# @api private
|
89
92
|
def changed_by(controller)
|
90
|
-
if
|
93
|
+
if @changed_by_block
|
91
94
|
controller.instance_eval(&@changed_by_block)
|
92
95
|
end
|
93
96
|
end
|
@@ -101,15 +104,19 @@ module SuperSettings
|
|
101
104
|
|
102
105
|
attr_writer :storage
|
103
106
|
|
107
|
+
attr_reader :after_save_blocks, :changed_by_display
|
108
|
+
|
109
|
+
def initialize
|
110
|
+
@storage = :active_record
|
111
|
+
@after_save_blocks = []
|
112
|
+
@changed_by_display = nil
|
113
|
+
end
|
114
|
+
|
104
115
|
# Specify the storage engine to use for persisting settings. The value can either be specified
|
105
116
|
# as a full class name or an underscored class name for a storage classed defined in the
|
106
117
|
# SuperSettings::Storage namespace. The default storage engine is +SuperSettings::Storage::ActiveRecord+.
|
107
118
|
def storage
|
108
|
-
|
109
|
-
@storage
|
110
|
-
else
|
111
|
-
:active_record
|
112
|
-
end
|
119
|
+
@storage || :active_record
|
113
120
|
end
|
114
121
|
|
115
122
|
# @return [Class]
|
@@ -126,6 +133,27 @@ module SuperSettings
|
|
126
133
|
end
|
127
134
|
end
|
128
135
|
end
|
136
|
+
|
137
|
+
# Add an after_save callback to the setting model. The block will be called with the
|
138
|
+
# setting object after it is saved.
|
139
|
+
#
|
140
|
+
# @yieldparam setting [SuperSettings::Setting]
|
141
|
+
def after_save(&block)
|
142
|
+
after_save_blocks << block
|
143
|
+
end
|
144
|
+
|
145
|
+
# Define how the changed_by attibute on the setting history will be displayed. The block
|
146
|
+
# will be called with the changed_by attribute and should return a string to display.
|
147
|
+
# The block will not be called if the changed_by attribute is nil.
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# define_changed_by_display { |changed_by| User.find_by(id: changed_by)&.name }
|
151
|
+
#
|
152
|
+
# @yield Block of code to call on the controller at request time
|
153
|
+
# @yieldparam changed_by [String] The value of the changed_by attribute
|
154
|
+
def define_changed_by_display(&block)
|
155
|
+
@changed_by_display = block
|
156
|
+
end
|
129
157
|
end
|
130
158
|
|
131
159
|
# Return the model specific configuration object.
|
@@ -141,6 +169,7 @@ module SuperSettings
|
|
141
169
|
def initialize
|
142
170
|
@model = Model.new
|
143
171
|
@controller = Controller.new
|
172
|
+
@deferred_configs = []
|
144
173
|
end
|
145
174
|
|
146
175
|
# Defer the execution of a block that will be yielded to with the config object. This
|
@@ -149,15 +178,16 @@ module SuperSettings
|
|
149
178
|
#
|
150
179
|
# @api private
|
151
180
|
def defer(&block)
|
152
|
-
@
|
181
|
+
@deferred_configs << block
|
153
182
|
end
|
154
183
|
|
155
184
|
# Call the block deferred during initialization.
|
156
185
|
#
|
157
186
|
# @api private
|
158
187
|
def call
|
159
|
-
@
|
160
|
-
|
188
|
+
while (block = @deferred_configs.shift)
|
189
|
+
block&.call(self)
|
190
|
+
end
|
161
191
|
end
|
162
192
|
end
|
163
193
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
module Context
|
5
|
+
# Rack middleware you can use to add a context to your requests so that
|
6
|
+
# settings are not changed during request execution.
|
7
|
+
#
|
8
|
+
# This middleware is automatically added to Rails applications.
|
9
|
+
class RackMiddleware
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
SuperSettings.context do
|
16
|
+
@app.call(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
module Context
|
5
|
+
# Sidekiq middleware you can use to add a context to your jobs so that
|
6
|
+
# settings are not changed during job execution.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# require "super_settings/context/sidekiq_middleware"
|
10
|
+
#
|
11
|
+
# Sidekiq.configure_server do |config|
|
12
|
+
# config.server_middleware do |chain|
|
13
|
+
# chain.add SuperSettings::Context::SidekiqMiddleware
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# You can disable the context by setting the `super_settings_context` key
|
18
|
+
# to `false` in the job payload.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# class MyWorker
|
22
|
+
# include Sidekiq::Worker
|
23
|
+
# sidekiq_options super_settings_context: false
|
24
|
+
# end
|
25
|
+
class SidekiqMiddleware
|
26
|
+
if defined?(Sidekiq::ServerMiddleware)
|
27
|
+
include Sidekiq::ServerMiddleware
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(job_instance, job_payload, queue)
|
31
|
+
if job_payload["super_settings_context"] == false
|
32
|
+
yield
|
33
|
+
else
|
34
|
+
SuperSettings.context do
|
35
|
+
yield
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -7,6 +7,26 @@ module SuperSettings
|
|
7
7
|
class Engine < Rails::Engine
|
8
8
|
isolate_namespace ::SuperSettings
|
9
9
|
|
10
|
+
initializer("SuperSettings") do
|
11
|
+
Rails.configuration.middleware.unshift(SuperSettings::Context::RackMiddleware)
|
12
|
+
|
13
|
+
if defined?(ActiveJob::Base.around_perform)
|
14
|
+
ActiveJob::Base.around_perform do |job, block|
|
15
|
+
SuperSettings.context(&block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if defined?(Sidekiq.server?) && Sidekiq.server?
|
20
|
+
require_relative "context/sidekiq_middleware"
|
21
|
+
|
22
|
+
Sidekiq.configure_server do |sidekiq_config|
|
23
|
+
sidekiq_config.server_middleware do |chain|
|
24
|
+
chain.prepend(SuperSettings::Context::SidekiqMiddleware)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
10
30
|
config.after_initialize do
|
11
31
|
# Call the deferred initialization block.
|
12
32
|
configuration = Configuration.instance
|
@@ -46,6 +66,10 @@ module SuperSettings
|
|
46
66
|
Setting.cache = (configuration.model.cache || Rails.cache)
|
47
67
|
Setting.storage = configuration.model.storage_class
|
48
68
|
|
69
|
+
configuration.model.after_save_blocks.each do |block|
|
70
|
+
Setting.after_save(&block)
|
71
|
+
end
|
72
|
+
|
49
73
|
if !SuperSettings.loaded?
|
50
74
|
begin
|
51
75
|
SuperSettings.load_settings
|
@@ -9,26 +9,30 @@ module SuperSettings
|
|
9
9
|
attr_accessor :key, :value, :changed_by, :created_at
|
10
10
|
attr_writer :deleted
|
11
11
|
|
12
|
+
def initialize(*)
|
13
|
+
@deleted = false
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
12
17
|
def deleted?
|
13
|
-
|
14
|
-
!!(defined?(@deleted) && @deleted)
|
18
|
+
!!@deleted
|
15
19
|
end
|
16
20
|
|
17
|
-
# The
|
18
|
-
#
|
19
|
-
#
|
21
|
+
# The display value for the changed_by attribute. This method can be overridden
|
22
|
+
# in the configuration by calling `model.define_changed_by_display` with the block to use
|
23
|
+
# to get the display value for the changed_by attribute. The default value is
|
24
|
+
# the changed_by attribute itself.
|
20
25
|
#
|
21
|
-
# @
|
22
|
-
# class SuperSettings::HistoryItem
|
23
|
-
# def changed_by_display
|
24
|
-
# user = User.find_by(id: changed_by) if changed_by
|
25
|
-
# user ? user.name : changed_by
|
26
|
-
# end
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# @return [String]
|
26
|
+
# @return [String, nil]
|
30
27
|
def changed_by_display
|
31
|
-
changed_by
|
28
|
+
return changed_by if changed_by.nil?
|
29
|
+
|
30
|
+
display_proc = Configuration.instance.model.changed_by_display
|
31
|
+
if display_proc && !changed_by.nil?
|
32
|
+
display_proc.call(changed_by) || changed_by
|
33
|
+
else
|
34
|
+
changed_by
|
35
|
+
end
|
32
36
|
end
|
33
37
|
end
|
34
38
|
end
|
@@ -54,7 +54,6 @@ module SuperSettings
|
|
54
54
|
@lock.synchronize do
|
55
55
|
# For case where one thread could be iterating over the cache while it's updated causing an error
|
56
56
|
@cache = @cache.merge(key => value).freeze
|
57
|
-
@hashes = {}
|
58
57
|
end
|
59
58
|
end
|
60
59
|
end
|
@@ -64,53 +63,6 @@ module SuperSettings
|
|
64
63
|
value
|
65
64
|
end
|
66
65
|
|
67
|
-
# Return the setting as structured data. The keys will be split by the specified delimiter
|
68
|
-
# to create a nested hash.
|
69
|
-
#
|
70
|
-
# @example
|
71
|
-
# Setting with key "a.b.c" and value 1 becomes
|
72
|
-
#
|
73
|
-
# {
|
74
|
-
# "a" => {
|
75
|
-
# "b" => {
|
76
|
-
# "c" => 1
|
77
|
-
# }
|
78
|
-
# }
|
79
|
-
# }
|
80
|
-
#
|
81
|
-
# See SuperSettings.structured for more details.
|
82
|
-
def structured(key = nil, delimiter: ".", max_depth: nil)
|
83
|
-
key = key.to_s
|
84
|
-
cache_key = [key, delimiter, max_depth]
|
85
|
-
cached_value = @hashes[cache_key]
|
86
|
-
return cached_value if cached_value
|
87
|
-
|
88
|
-
flattened = to_h
|
89
|
-
root_key = ""
|
90
|
-
if Coerce.present?(key)
|
91
|
-
root_key = "#{key}#{delimiter}"
|
92
|
-
reduced_hash = {}
|
93
|
-
flattened.each do |k, v|
|
94
|
-
if k.start_with?(root_key)
|
95
|
-
reduced_hash[k[root_key.length, k.length]] = v
|
96
|
-
end
|
97
|
-
end
|
98
|
-
flattened = reduced_hash
|
99
|
-
end
|
100
|
-
|
101
|
-
structured_hash = {}
|
102
|
-
flattened.each do |key, value|
|
103
|
-
set_nested_hash_value(structured_hash, key, value, 0, delimiter: delimiter, max_depth: max_depth)
|
104
|
-
end
|
105
|
-
|
106
|
-
deep_freeze_hash(structured_hash)
|
107
|
-
@lock.synchronize do
|
108
|
-
@hashes[cache_key] = structured_hash
|
109
|
-
end
|
110
|
-
|
111
|
-
structured_hash
|
112
|
-
end
|
113
|
-
|
114
66
|
# Check if the cache includes a key. Note that this will return true if you have tried
|
115
67
|
# to fetch a non-existent key since the cache will store that as undefined. This method
|
116
68
|
# is provided for testing purposes.
|
@@ -217,7 +169,6 @@ module SuperSettings
|
|
217
169
|
def reset
|
218
170
|
@lock.synchronize do
|
219
171
|
@cache = {}.freeze
|
220
|
-
@hashes = {}
|
221
172
|
@last_refreshed = nil
|
222
173
|
@next_check_at = Time.now + @refresh_interval
|
223
174
|
@refreshing = false
|
@@ -240,7 +191,6 @@ module SuperSettings
|
|
240
191
|
return if Coerce.blank?(setting.key)
|
241
192
|
@lock.synchronize do
|
242
193
|
@cache = @cache.merge(setting.key => setting.value)
|
243
|
-
@hashes = {}
|
244
194
|
end
|
245
195
|
end
|
246
196
|
|
@@ -277,7 +227,7 @@ module SuperSettings
|
|
277
227
|
end
|
278
228
|
load_settings(Setting.storage.load_asynchronous?)
|
279
229
|
elsif Time.now >= @next_check_at
|
280
|
-
refresh
|
230
|
+
refresh(Setting.storage.load_asynchronous?)
|
281
231
|
end
|
282
232
|
end
|
283
233
|
|
@@ -287,7 +237,6 @@ module SuperSettings
|
|
287
237
|
@last_refreshed = refreshed_at_time
|
288
238
|
@refreshing = false
|
289
239
|
@cache = block.call.freeze
|
290
|
-
@hashes = {}
|
291
240
|
end
|
292
241
|
end
|
293
242
|
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack"
|
4
|
-
|
5
3
|
module SuperSettings
|
6
4
|
# Rack middleware for serving the REST API. See SuperSettings::RestAPI for more details on usage.
|
7
5
|
#
|
@@ -39,6 +37,13 @@ module SuperSettings
|
|
39
37
|
# end
|
40
38
|
# end
|
41
39
|
def initialize(app = nil, path_prefix = "/", &block)
|
40
|
+
# Requiring rack here so that the gem does not have a hard dependency on it.
|
41
|
+
begin
|
42
|
+
require "rack"
|
43
|
+
rescue LoadError
|
44
|
+
raise LoadError, "SuperSettings::RackApplication requires the rack gem"
|
45
|
+
end
|
46
|
+
|
42
47
|
@app = app
|
43
48
|
@path_prefix = path_prefix.to_s.chomp("/")
|
44
49
|
instance_eval(&block) if block
|
@@ -22,6 +22,9 @@ module SuperSettings
|
|
22
22
|
|
23
23
|
ARRAY_DELIMITER = /[\n\r]+/.freeze
|
24
24
|
|
25
|
+
NOT_SET = Object.new.freeze
|
26
|
+
private_constant :NOT_SET
|
27
|
+
|
25
28
|
# Exception raised if you try to save with invalid data.
|
26
29
|
class InvalidRecordError < StandardError
|
27
30
|
end
|
@@ -33,6 +36,9 @@ module SuperSettings
|
|
33
36
|
# and is cleared after the record is saved.
|
34
37
|
attr_accessor :changed_by
|
35
38
|
|
39
|
+
@storage = NOT_SET
|
40
|
+
@after_save_blocks = []
|
41
|
+
|
36
42
|
class << self
|
37
43
|
# Set a cache to use for caching values. This feature is optional. The cache must respond
|
38
44
|
# to +delete(key)+ and +fetch(key, &block)+. If you are running in a Rails environment,
|
@@ -42,18 +48,31 @@ module SuperSettings
|
|
42
48
|
# Set the storage class to use for persisting data.
|
43
49
|
attr_writer :storage
|
44
50
|
|
51
|
+
attr_reader :after_save_blocks
|
52
|
+
|
45
53
|
# @return [Class] The storage class to use for persisting data.
|
46
54
|
# @api private
|
47
55
|
def storage
|
48
|
-
if
|
49
|
-
|
50
|
-
|
51
|
-
|
56
|
+
if @storage == NOT_SET
|
57
|
+
if defined?(::SuperSettings::Storage::ActiveRecordStorage)
|
58
|
+
::SuperSettings::Storage::ActiveRecordStorage
|
59
|
+
else
|
60
|
+
raise ArgumentError.new("No storage class defined for #{name}")
|
61
|
+
end
|
52
62
|
else
|
53
|
-
|
63
|
+
@storage
|
54
64
|
end
|
55
65
|
end
|
56
66
|
|
67
|
+
# Add a block of code that will be called when a setting is saved. The block will be
|
68
|
+
# called with a Setting object. The object will have been saved, but the `changes`
|
69
|
+
# hash will still be set indicating what was changed. You can define multiple after_save blocks.
|
70
|
+
#
|
71
|
+
# @yieldparam setting [SuperSetting::Setting]
|
72
|
+
def after_save(&block)
|
73
|
+
after_save_blocks << block
|
74
|
+
end
|
75
|
+
|
57
76
|
# Create a new setting with the specified attributes.
|
58
77
|
#
|
59
78
|
# @param attributes [Hash] hash of attribute names and values
|
@@ -433,6 +452,7 @@ module SuperSettings
|
|
433
452
|
|
434
453
|
begin
|
435
454
|
self.class.clear_last_updated_cache
|
455
|
+
call_after_save_callbacks
|
436
456
|
ensure
|
437
457
|
clear_changes
|
438
458
|
end
|
@@ -508,6 +528,15 @@ module SuperSettings
|
|
508
528
|
as_json.to_json(options)
|
509
529
|
end
|
510
530
|
|
531
|
+
# Get hash of attribute changes. The hash keys are the names of attributes that
|
532
|
+
# have changed and the values are an array with [old value, new value]. The keys
|
533
|
+
# will be one of key, raw_value, value_type, description, deleted, created_at, or updated_at.
|
534
|
+
#
|
535
|
+
# @return [Hash<String, Array>]
|
536
|
+
def changes
|
537
|
+
@changes.dup
|
538
|
+
end
|
539
|
+
|
511
540
|
private
|
512
541
|
|
513
542
|
# Coerce a value for the appropriate value type.
|
@@ -618,5 +647,11 @@ module SuperSettings
|
|
618
647
|
end
|
619
648
|
attribute_errors << "#{attribute.tr("_", " ")} #{message}"
|
620
649
|
end
|
650
|
+
|
651
|
+
def call_after_save_callbacks
|
652
|
+
self.class.after_save_blocks.each do |block|
|
653
|
+
block.call(self)
|
654
|
+
end
|
655
|
+
end
|
621
656
|
end
|
622
657
|
end
|
@@ -14,6 +14,9 @@ module SuperSettings
|
|
14
14
|
DEFAULT_HEADERS = {"Accept" => "application/json"}.freeze
|
15
15
|
DEFAULT_TIMEOUT = 5.0
|
16
16
|
|
17
|
+
@headers = {}
|
18
|
+
@query_params = {}
|
19
|
+
|
17
20
|
attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
|
18
21
|
|
19
22
|
class Error < StandardError
|
@@ -36,12 +39,17 @@ module SuperSettings
|
|
36
39
|
|
37
40
|
attr_accessor :key, :value, :changed_by, :deleted
|
38
41
|
|
42
|
+
def initialize(*)
|
43
|
+
@deleted = false
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
39
47
|
def created_at=(val)
|
40
48
|
@created_at = SuperSettings::Coerce.time(val)
|
41
49
|
end
|
42
50
|
|
43
51
|
def deleted?
|
44
|
-
|
52
|
+
!!@deleted
|
45
53
|
end
|
46
54
|
end
|
47
55
|
|
@@ -75,13 +83,9 @@ module SuperSettings
|
|
75
83
|
|
76
84
|
attr_accessor :timeout
|
77
85
|
|
78
|
-
|
79
|
-
@headers ||= {}
|
80
|
-
end
|
86
|
+
attr_reader :headers
|
81
87
|
|
82
|
-
|
83
|
-
@query_params ||= {}
|
84
|
-
end
|
88
|
+
attr_reader :query_params
|
85
89
|
|
86
90
|
protected
|
87
91
|
|
@@ -178,6 +182,12 @@ module SuperSettings
|
|
178
182
|
end
|
179
183
|
end
|
180
184
|
|
185
|
+
def initialize(*)
|
186
|
+
@persisted = false
|
187
|
+
@deleted = false
|
188
|
+
super
|
189
|
+
end
|
190
|
+
|
181
191
|
def save!
|
182
192
|
payload = {key: key}
|
183
193
|
if deleted?
|
@@ -248,11 +258,11 @@ module SuperSettings
|
|
248
258
|
end
|
249
259
|
|
250
260
|
def deleted?
|
251
|
-
|
261
|
+
!!@deleted
|
252
262
|
end
|
253
263
|
|
254
264
|
def persisted?
|
255
|
-
|
265
|
+
!!@persisted
|
256
266
|
end
|
257
267
|
|
258
268
|
private
|
@@ -65,6 +65,11 @@ module SuperSettings
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
def initialize(*)
|
69
|
+
@deleted = false
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
68
73
|
def created_at=(val)
|
69
74
|
@created_at = SuperSettings::Coerce.time(val)
|
70
75
|
end
|
@@ -77,7 +82,7 @@ module SuperSettings
|
|
77
82
|
end
|
78
83
|
|
79
84
|
def deleted?
|
80
|
-
|
85
|
+
!!@deleted
|
81
86
|
end
|
82
87
|
|
83
88
|
private
|
@@ -184,6 +189,12 @@ module SuperSettings
|
|
184
189
|
end
|
185
190
|
end
|
186
191
|
|
192
|
+
def initialize(*)
|
193
|
+
@deleted = false
|
194
|
+
@persisted = false
|
195
|
+
super
|
196
|
+
end
|
197
|
+
|
187
198
|
def history(limit: nil, offset: 0)
|
188
199
|
HistoryStorage.find_all_by_key(key: key, limit: limit, offset: offset).collect do |record|
|
189
200
|
HistoryItem.new(key: key, value: record.value, changed_by: record.changed_by, created_at: record.created_at, deleted: record.deleted?)
|
@@ -242,11 +253,11 @@ module SuperSettings
|
|
242
253
|
end
|
243
254
|
|
244
255
|
def deleted?
|
245
|
-
|
256
|
+
!!@deleted
|
246
257
|
end
|
247
258
|
|
248
259
|
def persisted?
|
249
|
-
|
260
|
+
!!@persisted
|
250
261
|
end
|
251
262
|
|
252
263
|
private
|
@@ -13,13 +13,13 @@ module SuperSettings
|
|
13
13
|
attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
|
14
14
|
attr_accessor :changed_by
|
15
15
|
|
16
|
+
@settings = {}
|
17
|
+
@history = {}
|
18
|
+
|
16
19
|
class << self
|
17
|
-
|
18
|
-
@settings ||= {}
|
19
|
-
end
|
20
|
+
attr_reader :settings
|
20
21
|
|
21
22
|
def history(key)
|
22
|
-
@history ||= {}
|
23
23
|
items = @history[key]
|
24
24
|
unless items
|
25
25
|
items = []
|
@@ -68,6 +68,18 @@ module SuperSettings
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
+
def initialize(*)
|
72
|
+
@original_key = nil
|
73
|
+
@raw_value = nil
|
74
|
+
@created_at = nil
|
75
|
+
@updated_at = nil
|
76
|
+
@description = nil
|
77
|
+
@value_type = nil
|
78
|
+
@deleted = false
|
79
|
+
@persisted = false
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
71
83
|
def history(limit: nil, offset: 0)
|
72
84
|
items = self.class.history(key)
|
73
85
|
items[offset, limit || items.length].collect do |attributes|
|
@@ -121,11 +133,11 @@ module SuperSettings
|
|
121
133
|
end
|
122
134
|
|
123
135
|
def deleted?
|
124
|
-
|
136
|
+
!!@deleted
|
125
137
|
end
|
126
138
|
|
127
139
|
def persisted?
|
128
|
-
|
140
|
+
!!@persisted
|
129
141
|
end
|
130
142
|
|
131
143
|
private
|
@@ -10,6 +10,8 @@ module SuperSettings
|
|
10
10
|
def self.included(base)
|
11
11
|
base.extend(ClassMethods)
|
12
12
|
base.include(Attributes) unless base.instance_methods.include?(:attributes=)
|
13
|
+
|
14
|
+
base.instance_variable_set(:@load_asynchronous, nil)
|
13
15
|
end
|
14
16
|
|
15
17
|
module ClassMethods
|
@@ -76,7 +78,7 @@ module SuperSettings
|
|
76
78
|
#
|
77
79
|
# @return [Boolean]
|
78
80
|
def load_asynchronous?
|
79
|
-
!!(
|
81
|
+
!!(@load_asynchronous.nil? ? default_load_asynchronous? : @load_asynchronous)
|
80
82
|
end
|
81
83
|
|
82
84
|
# Set to true to force loading setting asynchronously in a background thread.
|
data/lib/super_settings.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require_relative "super_settings/application"
|
4
4
|
require_relative "super_settings/coerce"
|
5
5
|
require_relative "super_settings/configuration"
|
6
|
+
require_relative "super_settings/context"
|
7
|
+
require_relative "super_settings/context/rack_middleware"
|
6
8
|
require_relative "super_settings/local_cache"
|
7
9
|
require_relative "super_settings/rest_api"
|
8
10
|
require_relative "super_settings/rack_application"
|
@@ -21,6 +23,8 @@ end
|
|
21
23
|
module SuperSettings
|
22
24
|
DEFAULT_REFRESH_INTERVAL = 5.0
|
23
25
|
|
26
|
+
@local_cache = LocalCache.new(refresh_interval: DEFAULT_REFRESH_INTERVAL)
|
27
|
+
|
24
28
|
class << self
|
25
29
|
# Get a setting value cast to a string.
|
26
30
|
#
|
@@ -28,7 +32,7 @@ module SuperSettings
|
|
28
32
|
# @param default [String] value to return if the setting value is nil
|
29
33
|
# @return [String]
|
30
34
|
def get(key, default = nil)
|
31
|
-
val =
|
35
|
+
val = context_setting(key)
|
32
36
|
val.nil? ? default : val.to_s
|
33
37
|
end
|
34
38
|
|
@@ -46,7 +50,7 @@ module SuperSettings
|
|
46
50
|
# @param default [Integer] value to return if the setting value is nil
|
47
51
|
# @return [Integer]
|
48
52
|
def integer(key, default = nil)
|
49
|
-
val =
|
53
|
+
val = context_setting(key)
|
50
54
|
(val.nil? ? default : val)&.to_i
|
51
55
|
end
|
52
56
|
|
@@ -56,7 +60,7 @@ module SuperSettings
|
|
56
60
|
# @param default [Numeric] value to return if the setting value is nil
|
57
61
|
# @return [Float]
|
58
62
|
def float(key, default = nil)
|
59
|
-
val =
|
63
|
+
val = context_setting(key)
|
60
64
|
(val.nil? ? default : val)&.to_f
|
61
65
|
end
|
62
66
|
|
@@ -66,7 +70,7 @@ module SuperSettings
|
|
66
70
|
# @param default [Boolean] value to return if the setting value is nil
|
67
71
|
# @return [Boolean]
|
68
72
|
def enabled?(key, default = false)
|
69
|
-
val =
|
73
|
+
val = context_setting(key)
|
70
74
|
Coerce.boolean(val.nil? ? default : val)
|
71
75
|
end
|
72
76
|
|
@@ -85,7 +89,7 @@ module SuperSettings
|
|
85
89
|
# @param default [Time] value to return if the setting value is nil
|
86
90
|
# @return [Time]
|
87
91
|
def datetime(key, default = nil)
|
88
|
-
val =
|
92
|
+
val = context_setting(key)
|
89
93
|
Coerce.time(val.nil? ? default : val)
|
90
94
|
end
|
91
95
|
|
@@ -95,36 +99,12 @@ module SuperSettings
|
|
95
99
|
# @param default [Array] value to return if the setting value is nil
|
96
100
|
# @return [Array]
|
97
101
|
def array(key, default = nil)
|
98
|
-
val =
|
102
|
+
val = context_setting(key)
|
99
103
|
val = default if val.nil?
|
100
104
|
return nil if val.nil?
|
101
105
|
Array(val).collect { |v| v&.to_s }
|
102
106
|
end
|
103
107
|
|
104
|
-
# Get setting values cast to a hash. This method can be used to cast the flat setting key/value
|
105
|
-
# store into a structured data store. It uses a delimiter to define how keys are nested which
|
106
|
-
# defaults to a dot.
|
107
|
-
#
|
108
|
-
# If, for example, you have three keys in you settings +A.B1.C1 = 1+, +A.B1.C2 = 2+, and +A.B2.C3 = 3+, the
|
109
|
-
# nested structure will be:
|
110
|
-
#
|
111
|
-
# +{"A" => {"B1" => {"C1" => 1, "C2" => 2}, "B2" => {"C3" => 3}}}+
|
112
|
-
#
|
113
|
-
# This whole hash would be returned if you called +hash+ without any key. If you called it with the
|
114
|
-
# key "A.B1", it would return
|
115
|
-
#
|
116
|
-
# +{"C1" => 1, "C2" => 2}+
|
117
|
-
#
|
118
|
-
# @param key [String, Symbol] the prefix patter to fetch keys for; default to returning all settings
|
119
|
-
# @param default [Hash] value to return if the setting value is nil
|
120
|
-
# @param delimiter [String] the delimiter to use to define nested keys in the hash; defaults to "."
|
121
|
-
# @return [Hash]
|
122
|
-
def structured(key = nil, default = nil, delimiter: ".", max_depth: nil)
|
123
|
-
value = local_cache.structured(key, delimiter: delimiter, max_depth: max_depth)
|
124
|
-
return (default || {}) if value.empty?
|
125
|
-
value
|
126
|
-
end
|
127
|
-
|
128
108
|
# Create settings and update the local cache with the values. If a block is given, then the
|
129
109
|
# value will be reverted at the end of the block. This method can be used in tests when you
|
130
110
|
# need to inject a specific value into your settings.
|
@@ -148,6 +128,7 @@ module SuperSettings
|
|
148
128
|
setting.save!
|
149
129
|
local_cache.load_settings unless local_cache.loaded?
|
150
130
|
local_cache.update_setting(setting)
|
131
|
+
|
151
132
|
if block_given?
|
152
133
|
yield
|
153
134
|
end
|
@@ -161,6 +142,19 @@ module SuperSettings
|
|
161
142
|
end
|
162
143
|
end
|
163
144
|
|
145
|
+
# Define a block where settings will remain unchanged. This is useful to
|
146
|
+
# prevent settings from changing while you are in the middle of a block of
|
147
|
+
# code that depends on the settings.
|
148
|
+
def context(&block)
|
149
|
+
reset_context = Thread.current[:super_settings_context].nil?
|
150
|
+
begin
|
151
|
+
Thread.current[:super_settings_context] ||= {}
|
152
|
+
yield
|
153
|
+
ensure
|
154
|
+
Thread.current[:super_settings_context] = nil if reset_context
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
164
158
|
# Load the settings from the database into the in memory cache.
|
165
159
|
#
|
166
160
|
# @return [void]
|
@@ -227,8 +221,23 @@ module SuperSettings
|
|
227
221
|
|
228
222
|
private
|
229
223
|
|
230
|
-
|
231
|
-
|
224
|
+
attr_reader :local_cache
|
225
|
+
|
226
|
+
def current_context
|
227
|
+
Thread.current[:super_settings_context]
|
228
|
+
end
|
229
|
+
|
230
|
+
def context_setting(key)
|
231
|
+
key = key.to_s
|
232
|
+
context = current_context
|
233
|
+
if context
|
234
|
+
unless context.include?(key)
|
235
|
+
context[key] = local_cache[key]
|
236
|
+
end
|
237
|
+
context[key]
|
238
|
+
else
|
239
|
+
local_cache[key]
|
240
|
+
end
|
232
241
|
end
|
233
242
|
end
|
234
243
|
end
|
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:
|
4
|
+
version: 1.0.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: 2023-
|
11
|
+
date: 2023-11-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -56,6 +56,9 @@ files:
|
|
56
56
|
- lib/super_settings/attributes.rb
|
57
57
|
- lib/super_settings/coerce.rb
|
58
58
|
- lib/super_settings/configuration.rb
|
59
|
+
- lib/super_settings/context.rb
|
60
|
+
- lib/super_settings/context/rack_middleware.rb
|
61
|
+
- lib/super_settings/context/sidekiq_middleware.rb
|
59
62
|
- lib/super_settings/controller_actions.rb
|
60
63
|
- lib/super_settings/engine.rb
|
61
64
|
- lib/super_settings/history_item.rb
|
@@ -85,11 +88,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
88
|
version: '2.5'
|
86
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
90
|
requirements:
|
88
|
-
- - "
|
91
|
+
- - ">="
|
89
92
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
93
|
+
version: '0'
|
91
94
|
requirements: []
|
92
|
-
rubygems_version: 3.
|
95
|
+
rubygems_version: 3.4.12
|
93
96
|
signing_key:
|
94
97
|
specification_version: 4
|
95
98
|
summary: Fast access runtime settings for a Rails application with an included UI
|