super_settings 1.0.1 → 2.0.0

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -2
  3. data/README.md +121 -16
  4. data/VERSION +1 -1
  5. data/app/helpers/super_settings/settings_helper.rb +13 -3
  6. data/app/views/layouts/super_settings/settings.html.erb +1 -1
  7. data/config/routes.rb +1 -1
  8. data/db/migrate/20210414004553_create_super_settings.rb +1 -7
  9. data/lib/super_settings/application/api.js +4 -1
  10. data/lib/super_settings/application/helper.rb +56 -17
  11. data/lib/super_settings/application/images/arrow-down-short.svg +3 -0
  12. data/lib/super_settings/application/images/arrow-up-short.svg +3 -0
  13. data/lib/super_settings/application/images/info-circle.svg +4 -0
  14. data/lib/super_settings/application/images/pencil-square.svg +4 -0
  15. data/lib/super_settings/application/images/plus.svg +3 -1
  16. data/lib/super_settings/application/images/trash3.svg +3 -0
  17. data/lib/super_settings/application/images/x-circle.svg +4 -0
  18. data/lib/super_settings/application/index.html.erb +54 -37
  19. data/lib/super_settings/application/layout.html.erb +5 -2
  20. data/lib/super_settings/application/layout_styles.css +7 -151
  21. data/lib/super_settings/application/layout_vars.css.erb +21 -0
  22. data/lib/super_settings/application/scripts.js +100 -21
  23. data/lib/super_settings/application/style_vars.css.erb +62 -0
  24. data/lib/super_settings/application/styles.css +183 -14
  25. data/lib/super_settings/application.rb +18 -11
  26. data/lib/super_settings/attributes.rb +1 -8
  27. data/lib/super_settings/configuration.rb +9 -0
  28. data/lib/super_settings/context/current.rb +33 -0
  29. data/lib/super_settings/context.rb +3 -0
  30. data/lib/super_settings/controller_actions.rb +2 -2
  31. data/lib/super_settings/engine.rb +1 -3
  32. data/lib/super_settings/history_item.rb +1 -1
  33. data/lib/super_settings/http_client.rb +165 -0
  34. data/lib/super_settings/local_cache.rb +0 -15
  35. data/lib/super_settings/rack_application.rb +3 -3
  36. data/lib/super_settings/rest_api.rb +5 -4
  37. data/lib/super_settings/setting.rb +14 -3
  38. data/lib/super_settings/storage/active_record_storage/models.rb +28 -0
  39. data/lib/super_settings/storage/active_record_storage.rb +10 -20
  40. data/lib/super_settings/storage/history_attributes.rb +31 -0
  41. data/lib/super_settings/storage/http_storage.rb +60 -184
  42. data/lib/super_settings/storage/json_storage.rb +201 -0
  43. data/lib/super_settings/storage/mongodb_storage.rb +238 -0
  44. data/lib/super_settings/storage/redis_storage.rb +50 -111
  45. data/lib/super_settings/storage/s3_storage.rb +165 -0
  46. data/lib/super_settings/storage/storage_attributes.rb +64 -0
  47. data/lib/super_settings/storage/test_storage.rb +3 -5
  48. data/lib/super_settings/storage/transaction.rb +67 -0
  49. data/lib/super_settings/storage.rb +17 -8
  50. data/lib/super_settings/time_precision.rb +36 -0
  51. data/lib/super_settings.rb +48 -13
  52. data/super_settings.gemspec +11 -2
  53. metadata +30 -12
  54. data/lib/super_settings/application/images/edit.svg +0 -1
  55. data/lib/super_settings/application/images/info.svg +0 -1
  56. data/lib/super_settings/application/images/slash.svg +0 -1
  57. data/lib/super_settings/application/images/trash.svg +0 -1
  58. /data/{MIT-LICENSE → MIT-LICENSE.txt} +0 -0
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SuperSettings
4
+ module Storage
5
+ # This is an abstract storage class that provides support for storing settings in a JSON file.
6
+ # The settings are stored in JSON as an array of hashes with each hash representing a setting.
7
+ #
8
+ # Setting history should be stored in separate JSON files per key and are loaded separately
9
+ # from the main settings file.
10
+ #
11
+ # This class can be used as the base for any storage class where the settings are all stored
12
+ # together in a single JSON payload.
13
+ #
14
+ # Subclasses must implement the following methods:
15
+ # - self.all
16
+ # - self.last_updated_at
17
+ # - save!
18
+ class JSONStorage < StorageAttributes
19
+ include Transaction
20
+
21
+ class HistoryStorage < HistoryAttributes
22
+ def created_at=(val)
23
+ super(TimePrecision.new(val).time)
24
+ end
25
+ end
26
+
27
+ class << self
28
+ def all
29
+ parse_settings(settings_json_payload)
30
+ end
31
+
32
+ def updated_since(timestamp)
33
+ all.select { |setting| setting.updated_at > timestamp }
34
+ end
35
+
36
+ def find_by_key(key)
37
+ active.detect { |setting| setting.key == key }
38
+ end
39
+
40
+ def save_all(changes)
41
+ existing = {}
42
+ parse_settings(settings_json_payload).each do |setting|
43
+ existing[setting.key] = setting
44
+ end
45
+
46
+ changes.each do |setting|
47
+ existing[setting.key] = setting
48
+ end
49
+
50
+ settings = existing.values.sort_by(&:key)
51
+ changed_histories = {}
52
+ changes.collect do |setting|
53
+ history = (setting.new_history + setting.history).sort_by(&:created_at).reverse
54
+ changed_histories[setting.key] = history.collect do |history_item|
55
+ {
56
+ value: history_item.value,
57
+ changed_by: history_item.changed_by,
58
+ created_at: history_item.created_at&.iso8601(6),
59
+ deleted: history_item.deleted?
60
+ }
61
+ end
62
+ setting.new_history.clear
63
+ end
64
+
65
+ settings_json = JSON.dump(settings.collect(&:as_json))
66
+ save_settings_json(settings_json)
67
+
68
+ changed_histories.each do |setting_key, setting_history|
69
+ history_json = JSON.dump(setting_history)
70
+ save_history_json(setting_key, history_json)
71
+ end
72
+ end
73
+
74
+ # Heper method to load settings from a JSON string.
75
+ #
76
+ # @param json [String] JSON string to parse.
77
+ # @return [Array<SuperSettings::Storage::JSONStorage>] Array of settings.
78
+ def parse_settings(json)
79
+ return [] if Coerce.blank?(json)
80
+
81
+ JSON.parse(json).collect do |attributes|
82
+ setting = new(
83
+ key: attributes["key"],
84
+ raw_value: attributes["value"],
85
+ description: attributes["description"],
86
+ value_type: attributes["value_type"],
87
+ updated_at: Time.parse(attributes["updated_at"]),
88
+ created_at: Time.parse(attributes["created_at"]),
89
+ deleted: attributes["deleted"]
90
+ )
91
+ setting.persisted = true
92
+ setting
93
+ end
94
+ end
95
+
96
+ protected
97
+
98
+ # Subclasses must implement this method to return the JSON payload containing all of the
99
+ # settings as a string.
100
+ #
101
+ # @return [String] JSON string.
102
+ def settings_json_payload
103
+ # :nocov:
104
+ raise NotImplementedError
105
+ # :nocov:
106
+ end
107
+
108
+ # Subclasses must implement this method to persist the JSON payload containing all of the
109
+ # settings.
110
+ #
111
+ # @param json [String] JSON string to save.
112
+ def save_settings_json(json)
113
+ # :nocov:
114
+ raise NotImplementedError
115
+ # :nocov:
116
+ end
117
+
118
+ # Subclasses must implement this method to persist the JSON payload containing the history
119
+ # records for a setting key.
120
+ #
121
+ # @param key [String] Setting key.
122
+ # @param json [String] JSON string to save.
123
+ # @return [void]
124
+ def save_history_json(key, json)
125
+ # :nocov:
126
+ raise NotImplementedError
127
+ # :nocov:
128
+ end
129
+ end
130
+
131
+ def initialize(*)
132
+ @new_history = []
133
+ super
134
+ end
135
+
136
+ def created_at=(val)
137
+ super(TimePrecision.new(val).time)
138
+ end
139
+
140
+ def updated_at=(val)
141
+ super(TimePrecision.new(val).time)
142
+ end
143
+
144
+ def history(limit: nil, offset: 0)
145
+ history = fetch_history
146
+ limit ||= history.length
147
+ history[offset, limit].collect do |record|
148
+ HistoryItem.new(key: key, value: record.value, changed_by: record.changed_by, created_at: record.created_at, deleted: record.deleted?)
149
+ end
150
+ end
151
+
152
+ def create_history(changed_by:, created_at:, value: nil, deleted: false)
153
+ history = HistoryStorage.new(key: key, value: value, changed_by: changed_by, created_at: created_at, deleted: deleted)
154
+ @new_history.unshift(history)
155
+ history
156
+ end
157
+
158
+ attr_reader :new_history
159
+
160
+ def as_json
161
+ {
162
+ key: key,
163
+ value: raw_value,
164
+ value_type: value_type,
165
+ description: description,
166
+ created_at: created_at&.iso8601(6),
167
+ updated_at: updated_at&.iso8601(6),
168
+ deleted: deleted?
169
+ }
170
+ end
171
+
172
+ protected
173
+
174
+ # Subclasses must implement this method to return the JSON payload containing all of the
175
+ # history records for the setting key. The payload must be an array that contains hashes
176
+ # with the keys "value", "changed_by", "deleted", and "created_at".
177
+ def fetch_history_json
178
+ raise NotImplementedError
179
+ end
180
+
181
+ private
182
+
183
+ def fetch_history
184
+ json = fetch_history_json
185
+ history_payload = Coerce.blank?(json) ? [] : JSON.parse(json)
186
+
187
+ history_items = history_payload.collect do |attributes|
188
+ HistoryStorage.new(
189
+ key: key,
190
+ value: attributes["value"],
191
+ changed_by: attributes["changed_by"],
192
+ created_at: Time.parse(attributes["created_at"]),
193
+ deleted: attributes["deleted"]
194
+ )
195
+ end
196
+
197
+ history_items.sort_by(&:created_at).reverse
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,238 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mongo"
4
+
5
+ module SuperSettings
6
+ module Storage
7
+ # MongoDB implementation of the SuperSettings::Storage model.
8
+ #
9
+ # You must define the connection URL to use by setting the `url` or `mongodb` attribute on the class.
10
+ #
11
+ # @example
12
+ # SuperSettings::Storage::MongoDBStorage.url = "mongodb://user:password@localhost:27017/super_settings"
13
+ #
14
+ # @example
15
+ # SuperSettings::Storage::MongoDBStorage.mongodb = Mongo::Client.new("mongodb://user:password@localhost:27017/super_settings")
16
+ class MongoDBStorage < StorageAttributes
17
+ include Storage
18
+ include Transaction
19
+
20
+ DEFAULT_COLLECTION_NAME = "super_settings"
21
+
22
+ @mongodb = nil
23
+ @url = nil
24
+ @url_hash = @url.hash
25
+ @collection_name = DEFAULT_COLLECTION_NAME
26
+ @mutex = Mutex.new
27
+
28
+ class HistoryStorage < HistoryAttributes
29
+ def as_bson
30
+ attributes = {
31
+ value: value,
32
+ changed_by: changed_by,
33
+ created_at: created_at
34
+ }
35
+ attributes[:deleted] = true if deleted?
36
+ attributes
37
+ end
38
+ end
39
+
40
+ class << self
41
+ attr_writer :url, :mongodb
42
+ attr_accessor :collection_name
43
+
44
+ def mongodb
45
+ if @mongodb.nil? || @url_hash != @url.hash
46
+ @mutex.synchronize do
47
+ unless @url_hash == @url.hash
48
+ @url_hash = @url.hash
49
+ @mongodb = Mongo::Client.new(@url)
50
+ create_indexes!(@mongodb)
51
+ end
52
+ end
53
+ end
54
+ @mongodb
55
+ end
56
+
57
+ def settings_collection
58
+ mongodb[collection_name]
59
+ end
60
+
61
+ def updated_since(time)
62
+ time = TimePrecision.new(time, :millisecond).time
63
+ settings_collection.find(updated_at: {"$gt": time}).projection(history: 0).sort({updated_at: -1}).collect do |attributes|
64
+ record = new(attributes)
65
+ record.persisted = true
66
+ record
67
+ end
68
+ end
69
+
70
+ def all
71
+ settings_collection.find.projection(history: 0).collect do |attributes|
72
+ record = new(attributes)
73
+ record.persisted = true
74
+ record
75
+ end
76
+ end
77
+
78
+ def find_by_key(key)
79
+ query = {
80
+ key: key,
81
+ deleted: false
82
+ }
83
+ record = settings_collection.find(query).projection(history: 0).first
84
+ new(record) if record
85
+ end
86
+
87
+ def last_updated_at
88
+ last_updated_setting = settings_collection.find.projection(updated_at: 1).sort(updated_at: -1).limit(1).first
89
+ last_updated_setting["updated_at"] if last_updated_setting
90
+ end
91
+
92
+ def destroy_all
93
+ settings_collection.delete_many({})
94
+ end
95
+
96
+ def save_all(changes)
97
+ upserts = changes.collect { |setting| upsert(setting) }
98
+ changes.each { |setting| setting.new_history.clear }
99
+ settings_collection.bulk_write(upserts)
100
+ true
101
+ end
102
+
103
+ protected
104
+
105
+ def default_load_asynchronous?
106
+ true
107
+ end
108
+
109
+ private
110
+
111
+ def upsert(setting)
112
+ doc = setting.as_bson
113
+ history = setting.new_history.collect(&:as_bson)
114
+ {
115
+ update_one: {
116
+ filter: {key: setting.key},
117
+ update: {
118
+ "$set": doc.except(:key, :history),
119
+ "$setOnInsert": {key: setting.key},
120
+ "$push": {history: {"$each": history}}
121
+ },
122
+ upsert: true
123
+ }
124
+ }
125
+ end
126
+
127
+ def create_indexes!(client)
128
+ collection = client[collection_name]
129
+ collection_exists = client.database.collection_names.include?(collection.name)
130
+ existing_indexes = (collection_exists ? collection.indexes.to_a : [])
131
+
132
+ unique_key_index = {key: 1}
133
+ unless existing_indexes.any? { |index| index["key"] == unique_key_index }
134
+ collection.indexes.create_one(unique_key_index, unique: true)
135
+ end
136
+
137
+ updated_at_index = {updated_at: -1}
138
+ unless existing_indexes.any? { |index| index["key"] == updated_at_index }
139
+ collection.indexes.create_one(updated_at_index)
140
+ end
141
+
142
+ history_created_at_desc_index = {key: 1, "history.created_at": -1}
143
+ unless existing_indexes.any? { |index| index["key"] == history_created_at_desc_index }
144
+ collection.indexes.create_one(history_created_at_desc_index)
145
+ end
146
+ end
147
+ end
148
+
149
+ attr_reader :new_history
150
+
151
+ def initialize(*)
152
+ @new_history = []
153
+ super
154
+ end
155
+
156
+ def history(limit: nil, offset: 0)
157
+ pipeline = [
158
+ {
159
+ "$match": {key: key}
160
+ },
161
+ {
162
+ "$addFields": {
163
+ history: {
164
+ "$sortArray": {
165
+ input: "$history",
166
+ sortBy: {created_at: -1}
167
+ }
168
+ }
169
+ }
170
+ }
171
+ ]
172
+
173
+ if limit || offset > 0
174
+ pipeline << {
175
+ "$addFields": {
176
+ history: {
177
+ "$slice": ["$history", offset, (limit || {"$size": "$history"})]
178
+ }
179
+ }
180
+ }
181
+ end
182
+
183
+ pipeline << {
184
+ "$project": {
185
+ _id: 0,
186
+ history: 1
187
+ }
188
+ }
189
+
190
+ record = self.class.settings_collection.aggregate(pipeline).to_a.first
191
+ return [] unless record && record["history"].is_a?(Array)
192
+
193
+ record["history"].collect do |record|
194
+ HistoryItem.new(key: key, value: record["value"], changed_by: record["changed_by"], created_at: record["created_at"], deleted: record["deleted"])
195
+ end
196
+ end
197
+
198
+ def create_history(changed_by:, created_at:, value: nil, deleted: false)
199
+ created_at = TimePrecision.new(created_at, :millisecond).time
200
+ history = HistoryStorage.new(key: key, value: value, changed_by: changed_by, created_at: created_at, deleted: deleted)
201
+ @new_history.unshift(history)
202
+ history
203
+ end
204
+
205
+ def created_at=(val)
206
+ super(TimePrecision.new(val, :millisecond).time)
207
+ end
208
+
209
+ def updated_at=(val)
210
+ super(TimePrecision.new(val, :millisecond).time)
211
+ end
212
+
213
+ def destroy
214
+ settings_collection.delete_one(key: key)
215
+ end
216
+
217
+ def as_bson
218
+ {
219
+ key: key,
220
+ raw_value: raw_value,
221
+ value_type: value_type,
222
+ description: description,
223
+ created_at: created_at,
224
+ updated_at: updated_at,
225
+ deleted: deleted?
226
+ }
227
+ end
228
+ end
229
+
230
+ def created_at=(val)
231
+ super(TimePrecision.new(val, :millisecond).time)
232
+ end
233
+
234
+ def updated_at=(val)
235
+ super(TimePrecision.new(val, :millisecond).time)
236
+ end
237
+ end
238
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
3
+ require "redis"
4
4
 
