super_settings 0.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|