super_settings 0.0.0.rc1
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 +7 -0
- data/CHANGELOG.md +9 -0
- data/MIT-LICENSE +20 -0
- data/README.md +313 -0
- data/VERSION +1 -0
- data/app/helpers/super_settings/settings_helper.rb +32 -0
- data/app/views/layouts/super_settings/settings.html.erb +20 -0
- data/config/routes.rb +13 -0
- data/db/migrate/20210414004553_create_super_settings.rb +34 -0
- data/lib/super_settings/application/api.js +88 -0
- data/lib/super_settings/application/helper.rb +119 -0
- data/lib/super_settings/application/images/edit.svg +1 -0
- data/lib/super_settings/application/images/info.svg +1 -0
- data/lib/super_settings/application/images/plus.svg +1 -0
- data/lib/super_settings/application/images/slash.svg +1 -0
- data/lib/super_settings/application/images/trash.svg +1 -0
- data/lib/super_settings/application/index.html.erb +169 -0
- data/lib/super_settings/application/layout.html.erb +22 -0
- data/lib/super_settings/application/layout_styles.css +193 -0
- data/lib/super_settings/application/scripts.js +718 -0
- data/lib/super_settings/application/styles.css +122 -0
- data/lib/super_settings/application.rb +38 -0
- data/lib/super_settings/attributes.rb +24 -0
- data/lib/super_settings/coerce.rb +66 -0
- data/lib/super_settings/configuration.rb +144 -0
- data/lib/super_settings/controller_actions.rb +81 -0
- data/lib/super_settings/encryption.rb +76 -0
- data/lib/super_settings/engine.rb +70 -0
- data/lib/super_settings/history_item.rb +26 -0
- data/lib/super_settings/local_cache.rb +306 -0
- data/lib/super_settings/rack_middleware.rb +210 -0
- data/lib/super_settings/rest_api.rb +195 -0
- data/lib/super_settings/setting.rb +599 -0
- data/lib/super_settings/storage/active_record_storage.rb +123 -0
- data/lib/super_settings/storage/http_storage.rb +279 -0
- data/lib/super_settings/storage/redis_storage.rb +293 -0
- data/lib/super_settings/storage/test_storage.rb +158 -0
- data/lib/super_settings/storage.rb +254 -0
- data/lib/super_settings/version.rb +5 -0
- data/lib/super_settings.rb +213 -0
- data/lib/tasks/super_settings.rake +9 -0
- data/super_settings.gemspec +35 -0
- metadata +113 -0
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
|
5
|
+
# :nocov:
|
6
|
+
module SuperSettings
|
7
|
+
module Storage
|
8
|
+
# Implementation of the SuperSettings::Storage model for running unit tests.
|
9
|
+
class TestStorage
|
10
|
+
include Storage
|
11
|
+
|
12
|
+
attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
|
13
|
+
attr_accessor :changed_by
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def settings
|
17
|
+
@settings ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def history(key)
|
21
|
+
@history ||= {}
|
22
|
+
items = @history[key]
|
23
|
+
unless items
|
24
|
+
items = []
|
25
|
+
@history[key] = items
|
26
|
+
end
|
27
|
+
items
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear
|
31
|
+
@settings = {}
|
32
|
+
@history = {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def all
|
36
|
+
settings.values.collect do |attributes|
|
37
|
+
setting = new(attributes)
|
38
|
+
setting.send(:set_persisted!)
|
39
|
+
setting
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def updated_since(time)
|
44
|
+
settings.values.select { |attributes| attributes[:updated_at].to_f >= time.to_f }.collect do |attributes|
|
45
|
+
setting = new(attributes)
|
46
|
+
setting.send(:set_persisted!)
|
47
|
+
setting
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_by_key(key)
|
52
|
+
attributes = settings[key]
|
53
|
+
return nil unless attributes
|
54
|
+
setting = new(attributes)
|
55
|
+
setting.send(:set_persisted!)
|
56
|
+
setting
|
57
|
+
end
|
58
|
+
|
59
|
+
def last_updated_at
|
60
|
+
settings.values.collect { |attributes| attributes[:updated_at] }.max
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def default_load_asynchronous?
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def history(limit: nil, offset: 0)
|
71
|
+
items = self.class.history(key)
|
72
|
+
items[offset, limit || items.length].collect do |attributes|
|
73
|
+
HistoryItem.new(attributes)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def create_history(changed_by:, created_at:, value: nil, deleted: false)
|
78
|
+
item = {key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at}
|
79
|
+
self.class.history(key).unshift(item)
|
80
|
+
end
|
81
|
+
|
82
|
+
def save!
|
83
|
+
self.updated_at ||= Time.now
|
84
|
+
self.created_at ||= updated_at
|
85
|
+
if defined?(@original_key) && @original_key
|
86
|
+
self.class.settings.delete(@original_key)
|
87
|
+
end
|
88
|
+
self.class.settings[key] = attributes
|
89
|
+
set_persisted!
|
90
|
+
true
|
91
|
+
end
|
92
|
+
|
93
|
+
def key=(value)
|
94
|
+
@original_key ||= key
|
95
|
+
@key = (Coerce.blank?(value) ? nil : value.to_s)
|
96
|
+
end
|
97
|
+
|
98
|
+
def raw_value=(value)
|
99
|
+
@raw_value = (Coerce.blank?(value) ? nil : value.to_s)
|
100
|
+
end
|
101
|
+
|
102
|
+
def value_type=(value)
|
103
|
+
@value_type = (Coerce.blank?(value) ? nil : value.to_s)
|
104
|
+
end
|
105
|
+
|
106
|
+
def description=(value)
|
107
|
+
@description = (Coerce.blank?(value) ? nil : value.to_s)
|
108
|
+
end
|
109
|
+
|
110
|
+
def deleted=(value)
|
111
|
+
@deleted = Coerce.boolean(value)
|
112
|
+
end
|
113
|
+
|
114
|
+
def created_at=(value)
|
115
|
+
@created_at = SuperSettings::Coerce.time(value)
|
116
|
+
end
|
117
|
+
|
118
|
+
def updated_at=(value)
|
119
|
+
@updated_at = SuperSettings::Coerce.time(value)
|
120
|
+
end
|
121
|
+
|
122
|
+
def deleted?
|
123
|
+
!!(defined?(@deleted) && @deleted)
|
124
|
+
end
|
125
|
+
|
126
|
+
def persisted?
|
127
|
+
!!(defined?(@persisted) && @persisted)
|
128
|
+
end
|
129
|
+
|
130
|
+
protected
|
131
|
+
|
132
|
+
def redact_history!
|
133
|
+
self.class.history(key).each do |item|
|
134
|
+
item[:value] = nil
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def set_persisted!
|
141
|
+
@persisted = true
|
142
|
+
end
|
143
|
+
|
144
|
+
def attributes
|
145
|
+
{
|
146
|
+
key: key,
|
147
|
+
raw_value: raw_value,
|
148
|
+
value_type: value_type,
|
149
|
+
description: description,
|
150
|
+
deleted: deleted?,
|
151
|
+
updated_at: updated_at,
|
152
|
+
created_at: created_at
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
# :nocov:
|
@@ -0,0 +1,254 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SuperSettings
|
4
|
+
# Abstraction over how a setting is stored and retrieved from the storage engine. Models
|
5
|
+
# must implement the methods module in this module that raise `NotImplementedError`.
|
6
|
+
module Storage
|
7
|
+
class RecordInvalid < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
base.include(Attributes) unless base.instance_methods.include?(:attributes=)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Storage classes must implent this method to return all settings included deleted ones.
|
17
|
+
# @return [Array<SuperSetting::Setting::Storage>]
|
18
|
+
def all
|
19
|
+
# :nocov:
|
20
|
+
raise NotImplementedError
|
21
|
+
# :nocov:
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return all non-deleted settings.
|
25
|
+
# @return [Array<SuperSetting::Setting::Storage>]
|
26
|
+
def active
|
27
|
+
all.reject(&:deleted?)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Storage classes must implement this method to return all settings updates since the
|
31
|
+
# specified timestamp.
|
32
|
+
# @return [Array<SuperSetting::Setting::Storage>]
|
33
|
+
def updated_since(timestamp)
|
34
|
+
# :nocov:
|
35
|
+
raise NotImplementedError
|
36
|
+
# :nocov:
|
37
|
+
end
|
38
|
+
|
39
|
+
# Storage classes must implement this method to return a settings by it's key.
|
40
|
+
# @return [SuperSetting::Setting::Storage]
|
41
|
+
def find_by_key(key)
|
42
|
+
# :nocov:
|
43
|
+
raise NotImplementedError
|
44
|
+
# :nocov:
|
45
|
+
end
|
46
|
+
|
47
|
+
# Storage classes must implement this method to return most recent time that any
|
48
|
+
# setting was updated.
|
49
|
+
# @return [Time]
|
50
|
+
def last_updated_at
|
51
|
+
# :nocov:
|
52
|
+
raise NotImplementedError
|
53
|
+
# :nocov:
|
54
|
+
end
|
55
|
+
|
56
|
+
# Implementing classes can override this method to setup a thread safe connection within a block.
|
57
|
+
def with_connection(&block)
|
58
|
+
yield
|
59
|
+
end
|
60
|
+
|
61
|
+
# Implementing classes can override this method to wrap an operation in an atomic transaction.
|
62
|
+
def transaction(&block)
|
63
|
+
yield
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Boolean] true if it's safe to load setting asynchronously in a background thread.
|
67
|
+
def load_asynchronous?
|
68
|
+
!!(defined?(@load_asynchronous) && !@load_asynchronous.nil? ? @load_asynchronous : default_load_asynchronous?)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set to true to force loading setting asynchronously in a background thread.
|
72
|
+
attr_writer :load_asynchronous
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
# Implementing classes can override this method to indicate if it is safe to load the
|
77
|
+
# setting in a separate thread.
|
78
|
+
def default_load_asynchronous?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [String] the key for the setting
|
84
|
+
def key
|
85
|
+
# :nocov:
|
86
|
+
raise NotImplementedError
|
87
|
+
# :nocov:
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set the key for the setting.
|
91
|
+
# @param val [String]
|
92
|
+
# @return [void]
|
93
|
+
def key=(val)
|
94
|
+
# :nocov:
|
95
|
+
raise NotImplementedError
|
96
|
+
# :nocov:
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [String] the raw value for the setting before it is type cast.
|
100
|
+
def raw_value
|
101
|
+
# :nocov:
|
102
|
+
raise NotImplementedError
|
103
|
+
# :nocov:
|
104
|
+
end
|
105
|
+
|
106
|
+
# Set the raw value for the setting.
|
107
|
+
# @param val [String]
|
108
|
+
# @return [void]
|
109
|
+
def raw_value=(val)
|
110
|
+
# :nocov:
|
111
|
+
raise NotImplementedError
|
112
|
+
# :nocov:
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [String] the value type for the setting
|
116
|
+
def value_type
|
117
|
+
# :nocov:
|
118
|
+
raise NotImplementedError
|
119
|
+
# :nocov:
|
120
|
+
end
|
121
|
+
|
122
|
+
# Set the value type for the setting.
|
123
|
+
# @param val [String] one of string, integer, float, boolean, datetime, array, or secret
|
124
|
+
# @return [void]
|
125
|
+
def value_type=(val)
|
126
|
+
# :nocov:
|
127
|
+
raise NotImplementedError
|
128
|
+
# :nocov:
|
129
|
+
end
|
130
|
+
|
131
|
+
# @return [String] the description for the setting
|
132
|
+
def description
|
133
|
+
# :nocov:
|
134
|
+
raise NotImplementedError
|
135
|
+
# :nocov:
|
136
|
+
end
|
137
|
+
|
138
|
+
# Set the description for the setting.
|
139
|
+
# @param val [String]
|
140
|
+
# @return [void]
|
141
|
+
def description=(val)
|
142
|
+
# :nocov:
|
143
|
+
raise NotImplementedError
|
144
|
+
# :nocov:
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Boolean] true if the setting marked as deleted
|
148
|
+
def deleted?
|
149
|
+
# :nocov:
|
150
|
+
raise NotImplementedError
|
151
|
+
# :nocov:
|
152
|
+
end
|
153
|
+
|
154
|
+
# Set the deleted flag for the setting. Settings should not actually be deleted since
|
155
|
+
# the record is needed to keep the local cache up to date.
|
156
|
+
# @param val [Boolean]
|
157
|
+
# @return [void]
|
158
|
+
def deleted=(val)
|
159
|
+
# :nocov:
|
160
|
+
raise NotImplementedError
|
161
|
+
# :nocov:
|
162
|
+
end
|
163
|
+
|
164
|
+
# @return [Time] the time the setting was last updated
|
165
|
+
def updated_at
|
166
|
+
# :nocov:
|
167
|
+
raise NotImplementedError
|
168
|
+
# :nocov:
|
169
|
+
end
|
170
|
+
|
171
|
+
# Set the last updated time for the setting.
|
172
|
+
# @param val [Time]
|
173
|
+
# @return [void]
|
174
|
+
def updated_at=(val)
|
175
|
+
# :nocov:
|
176
|
+
raise NotImplementedError
|
177
|
+
# :nocov:
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [Time] the time the setting was created
|
181
|
+
def created_at
|
182
|
+
# :nocov:
|
183
|
+
raise NotImplementedError
|
184
|
+
# :nocov:
|
185
|
+
end
|
186
|
+
|
187
|
+
# Set the created time for the setting.
|
188
|
+
# @param val [Time]
|
189
|
+
# @return [void]
|
190
|
+
def created_at=(val)
|
191
|
+
# :nocov:
|
192
|
+
raise NotImplementedError
|
193
|
+
# :nocov:
|
194
|
+
end
|
195
|
+
|
196
|
+
# Return array of history items reflecting changes made to the setting over time. Items
|
197
|
+
# should be returned in reverse chronological order so that the most recent changes are first.
|
198
|
+
# @return [Array<SuperSettings::History>]
|
199
|
+
def history(limit: nil, offset: 0)
|
200
|
+
# :nocov:
|
201
|
+
raise NotImplementedError
|
202
|
+
# :nocov:
|
203
|
+
end
|
204
|
+
|
205
|
+
# Create a history item for the setting
|
206
|
+
def create_history(changed_by:, created_at:, value: nil, deleted: false)
|
207
|
+
# :nocov:
|
208
|
+
raise NotImplementedError
|
209
|
+
# :nocov:
|
210
|
+
end
|
211
|
+
|
212
|
+
# Persist the record to storage.
|
213
|
+
# @return [void]
|
214
|
+
def save!
|
215
|
+
# :nocov:
|
216
|
+
raise NotImplementedError
|
217
|
+
# :nocov:
|
218
|
+
end
|
219
|
+
|
220
|
+
# @return [Boolean] true if the record has been stored.
|
221
|
+
def persisted?
|
222
|
+
# :nocov:
|
223
|
+
raise NotImplementedError
|
224
|
+
# :nocov:
|
225
|
+
end
|
226
|
+
|
227
|
+
def ==(other)
|
228
|
+
other.is_a?(self.class) && other.key == key
|
229
|
+
end
|
230
|
+
|
231
|
+
protected
|
232
|
+
|
233
|
+
# Remove the value stored on history records if the setting is changed to a secret since
|
234
|
+
# these are not stored encrypted in the database. Implementing classes must redefine this
|
235
|
+
# method.
|
236
|
+
def redact_history!
|
237
|
+
# :nocov:
|
238
|
+
raise NotImplementedError
|
239
|
+
# :nocov:
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# :nocov:
|
245
|
+
require_relative "storage/http_storage"
|
246
|
+
require_relative "storage/redis_storage"
|
247
|
+
if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
|
248
|
+
ActiveSupport.on_load(:active_record) do
|
249
|
+
require_relative "storage/active_record_storage"
|
250
|
+
end
|
251
|
+
elsif defined?(ActiveRecord::Base)
|
252
|
+
require_relative "storage/active_record_storage"
|
253
|
+
end
|
254
|
+
# :nocov:
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "secret_keys"
|
4
|
+
|
5
|
+
require_relative "super_settings/application"
|
6
|
+
require_relative "super_settings/coerce"
|
7
|
+
require_relative "super_settings/configuration"
|
8
|
+
require_relative "super_settings/local_cache"
|
9
|
+
require_relative "super_settings/encryption"
|
10
|
+
require_relative "super_settings/rest_api"
|
11
|
+
require_relative "super_settings/rack_middleware"
|
12
|
+
require_relative "super_settings/controller_actions"
|
13
|
+
require_relative "super_settings/attributes"
|
14
|
+
require_relative "super_settings/setting"
|
15
|
+
require_relative "super_settings/history_item"
|
16
|
+
require_relative "super_settings/storage"
|
17
|
+
require_relative "super_settings/version"
|
18
|
+
|
19
|
+
if defined?(Rails::Engine)
|
20
|
+
require_relative "super_settings/engine"
|
21
|
+
end
|
22
|
+
|
23
|
+
# This is the main interface to the access settings.
|
24
|
+
module SuperSettings
|
25
|
+
DEFAULT_REFRESH_INTERVAL = 5.0
|
26
|
+
|
27
|
+
class << self
|
28
|
+
# Get a setting value cast to a string.
|
29
|
+
#
|
30
|
+
# @param key [String, Symbol]
|
31
|
+
# @param default [String] value to return if the setting value is nil
|
32
|
+
# @return [String]
|
33
|
+
def get(key, default = nil)
|
34
|
+
val = local_cache[key]
|
35
|
+
val.nil? ? default : val.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
# Get a setting value cast to an integer.
|
39
|
+
#
|
40
|
+
# @param key [String, Symbol]
|
41
|
+
# @param default [Integer] value to return if the setting value is nil
|
42
|
+
# @return [Integer]
|
43
|
+
def integer(key, default = nil)
|
44
|
+
val = local_cache[key]
|
45
|
+
(val.nil? ? default : val)&.to_i
|
46
|
+
end
|
47
|
+
|
48
|
+
# Get a setting value cast to a float.
|
49
|
+
#
|
50
|
+
# @param key [String, Symbol]
|
51
|
+
# @param default [Numeric] value to return if the setting value is nil
|
52
|
+
# @return [Float]
|
53
|
+
def float(key, default = nil)
|
54
|
+
val = local_cache[key]
|
55
|
+
(val.nil? ? default : val)&.to_f
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get a setting value cast to a boolean.
|
59
|
+
#
|
60
|
+
# @param key [String, Symbol]
|
61
|
+
# @param default [Boolean] value to return if the setting value is nil
|
62
|
+
# @return [Boolean]
|
63
|
+
def enabled?(key, default = false)
|
64
|
+
val = local_cache[key]
|
65
|
+
Coerce.boolean(val.nil? ? default : val)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get a setting value cast to a Time.
|
69
|
+
#
|
70
|
+
# @param key [String, Symbol]
|
71
|
+
# @param default [Time] value to return if the setting value is nil
|
72
|
+
# @return [Time]
|
73
|
+
def datetime(key, default = nil)
|
74
|
+
val = local_cache[key]
|
75
|
+
Coerce.time(val.nil? ? default : val)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Get a setting value cast to an array of strings.
|
79
|
+
#
|
80
|
+
# @param key [String, Symbol]
|
81
|
+
# @param default [Array] value to return if the setting value is nil
|
82
|
+
# @return [Array]
|
83
|
+
def array(key, default = nil)
|
84
|
+
val = local_cache[key]
|
85
|
+
val = default if val.nil?
|
86
|
+
return nil if val.nil?
|
87
|
+
Array(val).collect { |v| v&.to_s }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Get setting values cast to a hash. This method can be used to cast the flat setting key/value
|
91
|
+
# store into a structured data store. It uses a delimiter to define how keys are nested which
|
92
|
+
# defaults to a dot.
|
93
|
+
#
|
94
|
+
# If, for example, you have three keys in you settings "A.B1.C1 = 1", "A.B1.C2 = 2", and "A.B2.C3 = 3", the
|
95
|
+
# nested structure will be:
|
96
|
+
#
|
97
|
+
# `{"A" => {"B1" => {"C1" => 1, "C2" => 2}, "B2" => {"C3" => 3}}}`
|
98
|
+
#
|
99
|
+
# This whole hash would be returned if you called `hash` without any key. If you called it with the
|
100
|
+
# key "A.B1", it would return `{"C1" => 1, "C2" => 2}`.
|
101
|
+
#
|
102
|
+
# @param key [String, Symbol] the prefix patter to fetch keys for; default to returning all settings
|
103
|
+
# @param default [Hash] value to return if the setting value is nil
|
104
|
+
# @param delimiter [String] the delimiter to use to define nested keys in the hash; defaults to "."
|
105
|
+
# @return [Hash]
|
106
|
+
def structured(key = nil, default = nil, delimiter: ".", max_depth: nil)
|
107
|
+
value = local_cache.structured(key, delimiter: delimiter, max_depth: max_depth)
|
108
|
+
return (default || {}) if value.empty?
|
109
|
+
value
|
110
|
+
end
|
111
|
+
|
112
|
+
# Create settings and update the local cache with the values. If a block is given, then the
|
113
|
+
# value will be reverted at the end of the block. This method can be used in tests when you
|
114
|
+
# need to inject a specific value into your settings.
|
115
|
+
#
|
116
|
+
# @param key [String, Symbol] the key to set
|
117
|
+
# @param value [Object] the value to set
|
118
|
+
# @param value_type [String, Symbol] the value type to set; if the setting does not already exist,
|
119
|
+
# this will be inferred from the value.
|
120
|
+
# @return [void]
|
121
|
+
def set(key, value, value_type: nil)
|
122
|
+
setting = Setting.find_by_key(key)
|
123
|
+
if setting
|
124
|
+
setting.value_type = value_type if value_type
|
125
|
+
else
|
126
|
+
setting = Setting.new(key: key)
|
127
|
+
setting.value_type = (value_type || Setting.value_type(value) || Setting::STRING)
|
128
|
+
end
|
129
|
+
previous_value = setting.value
|
130
|
+
setting.value = value
|
131
|
+
begin
|
132
|
+
setting.save!
|
133
|
+
local_cache.load_settings unless local_cache.loaded?
|
134
|
+
local_cache.update_setting(setting)
|
135
|
+
if block_given?
|
136
|
+
yield
|
137
|
+
end
|
138
|
+
ensure
|
139
|
+
if block_given?
|
140
|
+
setting.value = previous_value
|
141
|
+
setting.save!
|
142
|
+
local_cache.load_settings unless local_cache.loaded?
|
143
|
+
local_cache.update_setting(setting)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Load the settings from the database into the in memory cache.
|
149
|
+
def load_settings
|
150
|
+
local_cache.load_settings
|
151
|
+
local_cache.wait_for_load
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
# Force refresh the settings in the in memory cache to be in sync with the database.
|
156
|
+
def refresh_settings
|
157
|
+
local_cache.refresh
|
158
|
+
nil
|
159
|
+
end
|
160
|
+
|
161
|
+
# Reset the in memory cache. The cache will be automatically reloaded the next time
|
162
|
+
# you access a setting.
|
163
|
+
def clear_cache
|
164
|
+
local_cache.reset
|
165
|
+
nil
|
166
|
+
end
|
167
|
+
|
168
|
+
# Return true if the in memory cache has been loaded from the database.
|
169
|
+
#
|
170
|
+
# @return [Boolean]
|
171
|
+
def loaded?
|
172
|
+
local_cache.loaded?
|
173
|
+
end
|
174
|
+
|
175
|
+
# Configure various aspects of the gem. The block will be yielded to with a configuration
|
176
|
+
# object. You should use this method to configure the gem from an Rails initializer since
|
177
|
+
# it will handle ensuring all the appropriate frameworks are loaded first.
|
178
|
+
#
|
179
|
+
# yieldparam config [SuperSettings::Configuration]
|
180
|
+
def configure(&block)
|
181
|
+
Configuration.instance.defer(&block)
|
182
|
+
unless defined?(Rails::Engine)
|
183
|
+
Configuration.instance.call
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# Set the number of seconds between checks to synchronize the in memory cache from the database.
|
188
|
+
# This setting aids in performance since it throttles the number of times the database is queried
|
189
|
+
# for changes. However, changes made to the settings in the databae will take up to the number of
|
190
|
+
# seconds in the refresh interval to be updated in the cache.
|
191
|
+
def refresh_interval=(value)
|
192
|
+
local_cache.refresh_interval = value
|
193
|
+
end
|
194
|
+
|
195
|
+
# Set the secret used to encrypt secret settings in the database.
|
196
|
+
#
|
197
|
+
# If you need to roll your secret, you can pass in an array of values. The first one
|
198
|
+
# specified will be used to encrypt values, but all of the keys will be tried when
|
199
|
+
# decrypting a value already stored in the database.
|
200
|
+
#
|
201
|
+
# @param value [String, Array]
|
202
|
+
def secret=(value)
|
203
|
+
Encryption.secret = value
|
204
|
+
load_settings if loaded?
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def local_cache
|
210
|
+
@local_cache ||= LocalCache.new(refresh_interval: DEFAULT_REFRESH_INTERVAL)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "super_settings"
|
3
|
+
spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
|
4
|
+
spec.authors = ["Brian Durand"]
|
5
|
+
spec.email = ["bbdurand@gmail.com"]
|
6
|
+
|
7
|
+
spec.summary = "Fast access runtime settings for a Rails application with an included UI and API for administration."
|
8
|
+
spec.homepage = "https://github.com/bdurand/super_settings"
|
9
|
+
spec.license = "MIT"
|
10
|
+
|
11
|
+
# Specify which files should be added to the gem when it is released.
|
12
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
13
|
+
ignore_files = %w[
|
14
|
+
.
|
15
|
+
Appraisals
|
16
|
+
Gemfile
|
17
|
+
Gemfile.lock
|
18
|
+
Rakefile
|
19
|
+
bin/
|
20
|
+
gemfiles/
|
21
|
+
spec/
|
22
|
+
web_ui.png
|
23
|
+
]
|
24
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
|
26
|
+
end
|
27
|
+
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_dependency "secret_keys", ">= 1.0"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler"
|
33
|
+
|
34
|
+
spec.required_ruby_version = ">= 2.5"
|
35
|
+
end
|