5
5
  module SuperSettings
6
6
  module Storage
@@ -22,20 +22,15 @@ module SuperSettings
22
22
  #
23
23
  # @example
24
24
  # SuperSettings::Storage::RedisStorage.redis = ConnectionPool.new(size: 5) { Redis.new(url: ENV["REDIS_URL"]) }
25
- class RedisStorage
26
- include Storage
25
+ class RedisStorage < StorageAttributes
26
+ include Transaction
27
27
 
28
28
  SETTINGS_KEY = "SuperSettings.settings"
29
29
  UPDATED_KEY = "SuperSettings.order_by_updated_at"
30
30
 
31
- class HistoryStorage
31
+ class HistoryStorage < HistoryAttributes
32
32
  HISTORY_KEY_PREFIX = "SuperSettings.history"
33
33
 
34
- include SuperSettings::Attributes
35
-
36
- attr_accessor :key, :value, :changed_by, :deleted
37
- attr_reader :created_at
38
-
39
34
  class << self
40
35
  def find_all_by_key(key:, offset: 0, limit: nil)
41
36
  end_index = (limit.nil? ? -1 : offset + limit - 1)
@@ -54,10 +49,8 @@ module SuperSettings
54
49
  record
55
50
  end
56
51
 
57
- def destroy_all_by_key(key)
58
- RedisStorage.transaction do |redis|
59
- redis.del("#{HISTORY_KEY_PREFIX}.#{key}")
60
- end
52
+ def destroy_all_by_key(key, redis)
53
+ redis.del("#{HISTORY_KEY_PREFIX}.#{key}")
61
54
  end
