super_settings 0.0.0.rc1 → 0.0.1.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 +4 -4
- data/CHANGELOG.md +2 -1
- data/README.md +65 -62
- data/VERSION +1 -1
- data/config/routes.rb +3 -1
- data/lib/super_settings/application/api.js +8 -1
- data/lib/super_settings/application/helper.rb +2 -1
- data/lib/super_settings/application/index.html.erb +3 -3
- data/lib/super_settings/application/scripts.js +50 -12
- data/lib/super_settings/application/styles.css +5 -0
- data/lib/super_settings/application.rb +3 -1
- data/lib/super_settings/coerce.rb +6 -2
- data/lib/super_settings/configuration.rb +34 -15
- data/lib/super_settings/engine.rb +0 -5
- data/lib/super_settings/history_item.rb +10 -2
- data/lib/super_settings/local_cache.rb +14 -3
- data/lib/super_settings/{rack_middleware.rb → rack_application.rb} +66 -36
- data/lib/super_settings/rest_api.rb +98 -97
- data/lib/super_settings/setting.rb +96 -73
- data/lib/super_settings/storage/active_record_storage.rb +28 -10
- data/lib/super_settings/storage/http_storage.rb +7 -17
- data/lib/super_settings/storage/redis_storage.rb +20 -33
- data/lib/super_settings/storage/test_storage.rb +3 -10
- data/lib/super_settings/storage.rb +49 -24
- data/lib/super_settings.rb +33 -20
- data/super_settings.gemspec +1 -3
- metadata +4 -20
- data/lib/super_settings/encryption.rb +0 -76
- data/lib/tasks/super_settings.rake +0 -9
@@ -5,7 +5,7 @@ module SuperSettings
|
|
5
5
|
# updating settings.
|
6
6
|
#
|
7
7
|
# This class does not deal with actually persisting settings to and fetching them from a data store.
|
8
|
-
# You need to specify the storage engine you want to use with the
|
8
|
+
# You need to specify the storage engine you want to use with the +storage+ class method. This gem
|
9
9
|
# ships with storage engines for ActiveRecord, Redis, and HTTP (microservice). See the SuperSettings::Storage
|
10
10
|
# class for more details.
|
11
11
|
class Setting
|
@@ -17,9 +17,8 @@ module SuperSettings
|
|
17
17
|
BOOLEAN = "boolean"
|
18
18
|
DATETIME = "datetime"
|
19
19
|
ARRAY = "array"
|
20
|
-
SECRET = "secret"
|
21
20
|
|
22
|
-
VALUE_TYPES = [STRING, INTEGER, FLOAT, BOOLEAN, DATETIME, ARRAY
|
21
|
+
VALUE_TYPES = [STRING, INTEGER, FLOAT, BOOLEAN, DATETIME, ARRAY].freeze
|
23
22
|
|
24
23
|
ARRAY_DELIMITER = /[\n\r]+/.freeze
|
25
24
|
|
@@ -36,8 +35,8 @@ module SuperSettings
|
|
36
35
|
|
37
36
|
class << self
|
38
37
|
# Set a cache to use for caching values. This feature is optional. The cache must respond
|
39
|
-
# to
|
40
|
-
# you can use
|
38
|
+
# to +delete(key)+ and +fetch(key, &block)+. If you are running in a Rails environment,
|
39
|
+
# you can use +Rails.cache+ or any ActiveSupport::Cache::Store object.
|
41
40
|
attr_accessor :cache
|
42
41
|
|
43
42
|
# Set the storage class to use for persisting data.
|
@@ -56,6 +55,7 @@ module SuperSettings
|
|
56
55
|
end
|
57
56
|
|
58
57
|
# Create a new setting with the specified attributes.
|
58
|
+
#
|
59
59
|
# @param attributes [Hash] hash of attribute names and values
|
60
60
|
# @return [Setting]
|
61
61
|
def create!(attributes)
|
@@ -68,6 +68,7 @@ module SuperSettings
|
|
68
68
|
|
69
69
|
# Get all the settings. This will even return settings that have been marked as deleted.
|
70
70
|
# If you just want current settings, then call #active instead.
|
71
|
+
#
|
71
72
|
# @return [Array<Setting>]
|
72
73
|
def all
|
73
74
|
storage.with_connection do
|
@@ -76,6 +77,7 @@ module SuperSettings
|
|
76
77
|
end
|
77
78
|
|
78
79
|
# Get all the current settings.
|
80
|
+
#
|
79
81
|
# @return [Array<Setting>]
|
80
82
|
def active
|
81
83
|
storage.with_connection do
|
@@ -84,6 +86,7 @@ module SuperSettings
|
|
84
86
|
end
|
85
87
|
|
86
88
|
# Get all settings that have been updated since the specified time stamp.
|
89
|
+
#
|
87
90
|
# @param time [Time]
|
88
91
|
# @return [Array<Setting>]
|
89
92
|
def updated_since(time)
|
@@ -93,6 +96,7 @@ module SuperSettings
|
|
93
96
|
end
|
94
97
|
|
95
98
|
# Get a setting by its unique key.
|
99
|
+
#
|
96
100
|
# @return Setting
|
97
101
|
def find_by_key(key)
|
98
102
|
record = storage.with_connection { storage.find_by_key(key) }
|
@@ -103,6 +107,7 @@ module SuperSettings
|
|
103
107
|
|
104
108
|
# Return the maximum updated at value from all the rows. This is used in the caching
|
105
109
|
# scheme to determine if data needs to be reloaded from the database.
|
110
|
+
#
|
106
111
|
# @return [Time]
|
107
112
|
def last_updated_at
|
108
113
|
fetch_from_cache(LAST_UPDATED_CACHE_KEY) do
|
@@ -113,22 +118,20 @@ module SuperSettings
|
|
113
118
|
# Bulk update settings in a single database transaction. No changes will be saved
|
114
119
|
# if there are any invalid records.
|
115
120
|
#
|
116
|
-
#
|
121
|
+
# @example
|
117
122
|
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
130
|
-
# ])
|
131
|
-
# ```
|
123
|
+
# SuperSettings.bulk_update([
|
124
|
+
# {
|
125
|
+
# key: "setting-key",
|
126
|
+
# value: "foobar",
|
127
|
+
# value_type: "string",
|
128
|
+
# description: "A sample setting"
|
129
|
+
# },
|
130
|
+
# {
|
131
|
+
# key: "setting-to-delete",
|
132
|
+
# deleted: true
|
133
|
+
# }
|
134
|
+
# ])
|
132
135
|
#
|
133
136
|
# @param params [Array] Array of hashes with setting attributes. Each hash must include
|
134
137
|
# a "key" element to identify the setting. To update a key, it must also include at least
|
@@ -136,7 +139,7 @@ module SuperSettings
|
|
136
139
|
# the hash, it will be updated. If a setting with the given key does not exist, it will be created.
|
137
140
|
# A setting may also be deleted by providing the attribute "deleted: true".
|
138
141
|
# @return [Array] Boolean indicating if update succeeded, Array of settings affected by the update;
|
139
|
-
# if the settings were not updated, the
|
142
|
+
# if the settings were not updated, the +errors+ on the settings that failed validation will be filled.
|
140
143
|
def bulk_update(params, changed_by = nil)
|
141
144
|
all_valid, settings = update_settings(params, changed_by)
|
142
145
|
if all_valid
|
@@ -153,6 +156,8 @@ module SuperSettings
|
|
153
156
|
end
|
154
157
|
|
155
158
|
# Determine the value type from a value.
|
159
|
+
#
|
160
|
+
# @return [String]
|
156
161
|
def value_type(value)
|
157
162
|
case value
|
158
163
|
when Integer
|
@@ -171,6 +176,7 @@ module SuperSettings
|
|
171
176
|
end
|
172
177
|
|
173
178
|
# Clear the last updated timestamp from the cache.
|
179
|
+
#
|
174
180
|
# @api private
|
175
181
|
def clear_last_updated_cache
|
176
182
|
cache&.delete(Setting::LAST_UPDATED_CACHE_KEY)
|
@@ -179,8 +185,9 @@ module SuperSettings
|
|
179
185
|
private
|
180
186
|
|
181
187
|
# Updates settings in memory from an array of parameters.
|
182
|
-
#
|
183
|
-
#
|
188
|
+
#
|
189
|
+
# @param params [Array<Hash>] Each hash must contain a "key" element and may contain elements
|
190
|
+
# for "value", "value_type", "description", and "deleted".
|
184
191
|
# @param changed_by [String] Value to be stored in the history for each setting
|
185
192
|
# @return [Array] The first value is a boolean indicating if all the settings are valid,
|
186
193
|
# the second is an array of settings with their attributes updated in memory and ready to be saved.
|
@@ -247,20 +254,25 @@ module SuperSettings
|
|
247
254
|
end
|
248
255
|
end
|
249
256
|
|
250
|
-
#
|
257
|
+
# Get the unique key for the setting.
|
258
|
+
#
|
259
|
+
# @return [String]
|
251
260
|
def key
|
252
261
|
@record.key
|
253
262
|
end
|
254
263
|
|
255
|
-
# Set the value of the setting.
|
256
|
-
#
|
264
|
+
# Set the value of the setting. The value will be coerced to a string for storage.
|
265
|
+
#
|
266
|
+
# @param val [Object]
|
257
267
|
def key=(val)
|
258
268
|
val = val&.to_s
|
259
269
|
will_change!(:key, val) unless key == val
|
260
270
|
@record.key = val
|
261
271
|
end
|
262
272
|
|
263
|
-
#
|
273
|
+
# The value of a setting coerced to the appropriate class depending on its value type.
|
274
|
+
#
|
275
|
+
# @return [Object]
|
264
276
|
def value
|
265
277
|
if deleted?
|
266
278
|
nil
|
@@ -270,6 +282,7 @@ module SuperSettings
|
|
270
282
|
end
|
271
283
|
|
272
284
|
# Set the value of the setting.
|
285
|
+
#
|
273
286
|
# @param val [Object]
|
274
287
|
def value=(val)
|
275
288
|
val = serialize(val) unless val.is_a?(Array)
|
@@ -277,22 +290,31 @@ module SuperSettings
|
|
277
290
|
self.raw_value = val
|
278
291
|
end
|
279
292
|
|
293
|
+
# Get the type of value being stored in the setting.
|
294
|
+
#
|
295
|
+
# @return [String] one of string, integer, float, boolean, datetime, or array.
|
280
296
|
def value_type
|
281
297
|
@record.value_type
|
282
298
|
end
|
283
299
|
|
284
300
|
# Set the value type of the setting.
|
285
|
-
#
|
301
|
+
#
|
302
|
+
# @param val [String] one of string, integer, float, boolean, datetime, or array.
|
286
303
|
def value_type=(val)
|
287
304
|
val = val&.to_s
|
288
305
|
will_change!(:value_type, val) unless value_type == val
|
289
306
|
@record.value_type = val
|
290
307
|
end
|
291
308
|
|
309
|
+
# Get the description for the setting.
|
310
|
+
#
|
311
|
+
# @return [String]
|
292
312
|
def description
|
293
313
|
@record.description
|
294
314
|
end
|
295
315
|
|
316
|
+
# Set the description of the setting.
|
317
|
+
#
|
296
318
|
# @param val [String]
|
297
319
|
def description=(val)
|
298
320
|
val = val&.to_s
|
@@ -301,6 +323,9 @@ module SuperSettings
|
|
301
323
|
@record.description = val
|
302
324
|
end
|
303
325
|
|
326
|
+
# Return true if the setting has been marked as deleted.
|
327
|
+
#
|
328
|
+
# @return [Boolean]
|
304
329
|
def deleted?
|
305
330
|
@record.deleted?
|
306
331
|
end
|
@@ -309,6 +334,7 @@ module SuperSettings
|
|
309
334
|
|
310
335
|
# Set the deleted flag on the setting. Deleted settings are not visible but are not actually
|
311
336
|
# removed from the data store.
|
337
|
+
#
|
312
338
|
# @param val [Boolean]
|
313
339
|
def deleted=(val)
|
314
340
|
val = Coerce.boolean(val)
|
@@ -316,10 +342,15 @@ module SuperSettings
|
|
316
342
|
@record.deleted = val
|
317
343
|
end
|
318
344
|
|
345
|
+
# Get the time the setting was first created.
|
346
|
+
#
|
347
|
+
# @return [Time]
|
319
348
|
def created_at
|
320
349
|
@record.created_at
|
321
350
|
end
|
322
351
|
|
352
|
+
# Set the time when the setting was created.
|
353
|
+
#
|
323
354
|
# @param val [Time, DateTime]
|
324
355
|
def created_at=(val)
|
325
356
|
val = Coerce.time(val)
|
@@ -327,10 +358,15 @@ module SuperSettings
|
|
327
358
|
@record.created_at = val
|
328
359
|
end
|
329
360
|
|
361
|
+
# Get the time the setting was last updated.
|
362
|
+
#
|
363
|
+
# @return [Time]
|
330
364
|
def updated_at
|
331
365
|
@record.updated_at
|
332
366
|
end
|
333
367
|
|
368
|
+
# Set the time when the setting was last updated.
|
369
|
+
#
|
334
370
|
# @param val [Time, DateTime]
|
335
371
|
def updated_at=(val)
|
336
372
|
val = Coerce.time(val)
|
@@ -338,50 +374,49 @@ module SuperSettings
|
|
338
374
|
@record.updated_at = val
|
339
375
|
end
|
340
376
|
|
341
|
-
#
|
377
|
+
# Return true if the setting has a string value type.
|
378
|
+
#
|
379
|
+
# @return [Boolean]
|
342
380
|
def string?
|
343
381
|
value_type == STRING
|
344
382
|
end
|
345
383
|
|
346
|
-
#
|
384
|
+
# Return true if the setting has an integer value type.
|
385
|
+
#
|
386
|
+
# @return [Boolean]
|
347
387
|
def integer?
|
348
388
|
value_type == INTEGER
|
349
389
|
end
|
350
390
|
|
351
|
-
#
|
391
|
+
# Return true if the setting has a float value type.
|
392
|
+
# @return [Boolean]
|
352
393
|
def float?
|
353
394
|
value_type == FLOAT
|
354
395
|
end
|
355
396
|
|
356
|
-
#
|
397
|
+
# Return true if the setting has a boolean value type.
|
398
|
+
# @return [Boolean]
|
357
399
|
def boolean?
|
358
400
|
value_type == BOOLEAN
|
359
401
|
end
|
360
402
|
|
361
|
-
#
|
403
|
+
# Return true if the setting has a datetime value type.
|
404
|
+
# @return [Boolean]
|
362
405
|
def datetime?
|
363
406
|
value_type == DATETIME
|
364
407
|
end
|
365
408
|
|
366
|
-
#
|
409
|
+
# Return true if the setting has an array value type.
|
410
|
+
# @return [Boolean]
|
367
411
|
def array?
|
368
412
|
value_type == ARRAY
|
369
413
|
end
|
370
414
|
|
371
|
-
# @return [true] if the setting has a secret value type.
|
372
|
-
def secret?
|
373
|
-
value_type == SECRET
|
374
|
-
end
|
375
|
-
|
376
|
-
# @return [true] if the setting is a secret setting and the value is encrypted in the database.
|
377
|
-
def encrypted?
|
378
|
-
secret? && Encryption.encrypted?(raw_value)
|
379
|
-
end
|
380
|
-
|
381
415
|
# Save the setting to the data storage engine.
|
416
|
+
#
|
382
417
|
# @return [void]
|
383
418
|
def save!
|
384
|
-
|
419
|
+
record_value_change
|
385
420
|
|
386
421
|
unless valid?
|
387
422
|
raise InvalidRecordError.new(errors.values.join("; "))
|
@@ -398,7 +433,6 @@ module SuperSettings
|
|
398
433
|
|
399
434
|
begin
|
400
435
|
self.class.clear_last_updated_cache
|
401
|
-
redact_history! if history_needs_redacting?
|
402
436
|
ensure
|
403
437
|
clear_changes
|
404
438
|
end
|
@@ -406,27 +440,36 @@ module SuperSettings
|
|
406
440
|
nil
|
407
441
|
end
|
408
442
|
|
409
|
-
#
|
443
|
+
# Return true if the record has been stored in the data storage engine.
|
444
|
+
#
|
445
|
+
# @return [Boolean]
|
410
446
|
def persisted?
|
411
447
|
@record.persisted?
|
412
448
|
end
|
413
449
|
|
414
|
-
#
|
450
|
+
# Return true if the record has valid data.
|
451
|
+
#
|
452
|
+
# @return [Boolean]
|
415
453
|
def valid?
|
416
454
|
validate!
|
417
455
|
@errors.empty?
|
418
456
|
end
|
419
457
|
|
420
|
-
#
|
458
|
+
# Return hash of errors generated from the last call to +valid?+
|
459
|
+
#
|
460
|
+
# @return [Hash<String, Array<String>>]
|
421
461
|
attr_reader :errors
|
422
462
|
|
423
463
|
# Mark the record as deleted. The record will not actually be deleted since it's still needed
|
424
464
|
# for caching purposes, but it will no longer be returned by queries.
|
465
|
+
#
|
466
|
+
# @return [void]
|
425
467
|
def delete!
|
426
468
|
update!(deleted: true)
|
427
469
|
end
|
428
470
|
|
429
471
|
# Update the setting attributes and save it.
|
472
|
+
#
|
430
473
|
# @param attributes [Hash]
|
431
474
|
# @return [void]
|
432
475
|
def update!(attributes)
|
@@ -436,12 +479,14 @@ module SuperSettings
|
|
436
479
|
|
437
480
|
# Return array of history items reflecting changes made to the setting over time. Items
|
438
481
|
# should be returned in reverse chronological order so that the most recent changes are first.
|
482
|
+
#
|
439
483
|
# @return [Array<SuperSettings::History>]
|
440
484
|
def history(limit: nil, offset: 0)
|
441
485
|
@record.history(limit: limit, offset: offset)
|
442
486
|
end
|
443
487
|
|
444
488
|
# Serialize to a hash that is used for rendering JSON responses.
|
489
|
+
#
|
445
490
|
# @return [Hash]
|
446
491
|
def as_json(options = nil)
|
447
492
|
attributes = {
|
@@ -452,12 +497,12 @@ module SuperSettings
|
|
452
497
|
created_at: created_at,
|
453
498
|
updated_at: updated_at
|
454
499
|
}
|
455
|
-
attributes[:encrypted] = encrypted? if secret?
|
456
500
|
attributes[:deleted] = true if deleted?
|
457
501
|
attributes
|
458
502
|
end
|
459
503
|
|
460
504
|
# Serialize to a JSON string.
|
505
|
+
#
|
461
506
|
# @return [String]
|
462
507
|
def to_json(options = nil)
|
463
508
|
as_json.to_json(options)
|
@@ -486,12 +531,6 @@ module SuperSettings
|
|
486
531
|
else
|
487
532
|
Array(value).reject { |v| v.respond_to?(:empty?) ? v.empty? : v.to_s.empty? }.collect { |v| v.to_s.freeze }.freeze
|
488
533
|
end
|
489
|
-
when Setting::SECRET
|
490
|
-
begin
|
491
|
-
Encryption.decrypt(value).freeze
|
492
|
-
rescue Encryption::InvalidSecretError
|
493
|
-
nil
|
494
|
-
end
|
495
534
|
else
|
496
535
|
value.freeze
|
497
536
|
end
|
@@ -510,18 +549,10 @@ module SuperSettings
|
|
510
549
|
end
|
511
550
|
end
|
512
551
|
|
513
|
-
# Set the raw string value that will be persisted to the data store.
|
514
|
-
def set_raw_value
|
515
|
-
if value_type == Setting::SECRET && !raw_value.to_s.empty? && (changed?(:raw_value) || !Encryption.encrypted?(raw_value))
|
516
|
-
self.raw_value = Encryption.encrypt(raw_value)
|
517
|
-
end
|
518
|
-
record_value_change
|
519
|
-
end
|
520
|
-
|
521
552
|
# Update the histories association whenever the value or key is changed.
|
522
553
|
def record_value_change
|
523
554
|
return unless changed?(:raw_value) || changed?(:deleted) || changed?(:key)
|
524
|
-
recorded_value = (deleted?
|
555
|
+
recorded_value = (deleted? ? nil : raw_value)
|
525
556
|
@record.create_history(value: recorded_value, deleted: deleted?, changed_by: changed_by, created_at: Time.now)
|
526
557
|
end
|
527
558
|
|
@@ -544,14 +575,6 @@ module SuperSettings
|
|
544
575
|
@changes.include?(attribute.to_s)
|
545
576
|
end
|
546
577
|
|
547
|
-
def history_needs_redacting?
|
548
|
-
value_type == Setting::SECRET && changed?(:value_type)
|
549
|
-
end
|
550
|
-
|
551
|
-
def redact_history!
|
552
|
-
@record.send(:redact_history!)
|
553
|
-
end
|
554
|
-
|
555
578
|
def raw_value=(val)
|
556
579
|
val = val&.to_s
|
557
580
|
val = nil if val&.empty?
|
@@ -4,9 +4,11 @@ module SuperSettings
|
|
4
4
|
module Storage
|
5
5
|
# ActiveRecord implementation of the SuperSettings::Storage model.
|
6
6
|
#
|
7
|
-
# To use this model, you must run the migration included with the gem.
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# To use this model, you must run the migration included with the gem. If the gem
|
8
|
+
# is mounted as an engine in a Rails applicationThe migration can be installed with
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# rake app:super_settings:install:migrations
|
10
12
|
class ActiveRecordStorage
|
11
13
|
# Base class that the models extend from.
|
12
14
|
class ApplicationRecord < ActiveRecord::Base
|
@@ -85,7 +87,7 @@ module SuperSettings
|
|
85
87
|
end
|
86
88
|
|
87
89
|
delegate :key, :key=, :raw_value, :raw_value=, :value_type, :value_type=, :description, :description=,
|
88
|
-
:deleted?, :deleted=, :updated_at, :updated_at=, :created_at, :created_at=, :persisted?,
|
90
|
+
:deleted?, :deleted=, :updated_at, :updated_at=, :created_at, :created_at=, :persisted?,
|
89
91
|
to: :@model
|
90
92
|
|
91
93
|
def initialize(attributes = {})
|
@@ -96,6 +98,28 @@ module SuperSettings
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
101
|
+
def save!
|
102
|
+
begin
|
103
|
+
@model.save!
|
104
|
+
rescue ActiveRecord::RecordNotUnique => e
|
105
|
+
# Gracefully handle duplicate key constraint on the database; in this case the existing
|
106
|
+
# record should be updated.
|
107
|
+
duplicate = @model.class.find_by(key: @model.key)
|
108
|
+
raise e if duplicate == @model
|
109
|
+
duplicate.raw_value = @model.raw_value
|
110
|
+
duplicate.value_type = @model.value_type
|
111
|
+
duplicate.description = @model.description
|
112
|
+
duplicate.deleted = false
|
113
|
+
@model.transaction do
|
114
|
+
if @model.persisted?
|
115
|
+
@model.reload.update!(deleted: true)
|
116
|
+
end
|
117
|
+
duplicate.save!
|
118
|
+
end
|
119
|
+
@model = duplicate
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
99
123
|
def history(limit: nil, offset: 0)
|
100
124
|
finder = @model.history_items.order(id: :desc).offset(offset)
|
101
125
|
finder = finder.limit(limit) if limit
|
@@ -112,12 +136,6 @@ module SuperSettings
|
|
112
136
|
@model.history_items.build(history_attributes)
|
113
137
|
end
|
114
138
|
end
|
115
|
-
|
116
|
-
protected
|
117
|
-
|
118
|
-
def redact_history!
|
119
|
-
@model.history_items.update_all(value: nil)
|
120
|
-
end
|
121
139
|
end
|
122
140
|
end
|
123
141
|
end
|
@@ -3,11 +3,11 @@
|
|
3
3
|
require "json"
|
4
4
|
require "net/http"
|
5
5
|
|
6
|
-
# SuperSettings::Storage model that reads from a remote service running the SuperSettings REST API.
|
7
|
-
# This storage engine is read only. It is intended to allow microservices to read settings from a
|
8
|
-
# central application that exposes the SuperSettings::RestAPI.
|
9
6
|
module SuperSettings
|
10
7
|
module Storage
|
8
|
+
# SuperSettings::Storage model that reads from a remote service running the SuperSettings REST API.
|
9
|
+
# This storage engine is read only. It is intended to allow microservices to read settings from a
|
10
|
+
# central application that exposes the SuperSettings::RestAPI.
|
11
11
|
class HttpStorage
|
12
12
|
include Storage
|
13
13
|
|
@@ -92,14 +92,14 @@ module SuperSettings
|
|
92
92
|
private
|
93
93
|
|
94
94
|
def call_api(method, path, params = {})
|
95
|
-
url_params = (method == :get ? query_params.merge(params) : query_params)
|
95
|
+
url_params = ((method == :get) ? query_params.merge(params) : query_params)
|
96
96
|
uri = api_uri(path, url_params)
|
97
97
|
|
98
98
|
body = nil
|
99
99
|
request_headers = DEFAULT_HEADERS.merge(headers)
|
100
100
|
if method == :post && !params&.empty?
|
101
101
|
body = params.to_json
|
102
|
-
request_headers["
|
102
|
+
request_headers["content-type"] = "application/json; charset=utf8-"
|
103
103
|
end
|
104
104
|
|
105
105
|
response = http_request(method: method, uri: uri, headers: request_headers, body: body)
|
@@ -131,7 +131,7 @@ module SuperSettings
|
|
131
131
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
132
132
|
end
|
133
133
|
|
134
|
-
request = (method == :post ? Net::HTTP::Post.new(uri.request_uri) : Net::HTTP::Get.new(uri.request_uri))
|
134
|
+
request = ((method == :post) ? Net::HTTP::Post.new(uri.request_uri) : Net::HTTP::Get.new(uri.request_uri))
|
135
135
|
set_headers(request, headers)
|
136
136
|
request.body = body if body
|
137
137
|
|
@@ -144,7 +144,7 @@ module SuperSettings
|
|
144
144
|
end
|
145
145
|
|
146
146
|
if response.is_a?(Net::HTTPRedirection)
|
147
|
-
location = resp["
|
147
|
+
location = resp["location"]
|
148
148
|
if redirect_count < 5 && SuperSettings::Coerce.present?(location)
|
149
149
|
return http_request(method: :get, uri: URI(location), headers: headers, body: body, redirect_count: redirect_count + 1)
|
150
150
|
end
|
@@ -255,12 +255,6 @@ module SuperSettings
|
|
255
255
|
!!(defined?(@persisted) && @persisted)
|
256
256
|
end
|
257
257
|
|
258
|
-
protected
|
259
|
-
|
260
|
-
def redact_history!
|
261
|
-
# No-op since history is maintained by the source system.
|
262
|
-
end
|
263
|
-
|
264
258
|
private
|
265
259
|
|
266
260
|
def set_persisted!
|
@@ -270,10 +264,6 @@ module SuperSettings
|
|
270
264
|
def call_api(method, path, params = {})
|
271
265
|
self.class.send(:call_api, method, path, params)
|
272
266
|
end
|
273
|
-
|
274
|
-
def encrypted=(value)
|
275
|
-
# No op; needed for API compatibility
|
276
|
-
end
|
277
267
|
end
|
278
268
|
end
|
279
269
|
end
|
@@ -2,27 +2,26 @@
|
|
2
2
|
|
3
3
|
require "json"
|
4
4
|
|
5
|
-
# Redis implementation of the SuperSettings::Storage model.
|
6
|
-
#
|
7
|
-
# You must define the redis connection to use by setting the redis attribute on the class.
|
8
|
-
# This can either be a `Redis` object or a block that yields a `Redis` object. You can use the
|
9
|
-
# block form if you need to get the `Redis` object at runtime instead of having a static object.
|
10
|
-
#
|
11
|
-
# ```ruby
|
12
|
-
# SuperSettings::Storage::RedisStorage.redis = Redis.new(url: ENV["REDIS_URL"])
|
13
|
-
#
|
14
|
-
# SuperSettings::Storage::RedisStorage.redis = lambda { RedisClient.get(:settings) }
|
15
|
-
# ```
|
16
|
-
#
|
17
|
-
# You can also use the [connection_pool]() gem to provide a pool of Redis connecions for
|
18
|
-
# a multi-threaded application. The connection_pool gem is not a dependency of this gem,
|
19
|
-
# so you would need to add it to your application dependencies to use it.
|
20
|
-
#
|
21
|
-
# ```ruby
|
22
|
-
# SuperSettings::Storage::RedisStorage.redis = ConnectionPool.new(size: 5) { Redis.new(url: ENV["REDIS_URL"]) }
|
23
|
-
# ```
|
24
5
|
module SuperSettings
|
25
6
|
module Storage
|
7
|
+
# Redis implementation of the SuperSettings::Storage model.
|
8
|
+
#
|
9
|
+
# You must define the redis connection to use by setting the redis attribute on the class.
|
10
|
+
# This can either be a +Redis+ object or a block that yields a +Redis+ object. You can use the
|
11
|
+
# block form if you need to get the Redis object at runtime instead of having a static object.
|
12
|
+
#
|
13
|
+
# You can also use the [connection_pool]() gem to provide a pool of Redis connecions for
|
14
|
+
# a multi-threaded application. The connection_pool gem is not a dependency of this gem,
|
15
|
+
# so you would need to add it to your application dependencies to use it.
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# SuperSettings::Storage::RedisStorage.redis = Redis.new(url: ENV["REDIS_URL"])
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# SuperSettings::Storage::RedisStorage.redis = lambda { RedisClient.get(:settings) }
|
22
|
+
#
|
23
|
+
# @example
|
24
|
+
# SuperSettings::Storage::RedisStorage.redis = ConnectionPool.new(size: 5) { Redis.new(url: ENV["REDIS_URL"]) }
|
26
25
|
class RedisStorage
|
27
26
|
include Storage
|
28
27
|
|
@@ -122,7 +121,8 @@ module SuperSettings
|
|
122
121
|
def find_by_key(key)
|
123
122
|
json = with_redis { |redis| redis.hget(SETTINGS_KEY, key) }
|
124
123
|
return nil unless json
|
125
|
-
load_from_json(json)
|
124
|
+
record = load_from_json(json)
|
125
|
+
record unless record.deleted?
|
126
126
|
end
|
127
127
|
|
128
128
|
def last_updated_at
|
@@ -249,19 +249,6 @@ module SuperSettings
|
|
249
249
|
!!(defined?(@persisted) && @persisted)
|
250
250
|
end
|
251
251
|
|
252
|
-
protected
|
253
|
-
|
254
|
-
def redact_history!
|
255
|
-
after_commit do
|
256
|
-
histories = HistoryStorage.find_all_by_key(key: key)
|
257
|
-
histories.each { |item| item.value = nil }
|
258
|
-
self.class.transaction do
|
259
|
-
HistoryStorage.destroy_all_by_key(key)
|
260
|
-
histories.reverse.each(&:save!)
|
261
|
-
end
|
262
|
-
end
|
263
|
-
end
|
264
|
-
|
265
252
|
private
|
266
253
|
|
267
254
|
def after_commit(&block)
|