super_settings 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +34 -2
- data/README.md +121 -16
- data/VERSION +1 -1
- data/app/helpers/super_settings/settings_helper.rb +13 -3
- data/app/views/layouts/super_settings/settings.html.erb +1 -1
- data/config/routes.rb +1 -1
- data/db/migrate/20210414004553_create_super_settings.rb +1 -7
- data/lib/super_settings/application/api.js +4 -1
- data/lib/super_settings/application/helper.rb +56 -17
- data/lib/super_settings/application/images/arrow-down-short.svg +3 -0
- data/lib/super_settings/application/images/arrow-up-short.svg +3 -0
- data/lib/super_settings/application/images/info-circle.svg +4 -0
- data/lib/super_settings/application/images/pencil-square.svg +4 -0
- data/lib/super_settings/application/images/plus.svg +3 -1
- data/lib/super_settings/application/images/trash3.svg +3 -0
- data/lib/super_settings/application/images/x-circle.svg +4 -0
- data/lib/super_settings/application/index.html.erb +54 -37
- data/lib/super_settings/application/layout.html.erb +5 -2
- data/lib/super_settings/application/layout_styles.css +7 -151
- data/lib/super_settings/application/layout_vars.css.erb +21 -0
- data/lib/super_settings/application/scripts.js +100 -21
- data/lib/super_settings/application/style_vars.css.erb +62 -0
- data/lib/super_settings/application/styles.css +183 -14
- data/lib/super_settings/application.rb +18 -11
- data/lib/super_settings/attributes.rb +1 -8
- data/lib/super_settings/configuration.rb +9 -0
- data/lib/super_settings/context/current.rb +33 -0
- data/lib/super_settings/context.rb +3 -0
- data/lib/super_settings/controller_actions.rb +2 -2
- data/lib/super_settings/engine.rb +1 -3
- data/lib/super_settings/history_item.rb +1 -1
- data/lib/super_settings/http_client.rb +165 -0
- data/lib/super_settings/local_cache.rb +0 -15
- data/lib/super_settings/rack_application.rb +3 -3
- data/lib/super_settings/rest_api.rb +5 -4
- data/lib/super_settings/setting.rb +14 -3
- data/lib/super_settings/storage/active_record_storage/models.rb +28 -0
- data/lib/super_settings/storage/active_record_storage.rb +10 -20
- data/lib/super_settings/storage/history_attributes.rb +31 -0
- data/lib/super_settings/storage/http_storage.rb +60 -184
- data/lib/super_settings/storage/json_storage.rb +201 -0
- data/lib/super_settings/storage/mongodb_storage.rb +238 -0
- data/lib/super_settings/storage/redis_storage.rb +50 -111
- data/lib/super_settings/storage/s3_storage.rb +165 -0
- data/lib/super_settings/storage/storage_attributes.rb +64 -0
- data/lib/super_settings/storage/test_storage.rb +3 -5
- data/lib/super_settings/storage/transaction.rb +67 -0
- data/lib/super_settings/storage.rb +17 -8
- data/lib/super_settings/time_precision.rb +36 -0
- data/lib/super_settings.rb +48 -13
- data/super_settings.gemspec +11 -2
- metadata +30 -12
- data/lib/super_settings/application/images/edit.svg +0 -1
- data/lib/super_settings/application/images/info.svg +0 -1
- data/lib/super_settings/application/images/slash.svg +0 -1
- data/lib/super_settings/application/images/trash.svg +0 -1
- /data/{MIT-LICENSE → MIT-LICENSE.txt} +0 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "aws-sdk-s3"
|
4
|
+
|
5
|
+
module SuperSettings
|
6
|
+
module Storage
|
7
|
+
# Storage backend for storing the settings in an S3 object. This should work with any S3-compatible
|
8
|
+
# storage service.
|
9
|
+
class S3Storage < JSONStorage
|
10
|
+
SETTINGS_FILE = "settings.json"
|
11
|
+
HISTORY_FILE_SUFFIX = ".history.json"
|
12
|
+
DEFAULT_PATH = "super_settings"
|
13
|
+
|
14
|
+
# Configuration for the S3 storage backend.
|
15
|
+
#
|
16
|
+
# * access_key_id - The AWS access key ID. Defaults to the SUPER_SETTINGS_AWS_ACCESS_KEY_ID
|
17
|
+
# or AWS_ACCESS_KEY_ID environment variable or whatever is set in the `Aws.config` object.
|
18
|
+
# * secret_access_key - The AWS secret access key. Defaults to the SUPER_SETTINGS_AWS_SECRET_ACCESS_KEY
|
19
|
+
# or AWS_SECRET_ACCESS_KEY environment variable or whatever is set in the `Aws.config` object.
|
20
|
+
# * region - The AWS region. Defaults to the SUPER_SETTINGS_AWS_REGION or AWS_REGION environment variable.
|
21
|
+
# This is required for AWS S3 but may be optional for S3-compatible services.
|
22
|
+
# * endpoint - The S3 endpoint URL. This is optional and should only be used for S3-compatible services.
|
23
|
+
# Defaults to the SUPER_SETTINGS_AWS_ENDPOINT or AWS_ENDPOINT environment variable.
|
24
|
+
# * bucket - The S3 bucket name. Defaults to the SUPER_SETTINGS_S3_BUCKET or AWS_S3_BUCKET
|
25
|
+
# environment variable.
|
26
|
+
# * object - The S3 object key. Defaults to "super_settings.json.gz" or the value set in the
|
27
|
+
# SUPER_SETTINGS_S3_OBJECT environment variable.
|
28
|
+
#
|
29
|
+
# You can also specify the configuration using a URL in the format using the SUPER_SETTINGS_S3_URL
|
30
|
+
# environment variable. The URL should be in the format:
|
31
|
+
#
|
32
|
+
# ```
|
33
|
+
# s3://access_key_id:secret_access_key@region/bucket/object
|
34
|
+
# ```
|
35
|
+
class Configuration
|
36
|
+
attr_accessor :access_key_id, :secret_access_key, :region, :endpoint, :bucket
|
37
|
+
attr_reader :path
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@access_key_id ||= ENV.fetch("SUPER_SETTINGS_AWS_ACCESS_KEY_ID", ENV["AWS_ACCESS_KEY_ID"])
|
41
|
+
@secret_access_key ||= ENV.fetch("SUPER_SETTINGS_AWS_SECRET_ACCESS_KEY", ENV["AWS_SECRET_ACCESS_KEY"])
|
42
|
+
@region ||= ENV.fetch("SUPER_SETTINGS_AWS_REGION", ENV["AWS_REGION"])
|
43
|
+
@endpoint ||= ENV.fetch("SUPER_SETTINGS_AWS_ENDPOINT", ENV["AWS_ENDPOINT"])
|
44
|
+
@bucket ||= ENV.fetch("SUPER_SETTINGS_S3_BUCKET", ENV["AWS_S3_BUCKET"])
|
45
|
+
@path ||= ENV.fetch("SUPER_SETTINGS_S3_OBJECT", DEFAULT_PATH)
|
46
|
+
self.url = ENV["SUPER_SETTINGS_S3_URL"] unless ENV["SUPER_SETTINGS_S3_URL"].to_s.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
def url=(url)
|
50
|
+
return if url.to_s.empty?
|
51
|
+
|
52
|
+
uri = URI.parse(url)
|
53
|
+
raise ArgumentError, "Invalid S3 URL" unless uri.scheme == "s3"
|
54
|
+
|
55
|
+
self.access_key_id = uri.user if uri.user
|
56
|
+
self.secret_access_key = uri.password if uri.password
|
57
|
+
self.region = uri.host if uri.host
|
58
|
+
_, bucket, path = uri.path.split("/", 3) if uri.path
|
59
|
+
self.bucket = bucket if bucket
|
60
|
+
self.path = path if path
|
61
|
+
end
|
62
|
+
|
63
|
+
def path=(value)
|
64
|
+
@path = "#{value}.chomp('/')/"
|
65
|
+
end
|
66
|
+
|
67
|
+
def hash
|
68
|
+
[self.class, access_key_id, secret_access_key, region, endpoint, bucket, path].hash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
@bucket = nil
|
73
|
+
@bucket_hash = nil
|
74
|
+
|
75
|
+
class << self
|
76
|
+
def last_updated_at
|
77
|
+
all.collect(&:updated_at).compact.max
|
78
|
+
end
|
79
|
+
|
80
|
+
def configuration
|
81
|
+
@config ||= Configuration.new
|
82
|
+
end
|
83
|
+
|
84
|
+
def destroy_all
|
85
|
+
s3_bucket.objects(prefix: configuration.path).each do |object|
|
86
|
+
if object.key == file_path(SETTINGS_FILE) || object.key.end_with?(HISTORY_FILE_SUFFIX)
|
87
|
+
object.delete
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def default_load_asynchronous?
|
95
|
+
true
|
96
|
+
end
|
97
|
+
|
98
|
+
def settings_json_payload
|
99
|
+
object = settings_object
|
100
|
+
return nil unless object.exists?
|
101
|
+
|
102
|
+
object.get.body.read
|
103
|
+
end
|
104
|
+
|
105
|
+
def save_settings_json(json)
|
106
|
+
object = settings_object
|
107
|
+
object.put(body: json)
|
108
|
+
end
|
109
|
+
|
110
|
+
def save_history_json(key, json)
|
111
|
+
object = history_object(key)
|
112
|
+
object.put(body: json)
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def s3_bucket
|
118
|
+
if configuration.hash != @bucket_hash
|
119
|
+
@bucket_hash = configuration.hash
|
120
|
+
options = {
|
121
|
+
endpoint: configuration.endpoint,
|
122
|
+
access_key_id: configuration.access_key_id,
|
123
|
+
secret_access_key: configuration.secret_access_key,
|
124
|
+
region: configuration.region
|
125
|
+
}
|
126
|
+
options[:force_path_style] = true if configuration.endpoint
|
127
|
+
options.compact!
|
128
|
+
|
129
|
+
@bucket = Aws::S3::Resource.new(options).bucket(configuration.bucket)
|
130
|
+
end
|
131
|
+
@bucket
|
132
|
+
end
|
133
|
+
|
134
|
+
def s3_object(filename)
|
135
|
+
s3_bucket.object(file_path(filename))
|
136
|
+
end
|
137
|
+
|
138
|
+
def file_path(filename)
|
139
|
+
"#{configuration.path}#{filename}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def settings_object
|
143
|
+
s3_object(SETTINGS_FILE)
|
144
|
+
end
|
145
|
+
|
146
|
+
def history_object(key)
|
147
|
+
s3_object("#{key}#{HISTORY_FILE_SUFFIX}")
|
148
|
+
end
|
149
|
+
|
150
|
+
def history_json(key)
|
151
|
+
object = history_object(key)
|
152
|
+
return nil unless object.exists?
|
153
|
+
|
154
|
+
object.get.body.read
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
protected
|
159
|
+
|
160
|
+
def fetch_history_json
|
161
|
+
self.class.send(:history_json, key)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
module Storage
|
5
|
+
# Generic class that can be extended to represent a setting in memory.
|
6
|
+
class StorageAttributes
|
7
|
+
include SuperSettings::Storage
|
8
|
+
|
9
|
+
attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
|
10
|
+
|
11
|
+
def initialize(attributes = nil)
|
12
|
+
@key = nil
|
13
|
+
@raw_value = nil
|
14
|
+
@description = nil
|
15
|
+
@value_type = nil
|
16
|
+
@deleted = false
|
17
|
+
@updated_at = nil
|
18
|
+
@created_at = nil
|
19
|
+
@persisted = false
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def key=(value)
|
24
|
+
@key = (Coerce.blank?(value) ? nil : value.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def raw_value=(value)
|
28
|
+
@raw_value = (Coerce.blank?(value) ? nil : value.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def value_type=(value)
|
32
|
+
@value_type = (Coerce.blank?(value) ? nil : value.to_s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def description=(value)
|
36
|
+
@description = (Coerce.blank?(value) ? nil : value.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
def updated_at=(value)
|
40
|
+
@updated_at = SuperSettings::Coerce.time(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def created_at=(value)
|
44
|
+
@created_at = SuperSettings::Coerce.time(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
def deleted?
|
48
|
+
!!@deleted
|
49
|
+
end
|
50
|
+
|
51
|
+
def deleted=(val)
|
52
|
+
@deleted = !!val
|
53
|
+
end
|
54
|
+
|
55
|
+
def persisted?
|
56
|
+
!!@persisted
|
57
|
+
end
|
58
|
+
|
59
|
+
def persisted=(val)
|
60
|
+
@persisted = !!val
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "json"
|
4
|
-
|
5
3
|
module SuperSettings
|
6
4
|
module Storage
|
7
5
|
# Implementation of the SuperSettings::Storage model for running unit tests.
|
@@ -28,7 +26,7 @@ module SuperSettings
|
|
28
26
|
items
|
29
27
|
end
|
30
28
|
|
31
|
-
def
|
29
|
+
def destroy_all
|
32
30
|
@settings = {}
|
33
31
|
@history = {}
|
34
32
|
end
|
@@ -42,7 +40,7 @@ module SuperSettings
|
|
42
40
|
end
|
43
41
|
|
44
42
|
def updated_since(time)
|
45
|
-
settings.values.select { |attributes| attributes[:updated_at].to_f
|
43
|
+
settings.values.select { |attributes| attributes[:updated_at].to_f > time.to_f }.collect do |attributes|
|
46
44
|
setting = new(attributes)
|
47
45
|
setting.send(:set_persisted!)
|
48
46
|
setting
|
@@ -54,7 +52,7 @@ module SuperSettings
|
|
54
52
|
return nil unless attributes
|
55
53
|
setting = new(attributes)
|
56
54
|
setting.send(:set_persisted!)
|
57
|
-
setting
|
55
|
+
setting unless setting.deleted?
|
58
56
|
end
|
59
57
|
|
60
58
|
def last_updated_at
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
module Storage
|
5
|
+
# This module provides support for transactions in storage models that don't natively
|
6
|
+
# support transactions.
|
7
|
+
module Transaction
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def transaction(&block)
|
14
|
+
if Thread.current[transaction_key]
|
15
|
+
yield Thread.current[transaction_key]
|
16
|
+
else
|
17
|
+
begin
|
18
|
+
changes = []
|
19
|
+
Thread.current[transaction_key] = changes
|
20
|
+
|
21
|
+
yield(changes)
|
22
|
+
|
23
|
+
if save_all(changes) != false
|
24
|
+
changes.each do |object|
|
25
|
+
object.persisted = true if object.respond_to?(:persisted=)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
ensure
|
29
|
+
Thread.current[transaction_key] = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def save_all(changes)
|
35
|
+
# :nocov:
|
36
|
+
raise NotImplementedError
|
37
|
+
# :nocov:
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def transaction_key
|
43
|
+
"#{name}.transaction"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def persisted=(value)
|
48
|
+
@persisted = Coerce.boolean(value)
|
49
|
+
end
|
50
|
+
|
51
|
+
def persisted?
|
52
|
+
!!@persisted
|
53
|
+
end
|
54
|
+
|
55
|
+
def save!
|
56
|
+
self.updated_at ||= Time.now
|
57
|
+
self.created_at ||= updated_at
|
58
|
+
|
59
|
+
self.class.transaction do |changes|
|
60
|
+
changes << self
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -4,8 +4,15 @@ module SuperSettings
|
|
4
4
|
# Abstraction over how a setting is stored and retrieved from the storage engine. Models
|
5
5
|
# must implement the methods module in this module that raise NotImplementedError.
|
6
6
|
module Storage
|
7
|
-
|
8
|
-
|
7
|
+
autoload :StorageAttributes, File.join(__dir__, "storage/storage_attributes")
|
8
|
+
autoload :HistoryAttributes, File.join(__dir__, "storage/history_attributes")
|
9
|
+
autoload :Transaction, File.join(__dir__, "storage/transaction")
|
10
|
+
autoload :ActiveRecordStorage, File.join(__dir__, "storage/active_record_storage")
|
11
|
+
autoload :HttpStorage, File.join(__dir__, "storage/http_storage")
|
12
|
+
autoload :RedisStorage, File.join(__dir__, "storage/redis_storage")
|
13
|
+
autoload :JSONStorage, File.join(__dir__, "storage/json_storage")
|
14
|
+
autoload :S3Storage, File.join(__dir__, "storage/s3_storage")
|
15
|
+
autoload :MongoDBStorage, File.join(__dir__, "storage/mongodb_storage")
|
9
16
|
|
10
17
|
def self.included(base)
|
11
18
|
base.extend(ClassMethods)
|
@@ -84,6 +91,11 @@ module SuperSettings
|
|
84
91
|
# Set to true to force loading setting asynchronously in a background thread.
|
85
92
|
attr_writer :load_asynchronous
|
86
93
|
|
94
|
+
# Implementing classes can override this method to delete all settings. This is used
|
95
|
+
# for testing purposes.
|
96
|
+
def destroy_all
|
97
|
+
end
|
98
|
+
|
87
99
|
protected
|
88
100
|
|
89
101
|
# Implementing classes can override this method to indicate if it is safe to load the
|
@@ -151,6 +163,7 @@ module SuperSettings
|
|
151
163
|
end
|
152
164
|
|
153
165
|
# The description for the setting.
|
166
|
+
#
|
154
167
|
# @return [String]
|
155
168
|
def description
|
156
169
|
# :nocov:
|
@@ -159,7 +172,7 @@ module SuperSettings
|
|
159
172
|
end
|
160
173
|
|
161
174
|
# Set the description for the setting.
|
162
|
-
|
175
|
+
#
|
163
176
|
# @param val [String]
|
164
177
|
# @return [void]
|
165
178
|
def description=(val)
|
@@ -269,13 +282,9 @@ module SuperSettings
|
|
269
282
|
end
|
270
283
|
|
271
284
|
# :nocov:
|
272
|
-
require_relative "storage/http_storage"
|
273
|
-
require_relative "storage/redis_storage"
|
274
285
|
if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
|
275
|
-
ActiveSupport.on_load(:
|
286
|
+
ActiveSupport.on_load(:active_record_base) do
|
276
287
|
require_relative "storage/active_record_storage"
|
277
288
|
end
|
278
|
-
elsif defined?(ActiveRecord::Base)
|
279
|
-
require_relative "storage/active_record_storage"
|
280
289
|
end
|
281
290
|
# :nocov:
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
# Helper class for truncating timestamps to a specific precision. This is used by storage engines
|
5
|
+
# to ensure that timestamps are stored and compared with the same precision.
|
6
|
+
class TimePrecision
|
7
|
+
attr_reader :time
|
8
|
+
|
9
|
+
def initialize(time, precision = :microsecond)
|
10
|
+
raise ArgumentError.new("Invalid precision: #{precision}") unless valid_precision?(precision)
|
11
|
+
|
12
|
+
@time = time_with_precision(time.to_f, precision) if time
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_f
|
16
|
+
@time.to_f
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def valid_precision?(precision)
|
22
|
+
[:microsecond, :millisecond].include?(precision)
|
23
|
+
end
|
24
|
+
|
25
|
+
def time_with_precision(timestamp, precision)
|
26
|
+
usec = (timestamp % 1) * 1_000_000.0
|
27
|
+
if precision == :millisecond
|
28
|
+
milliseconds = (usec / 1000.0).round(3).floor
|
29
|
+
Time.at(timestamp.to_i, milliseconds, :millisecond).utc
|
30
|
+
else
|
31
|
+
microseconds = usec.round
|
32
|
+
Time.at(timestamp.to_i, microseconds, :microsecond).utc
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/super_settings.rb
CHANGED
@@ -1,26 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# These classes are required for the gem to function.
|
6
|
+
require_relative "super_settings/attributes"
|
4
7
|
require_relative "super_settings/coerce"
|
5
8
|
require_relative "super_settings/configuration"
|
6
9
|
require_relative "super_settings/context"
|
7
|
-
require_relative "super_settings/context/rack_middleware"
|
8
10
|
require_relative "super_settings/local_cache"
|
9
|
-
require_relative "super_settings/rest_api"
|
10
|
-
require_relative "super_settings/rack_application"
|
11
|
-
require_relative "super_settings/controller_actions"
|
12
|
-
require_relative "super_settings/attributes"
|
13
11
|
require_relative "super_settings/setting"
|
14
|
-
require_relative "super_settings/history_item"
|
15
12
|
require_relative "super_settings/storage"
|
16
|
-
require_relative "super_settings/
|
17
|
-
|
18
|
-
if defined?(Rails::Engine)
|
19
|
-
require_relative "super_settings/engine"
|
20
|
-
end
|
13
|
+
require_relative "super_settings/time_precision"
|
21
14
|
|
22
15
|
# This is the main interface to the access settings.
|
23
16
|
module SuperSettings
|
17
|
+
# These classes are autoloaded when they are needed.
|
18
|
+
autoload :Application, "super_settings/application"
|
19
|
+
autoload :RestAPI, "super_settings/rest_api"
|
20
|
+
autoload :RackApplication, "super_settings/rack_application"
|
21
|
+
autoload :ControllerActions, "super_settings/controller_actions"
|
22
|
+
autoload :HistoryItem, "super_settings/history_item"
|
23
|
+
autoload :HttpClient, "super_settings/http_client"
|
24
|
+
autoload :VERSION, "super_settings/version"
|
25
|
+
|
24
26
|
DEFAULT_REFRESH_INTERVAL = 5.0
|
25
27
|
|
26
28
|
@local_cache = LocalCache.new(refresh_interval: DEFAULT_REFRESH_INTERVAL)
|
@@ -105,6 +107,28 @@ module SuperSettings
|
|
105
107
|
Array(val).collect { |v| v&.to_s }
|
106
108
|
end
|
107
109
|
|
110
|
+
# Get a pseudo random number. This method works the same as Kernel.rand. However, if you are
|
111
|
+
# inside a context block, then the random number will be the same each time you call this method.
|
112
|
+
# This is useful when you need to generate a random number for a setting that you want to remain
|
113
|
+
# constant for the duration of the block.
|
114
|
+
#
|
115
|
+
# So, for instance, if you are generating a random number to determine if a feature is enabled,
|
116
|
+
# you can use this method to ensure that the feature is either always enabled or always disabled
|
117
|
+
# for the duration of the block.
|
118
|
+
#
|
119
|
+
# @param max [Integer, Float, Range] the maximum value or range to use for the random number
|
120
|
+
# @return [Integer, Float] the random number. It will be an integer if max is an integer, otherwise
|
121
|
+
# it will be a float.
|
122
|
+
def rand(max = nil)
|
123
|
+
max ||= 1.0
|
124
|
+
context = current_context
|
125
|
+
if context
|
126
|
+
context.rand(max)
|
127
|
+
else
|
128
|
+
Random.rand(max)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
108
132
|
# Create settings and update the local cache with the values. If a block is given, then the
|
109
133
|
# value will be reverted at the end of the block. This method can be used in tests when you
|
110
134
|
# need to inject a specific value into your settings.
|
@@ -148,7 +172,7 @@ module SuperSettings
|
|
148
172
|
def context(&block)
|
149
173
|
reset_context = Thread.current[:super_settings_context].nil?
|
150
174
|
begin
|
151
|
-
Thread.current[:super_settings_context] ||=
|
175
|
+
Thread.current[:super_settings_context] ||= Context::Current.new
|
152
176
|
yield
|
153
177
|
ensure
|
154
178
|
Thread.current[:super_settings_context] = nil if reset_context
|
@@ -201,6 +225,13 @@ module SuperSettings
|
|
201
225
|
end
|
202
226
|
end
|
203
227
|
|
228
|
+
# Return the configuration object.
|
229
|
+
#
|
230
|
+
# @return [SuperSettings::Configuration]
|
231
|
+
def configuration
|
232
|
+
Configuration.instance
|
233
|
+
end
|
234
|
+
|
204
235
|
# Set the number of seconds between checks to synchronize the in memory cache from the database.
|
205
236
|
# This setting aids in performance since it throttles the number of times the database is queried
|
206
237
|
# for changes. However, changes made to the settings in the databae will take up to the number of
|
@@ -241,3 +272,7 @@ module SuperSettings
|
|
241
272
|
end
|
242
273
|
end
|
243
274
|
end
|
275
|
+
|
276
|
+
if defined?(Rails::Engine)
|
277
|
+
require_relative "super_settings/engine"
|
278
|
+
end
|
data/super_settings.gemspec
CHANGED
@@ -4,10 +4,17 @@ Gem::Specification.new do |spec|
|
|
4
4
|
spec.authors = ["Brian Durand"]
|
5
5
|
spec.email = ["bbdurand@gmail.com"]
|
6
6
|
|
7
|
-
spec.summary = "
|
7
|
+
spec.summary = "SuperSettings provides a scalable framework for managing dynamic runtime application settings with in-memory caching, strong typing, a built-in web UI, and support for multiple storage backends."
|
8
|
+
|
8
9
|
spec.homepage = "https://github.com/bdurand/super_settings"
|
9
10
|
spec.license = "MIT"
|
10
11
|
|
12
|
+
spec.metadata = {
|
13
|
+
"homepage_uri" => spec.homepage,
|
14
|
+
"source_code_uri" => spec.homepage,
|
15
|
+
"changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
|
16
|
+
}
|
17
|
+
|
11
18
|
# Specify which files should be added to the gem when it is released.
|
12
19
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
20
|
ignore_files = %w[
|
@@ -16,6 +23,8 @@ Gem::Specification.new do |spec|
|
|
16
23
|
Gemfile
|
17
24
|
Gemfile.lock
|
18
25
|
Rakefile
|
26
|
+
docker-compose.yml
|
27
|
+
config.ru
|
19
28
|
assets/
|
20
29
|
bin/
|
21
30
|
gemfiles/
|
@@ -29,5 +38,5 @@ Gem::Specification.new do |spec|
|
|
29
38
|
|
30
39
|
spec.add_development_dependency "bundler"
|
31
40
|
|
32
|
-
spec.required_ruby_version = ">= 2.
|
41
|
+
spec.required_ruby_version = ">= 2.6"
|
33
42
|
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: 2.0.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:
|
11
|
+
date: 2024-10-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -32,7 +32,7 @@ extensions: []
|
|
32
32
|
extra_rdoc_files: []
|
33
33
|
files:
|
34
34
|
- CHANGELOG.md
|
35
|
-
- MIT-LICENSE
|
35
|
+
- MIT-LICENSE.txt
|
36
36
|
- README.md
|
37
37
|
- VERSION
|
38
38
|
- app/helpers/super_settings/settings_helper.rb
|
@@ -43,40 +43,57 @@ files:
|
|
43
43
|
- lib/super_settings/application.rb
|
44
44
|
- lib/super_settings/application/api.js
|
45
45
|
- lib/super_settings/application/helper.rb
|
46
|
-
- lib/super_settings/application/images/
|
47
|
-
- lib/super_settings/application/images/
|
46
|
+
- lib/super_settings/application/images/arrow-down-short.svg
|
47
|
+
- lib/super_settings/application/images/arrow-up-short.svg
|
48
|
+
- lib/super_settings/application/images/info-circle.svg
|
49
|
+
- lib/super_settings/application/images/pencil-square.svg
|
48
50
|
- lib/super_settings/application/images/plus.svg
|
49
|
-
- lib/super_settings/application/images/
|
50
|
-
- lib/super_settings/application/images/
|
51
|
+
- lib/super_settings/application/images/trash3.svg
|
52
|
+
- lib/super_settings/application/images/x-circle.svg
|
51
53
|
- lib/super_settings/application/index.html.erb
|
52
54
|
- lib/super_settings/application/layout.html.erb
|
53
55
|
- lib/super_settings/application/layout_styles.css
|
56
|
+
- lib/super_settings/application/layout_vars.css.erb
|
54
57
|
- lib/super_settings/application/scripts.js
|
58
|
+
- lib/super_settings/application/style_vars.css.erb
|
55
59
|
- lib/super_settings/application/styles.css
|
56
60
|
- lib/super_settings/attributes.rb
|
57
61
|
- lib/super_settings/coerce.rb
|
58
62
|
- lib/super_settings/configuration.rb
|
59
63
|
- lib/super_settings/context.rb
|
64
|
+
- lib/super_settings/context/current.rb
|
60
65
|
- lib/super_settings/context/rack_middleware.rb
|
61
66
|
- lib/super_settings/context/sidekiq_middleware.rb
|
62
67
|
- lib/super_settings/controller_actions.rb
|
63
68
|
- lib/super_settings/engine.rb
|
64
69
|
- lib/super_settings/history_item.rb
|
70
|
+
- lib/super_settings/http_client.rb
|
65
71
|
- lib/super_settings/local_cache.rb
|
66
72
|
- lib/super_settings/rack_application.rb
|
67
73
|
- lib/super_settings/rest_api.rb
|
68
74
|
- lib/super_settings/setting.rb
|
69
75
|
- lib/super_settings/storage.rb
|
70
76
|
- lib/super_settings/storage/active_record_storage.rb
|
77
|
+
- lib/super_settings/storage/active_record_storage/models.rb
|
78
|
+
- lib/super_settings/storage/history_attributes.rb
|
71
79
|
- lib/super_settings/storage/http_storage.rb
|
80
|
+
- lib/super_settings/storage/json_storage.rb
|
81
|
+
- lib/super_settings/storage/mongodb_storage.rb
|
72
82
|
- lib/super_settings/storage/redis_storage.rb
|
83
|
+
- lib/super_settings/storage/s3_storage.rb
|
84
|
+
- lib/super_settings/storage/storage_attributes.rb
|
73
85
|
- lib/super_settings/storage/test_storage.rb
|
86
|
+
- lib/super_settings/storage/transaction.rb
|
87
|
+
- lib/super_settings/time_precision.rb
|
74
88
|
- lib/super_settings/version.rb
|
75
89
|
- super_settings.gemspec
|
76
90
|
homepage: https://github.com/bdurand/super_settings
|
77
91
|
licenses:
|
78
92
|
- MIT
|
79
|
-
metadata:
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://github.com/bdurand/super_settings
|
95
|
+
source_code_uri: https://github.com/bdurand/super_settings
|
96
|
+
changelog_uri: https://github.com/bdurand/super_settings/blob/main/CHANGELOG.md
|
80
97
|
post_install_message:
|
81
98
|
rdoc_options: []
|
82
99
|
require_paths:
|
@@ -85,16 +102,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
85
102
|
requirements:
|
86
103
|
- - ">="
|
87
104
|
- !ruby/object:Gem::Version
|
88
|
-
version: '2.
|
105
|
+
version: '2.6'
|
89
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
107
|
requirements:
|
91
108
|
- - ">="
|
92
109
|
- !ruby/object:Gem::Version
|
93
110
|
version: '0'
|
94
111
|
requirements: []
|
95
|
-
rubygems_version: 3.4.
|
112
|
+
rubygems_version: 3.4.10
|
96
113
|
signing_key:
|
97
114
|
specification_version: 4
|
98
|
-
summary:
|
99
|
-
|
115
|
+
summary: SuperSettings provides a scalable framework for managing dynamic runtime
|
116
|
+
application settings with in-memory caching, strong typing, a built-in web UI, and
|
117
|
+
support for multiple storage backends.
|
100
118
|
test_files: []
|
@@ -1 +0,0 @@
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
@@ -1 +0,0 @@
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-info"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
|
@@ -1 +0,0 @@
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-slash"><circle cx="12" cy="12" r="10"></circle><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line></svg>
|