62
55
 
63
56
  def redis_key(key)
@@ -65,24 +58,20 @@ module SuperSettings
65
58
  end
66
59
  end
67
60
 
68
- def initialize(*)
69
- @deleted = false
70
- super
71
- end
72
-
73
- def created_at=(val)
74
- @created_at = SuperSettings::Coerce.time(val)
75
- end
76
-
77
61
  def save!
78
62
  raise ArgumentError.new("Missing key") if Coerce.blank?(key)
79
- RedisStorage.transaction do |redis|
80
- redis.lpush(self.class.redis_key(key), payload_json.to_json)
63
+
64
+ RedisStorage.transaction do |changes|
65
+ changes << self
81
66
  end
82
67
  end
83
68
 
84
- def deleted?
85
- !!@deleted
69
+ def save_to_redis(redis)
70
+ redis.lpush(self.class.redis_key(key), payload_json.to_json)
71
+ end
72
+
73
+ def created_at
74
+ SuperSettings::Storage::RedisStorage.time_at_microseconds(super)
86
75
  end
87
76
 
88
77
  private
@@ -91,16 +80,13 @@ module SuperSettings
91
80
  payload = {
92
81
  value: value,
93
82
  changed_by: changed_by,
94
- created_at: created_at.to_f
83
+ created_at: SuperSettings::Storage::RedisStorage.microseconds(created_at)
95
84
  }
96
85
  payload[:deleted] = true if deleted?
97
86
  payload
98
87
  end
99
88
  end
100
89
 
101
- attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
102
- attr_accessor :changed_by
103
-
104
90
  class << self
105
91
  def all
106
92
  with_redis do |redis|
@@ -109,15 +95,15 @@ module SuperSettings
109
95
  end
110
96
 
111
97
  def updated_since(time)
112
- time = SuperSettings::Coerce.time(time)
98
+ min_score = microseconds(time)
113
99
  with_redis do |redis|
114
- min_score = time.to_f
115
100
  keys = redis.zrangebyscore(UPDATED_KEY, min_score, "+inf")
116
101
  return [] if keys.empty?
117
102
 
118
103
  settings = []
119
104
  redis.hmget(SETTINGS_KEY, *keys).each do |json|
120
- settings << load_from_json(json) if json
105
+ setting = load_from_json(json) if json
106
+ settings << setting if setting && setting.updated_at > time
121
107
  end
122
108
  settings
123
109
  end
@@ -133,7 +119,7 @@ module SuperSettings
133
119
  def last_updated_at
134
120
  result = with_redis { |redis| redis.zrevrange(UPDATED_KEY, 0, 1, withscores: true).first }
135
121
  return nil unless result
136
- Time.at(result[1])
122
+ time_at_microseconds(result[1])
137
123
  end
138
124
 
139
125
  def destroy_all
@@ -151,26 +137,23 @@ module SuperSettings
151
137
  end
152
138
  end
153
139
 
154
- def transaction(&block)
155
- if Thread.current[:super_settings_transaction_redis]
156
- block.call(Thread.current[:super_settings_transaction_redis])
157
- else
158
- begin
159
- with_redis do |redis|
160
- redis.multi do |multi_redis|
161
- Thread.current[:super_settings_transaction_redis] = multi_redis
162
- Thread.current[:super_settings_transaction_after_commit] = []
163
- block.call(multi_redis)
164
- end
165
- after_commits = Thread.current[:super_settings_transaction_after_commit]
166
- Thread.current[:super_settings_transaction_after_commit] = nil
167
- after_commits.each(&:call)
140
+ def save_all(changes)
141
+ with_redis do |redis|
142
+ redis.multi do |multi_redis|
143
+ changes.each do |object|
144
+ object.save_to_redis(multi_redis)
168
145
  end
169
- ensure
170
- Thread.current[:super_settings_transaction_redis] = nil
171
- Thread.current[:super_settings_transaction_after_commit] = nil
172
146
  end
173
147
  end
148
+ true
149
+ end
150
+
151
+ def time_at_microseconds(time)
152
+ TimePrecision.new(time, :microsecond).time
153
+ end
154
+
155
+ def microseconds(time)
156
+ TimePrecision.new(time, :microsecond).to_f
174
157
  end
175
158
 
176
159
  protected
@@ -184,7 +167,7 @@ module SuperSettings
184
167
  def load_from_json(json)
185
168
  attributes = JSON.parse(json)
186
169
  setting = new(attributes)
187
- setting.send(:set_persisted!)
170
+ setting.persisted = true
188
171
  setting
189
172
  end
190
173
  end
@@ -205,83 +188,39 @@ module SuperSettings
205
188
  HistoryStorage.create!(key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at)
206
189
  end
207
190
 
208
- def save!
209
- self.updated_at ||= Time.now
210
- self.created_at ||= updated_at
211
- self.class.transaction do |redis|
212
- redis.hset(SETTINGS_KEY, key, payload_json)
213
- redis.zadd(UPDATED_KEY, updated_at.to_f, key)
214
- set_persisted!
215
- end
216
- true
191
+ def save_to_redis(redis)
192
+ redis.hset(SETTINGS_KEY, key, payload_json)
193
+ redis.zadd(UPDATED_KEY, self.class.microseconds(updated_at), key)
217
194
  end
218
195
 
219
196
  def destroy
220
- self.class.transaction do |redis|
221
- redis.hdel(SETTINGS_KEY, key)
222
- redis.zrem(UPDATED_KEY, key)
223
- HistoryStorage.destroy_all_by_key(key)
197
+ self.class.with_redis do |redis|
198
+ redis.multi do |multi_redis|
199
+ multi_redis.hdel(SETTINGS_KEY, key)
200
+ multi_redis.zrem(UPDATED_KEY, key)
201
+ HistoryStorage.destroy_all_by_key(key, multi_redis)
202
+ end
224
203
  end
225
204
  end
226
205
 
227
- def key=(value)
228
- @key = (Coerce.blank?(value) ? nil : value.to_s)
206
+ def created_at
207
+ self.class.time_at_microseconds(super)
229
208
  end
230
209
 
231
- def raw_value=(value)
232
- @raw_value = (Coerce.blank?(value) ? nil : value.to_s)
233
- end
234
-
235
- def value_type=(value)
236
- @value_type = (Coerce.blank?(value) ? nil : value.to_s)
237
- end
238
-
239
- def description=(value)
240
- @description = (Coerce.blank?(value) ? nil : value.to_s)
241
- end
242
-
243
- def deleted=(value)
244
- @deleted = Coerce.boolean(value)
245
- end
246
-
247
- def created_at=(value)
248
- @created_at = SuperSettings::Coerce.time(value)
249
- end
250
-
251
- def updated_at=(value)
252
- @updated_at = SuperSettings::Coerce.time(value)
253
- end
254
-
255
- def deleted?
256
- !!@deleted
257
- end
258
-
259
- def persisted?
260
- !!@persisted
210
+ def updated_at
211
+ self.class.time_at_microseconds(super)
261
212
  end
262
213
 
263
214
  private
264
215
 
265
- def after_commit(&block)
266
- if Thread.current[:super_settings_transaction_after_commit]
267
- Thread.current[:super_settings_transaction_after_commit] << block
268
- else
269
- block.call
270
- end
271
- end
272
-
273
- def set_persisted!
274
- @persisted = true
275
- end
276
-
277
216
  def payload_json
278
217
  payload = {
279
218
  key: key,
280
219
  raw_value: raw_value,
281
220
  value_type: value_type,
282
221
  description: description,
283
- created_at: created_at.to_f,
284
- updated_at: updated_at.to_f
222
+ created_at: self.class.microseconds(created_at),
223
+ updated_at: self.class.microseconds(updated_at)
285
224
  }
286
225
  payload[:deleted] = true if deleted?
287
226
  payload.to_json