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,279 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "net/http"
|
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
|
+
module SuperSettings
|
10
|
+
module Storage
|
11
|
+
class HttpStorage
|
12
|
+
include Storage
|
13
|
+
|
14
|
+
DEFAULT_HEADERS = {"Accept" => "application/json"}.freeze
|
15
|
+
DEFAULT_TIMEOUT = 5.0
|
16
|
+
|
17
|
+
attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
|
18
|
+
|
19
|
+
class Error < StandardError
|
20
|
+
end
|
21
|
+
|
22
|
+
class NotFoundError < Error
|
23
|
+
end
|
24
|
+
|
25
|
+
class InvalidRecordError < Error
|
26
|
+
attr_reader :errors
|
27
|
+
|
28
|
+
def initialize(message, errors:)
|
29
|
+
super(message)
|
30
|
+
@errors = errors
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class HistoryStorage
|
35
|
+
include SuperSettings::Attributes
|
36
|
+
|
37
|
+
attr_accessor :key, :value, :changed_by, :deleted
|
38
|
+
|
39
|
+
def created_at=(val)
|
40
|
+
@created_at = SuperSettings::Coerce.time(val)
|
41
|
+
end
|
42
|
+
|
43
|
+
def deleted?
|
44
|
+
!!(defined?(@deleted) && @deleted)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def all
|
50
|
+
call_api(:get, "/settings")["settings"].collect do |attributes|
|
51
|
+
new(attributes)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def updated_since(time)
|
56
|
+
call_api(:get, "/settings/updated_since", time: time)["settings"].collect do |attributes|
|
57
|
+
new(attributes)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_by_key(key)
|
62
|
+
record = new(call_api(:get, "/setting", key: key))
|
63
|
+
record.send(:set_persisted!)
|
64
|
+
record
|
65
|
+
rescue NotFoundError
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def last_updated_at
|
70
|
+
value = call_api(:get, "/settings/last_updated_at")["last_updated_at"]
|
71
|
+
SuperSettings::Coerce.time(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
attr_accessor :base_url
|
75
|
+
|
76
|
+
attr_accessor :timeout
|
77
|
+
|
78
|
+
def headers
|
79
|
+
@headers ||= {}
|
80
|
+
end
|
81
|
+
|
82
|
+
def query_params
|
83
|
+
@query_params ||= {}
|
84
|
+
end
|
85
|
+
|
86
|
+
protected
|
87
|
+
|
88
|
+
def default_load_asynchronous?
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def call_api(method, path, params = {})
|
95
|
+
url_params = (method == :get ? query_params.merge(params) : query_params)
|
96
|
+
uri = api_uri(path, url_params)
|
97
|
+
|
98
|
+
body = nil
|
99
|
+
request_headers = DEFAULT_HEADERS.merge(headers)
|
100
|
+
if method == :post && !params&.empty?
|
101
|
+
body = params.to_json
|
102
|
+
request_headers["Content-Type"] = "application/json; charset=utf8-"
|
103
|
+
end
|
104
|
+
|
105
|
+
response = http_request(method: method, uri: uri, headers: request_headers, body: body)
|
106
|
+
|
107
|
+
begin
|
108
|
+
response.value # raises exception unless response is a success
|
109
|
+
JSON.parse(response.body)
|
110
|
+
rescue Net::ProtocolError
|
111
|
+
if [404, 410].include?(response.code.to_i)
|
112
|
+
raise NotFoundError.new("#{response.code} #{response.message}")
|
113
|
+
elsif response.code.to_i == 422
|
114
|
+
raise InvalidRecordError.new("#{response.code} #{response.message}", errors: JSON.parse(response.body)["errors"])
|
115
|
+
else
|
116
|
+
raise Error.new("#{response.code} #{response.message}")
|
117
|
+
end
|
118
|
+
rescue JSON::JSONError => e
|
119
|
+
raise Error.new(e.message)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def http_request(method:, uri:, headers: {}, body: nil, redirect_count: 0)
|
124
|
+
response = nil
|
125
|
+
http = Net::HTTP.new(uri.host, uri.port || uri.inferred_port)
|
126
|
+
begin
|
127
|
+
http.read_timeout = (timeout || DEFAULT_TIMEOUT)
|
128
|
+
http.open_timeout = (timeout || DEFAULT_TIMEOUT)
|
129
|
+
if uri.scheme == "https"
|
130
|
+
http.use_ssl = true
|
131
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
132
|
+
end
|
133
|
+
|
134
|
+
request = (method == :post ? Net::HTTP::Post.new(uri.request_uri) : Net::HTTP::Get.new(uri.request_uri))
|
135
|
+
set_headers(request, headers)
|
136
|
+
request.body = body if body
|
137
|
+
|
138
|
+
response = http.request(request)
|
139
|
+
ensure
|
140
|
+
begin
|
141
|
+
http.finish if http.started?
|
142
|
+
rescue IOError
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if response.is_a?(Net::HTTPRedirection)
|
147
|
+
location = resp["Location"]
|
148
|
+
if redirect_count < 5 && SuperSettings::Coerce.present?(location)
|
149
|
+
return http_request(method: :get, uri: URI(location), headers: headers, body: body, redirect_count: redirect_count + 1)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
response
|
154
|
+
end
|
155
|
+
|
156
|
+
def api_uri(path, params)
|
157
|
+
uri = URI("#{base_url.chomp("/")}#{path}")
|
158
|
+
if params && !params.empty?
|
159
|
+
q = []
|
160
|
+
q << uri.query unless uri.query.to_s.empty?
|
161
|
+
params.each do |name, value|
|
162
|
+
q << "#{URI.encode_www_form_component(name.to_s)}=#{URI.encode_www_form_component(value.to_s)}"
|
163
|
+
end
|
164
|
+
uri.query = q.join("&")
|
165
|
+
end
|
166
|
+
uri
|
167
|
+
end
|
168
|
+
|
169
|
+
def set_headers(request, headers)
|
170
|
+
headers.each do |name, value|
|
171
|
+
name = name.to_s
|
172
|
+
values = Array(value)
|
173
|
+
request[name] = values[0].to_s
|
174
|
+
values[1, values.length].each do |val|
|
175
|
+
request.add_field(name, val.to_s)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def save!
|
182
|
+
payload = {key: key}
|
183
|
+
if deleted?
|
184
|
+
payload[:deleted] = true
|
185
|
+
else
|
186
|
+
payload[:value] = value
|
187
|
+
payload[:value_type] = value_type
|
188
|
+
payload[:description] = description
|
189
|
+
end
|
190
|
+
|
191
|
+
begin
|
192
|
+
call_api(:post, "/settings", settings: [payload])
|
193
|
+
set_persisted!
|
194
|
+
rescue InvalidRecordError
|
195
|
+
return false
|
196
|
+
end
|
197
|
+
true
|
198
|
+
end
|
199
|
+
|
200
|
+
def history(limit: nil, offset: 0)
|
201
|
+
params = {key: key}
|
202
|
+
params[:offset] = offset if offset > 0
|
203
|
+
params[:limit] = limit if limit
|
204
|
+
history = call_api(:get, "/setting/history", params)
|
205
|
+
history["histories"].collect do |attributes|
|
206
|
+
HistoryItem.new(key: key, value: attributes["value"], changed_by: attributes["changed_by"], created_at: attributes["created_at"], deleted: attributes["deleted"])
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def create_history(changed_by:, created_at:, value: nil, deleted: false)
|
211
|
+
# No-op since history is maintained by the source system.
|
212
|
+
end
|
213
|
+
|
214
|
+
def reload
|
215
|
+
self.class.find_by_key(key)
|
216
|
+
self.attributes = self.class.find_by_key(key).attributes
|
217
|
+
self
|
218
|
+
end
|
219
|
+
|
220
|
+
def key=(value)
|
221
|
+
@key = (Coerce.blank?(value) ? nil : value.to_s)
|
222
|
+
end
|
223
|
+
|
224
|
+
def raw_value=(value)
|
225
|
+
@raw_value = (Coerce.blank?(value) ? nil : value.to_s)
|
226
|
+
end
|
227
|
+
alias_method :value=, :raw_value=
|
228
|
+
alias_method :value, :raw_value
|
229
|
+
|
230
|
+
def value_type=(value)
|
231
|
+
@value_type = (Coerce.blank?(value) ? nil : value.to_s)
|
232
|
+
end
|
233
|
+
|
234
|
+
def description=(value)
|
235
|
+
@description = (Coerce.blank?(value) ? nil : value.to_s)
|
236
|
+
end
|
237
|
+
|
238
|
+
def deleted=(value)
|
239
|
+
@deleted = Coerce.boolean(value)
|
240
|
+
end
|
241
|
+
|
242
|
+
def created_at=(value)
|
243
|
+
@created_at = SuperSettings::Coerce.time(value)
|
244
|
+
end
|
245
|
+
|
246
|
+
def updated_at=(value)
|
247
|
+
@updated_at = SuperSettings::Coerce.time(value)
|
248
|
+
end
|
249
|
+
|
250
|
+
def deleted?
|
251
|
+
!!(defined?(@deleted) && @deleted)
|
252
|
+
end
|
253
|
+
|
254
|
+
def persisted?
|
255
|
+
!!(defined?(@persisted) && @persisted)
|
256
|
+
end
|
257
|
+
|
258
|
+
protected
|
259
|
+
|
260
|
+
def redact_history!
|
261
|
+
# No-op since history is maintained by the source system.
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def set_persisted!
|
267
|
+
@persisted = true
|
268
|
+
end
|
269
|
+
|
270
|
+
def call_api(method, path, params = {})
|
271
|
+
self.class.send(:call_api, method, path, params)
|
272
|
+
end
|
273
|
+
|
274
|
+
def encrypted=(value)
|
275
|
+
# No op; needed for API compatibility
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
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
|
+
module SuperSettings
|
25
|
+
module Storage
|
26
|
+
class RedisStorage
|
27
|
+
include Storage
|
28
|
+
|
29
|
+
SETTINGS_KEY = "SuperSettings.settings"
|
30
|
+
UPDATED_KEY = "SuperSettings.order_by_updated_at"
|
31
|
+
|
32
|
+
class HistoryStorage
|
33
|
+
HISTORY_KEY_PREFIX = "SuperSettings.history"
|
34
|
+
|
35
|
+
include SuperSettings::Attributes
|
36
|
+
|
37
|
+
attr_accessor :key, :value, :changed_by, :deleted
|
38
|
+
attr_reader :created_at
|
39
|
+
|
40
|
+
class << self
|
41
|
+
def find_all_by_key(key:, offset: 0, limit: nil)
|
42
|
+
end_index = (limit.nil? ? -1 : offset + limit - 1)
|
43
|
+
return [] unless end_index >= -1
|
44
|
+
payloads = RedisStorage.with_redis { |redis| redis.lrange("#{HISTORY_KEY_PREFIX}.#{key}", offset, end_index) }
|
45
|
+
payloads.collect do |json|
|
46
|
+
record = new(JSON.parse(json))
|
47
|
+
record.key = key.to_s
|
48
|
+
record
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def create!(attributes)
|
53
|
+
record = new(attributes)
|
54
|
+
record.save!
|
55
|
+
record
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy_all_by_key(key)
|
59
|
+
RedisStorage.transaction do |redis|
|
60
|
+
redis.del("#{HISTORY_KEY_PREFIX}.#{key}")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def redis_key(key)
|
65
|
+
"#{HISTORY_KEY_PREFIX}.#{key}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def created_at=(val)
|
70
|
+
@created_at = SuperSettings::Coerce.time(val)
|
71
|
+
end
|
72
|
+
|
73
|
+
def save!
|
74
|
+
raise ArgumentError.new("Missing key") if Coerce.blank?(key)
|
75
|
+
RedisStorage.transaction do |redis|
|
76
|
+
redis.lpush(self.class.redis_key(key), payload_json.to_json)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def deleted?
|
81
|
+
!!(defined?(@deleted) && @deleted)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def payload_json
|
87
|
+
payload = {
|
88
|
+
value: value,
|
89
|
+
changed_by: changed_by,
|
90
|
+
created_at: created_at.to_f
|
91
|
+
}
|
92
|
+
payload[:deleted] = true if deleted?
|
93
|
+
payload
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
|
98
|
+
attr_accessor :changed_by
|
99
|
+
|
100
|
+
class << self
|
101
|
+
def all
|
102
|
+
with_redis do |redis|
|
103
|
+
redis.hgetall(SETTINGS_KEY).values.collect { |json| load_from_json(json) }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def updated_since(time)
|
108
|
+
time = SuperSettings::Coerce.time(time)
|
109
|
+
with_redis do |redis|
|
110
|
+
min_score = time.to_f
|
111
|
+
keys = redis.zrangebyscore(UPDATED_KEY, min_score, "+inf")
|
112
|
+
return [] if keys.empty?
|
113
|
+
|
114
|
+
settings = []
|
115
|
+
redis.hmget(SETTINGS_KEY, *keys).each do |json|
|
116
|
+
settings << load_from_json(json) if json
|
117
|
+
end
|
118
|
+
settings
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def find_by_key(key)
|
123
|
+
json = with_redis { |redis| redis.hget(SETTINGS_KEY, key) }
|
124
|
+
return nil unless json
|
125
|
+
load_from_json(json)
|
126
|
+
end
|
127
|
+
|
128
|
+
def last_updated_at
|
129
|
+
result = with_redis { |redis| redis.zrevrange(UPDATED_KEY, 0, 1, withscores: true).first }
|
130
|
+
return nil unless result
|
131
|
+
Time.at(result[1])
|
132
|
+
end
|
133
|
+
|
134
|
+
def destroy_all
|
135
|
+
all.each(&:destroy)
|
136
|
+
end
|
137
|
+
|
138
|
+
attr_writer :redis
|
139
|
+
|
140
|
+
def with_redis(&block)
|
141
|
+
connection = (@redis.is_a?(Proc) ? @redis.call : @redis)
|
142
|
+
if defined?(ConnectionPool) && connection.is_a?(ConnectionPool)
|
143
|
+
connection.with(&block)
|
144
|
+
else
|
145
|
+
block.call(connection)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def transaction(&block)
|
150
|
+
if Thread.current[:super_settings_transaction_redis]
|
151
|
+
block.call(Thread.current[:super_settings_transaction_redis])
|
152
|
+
else
|
153
|
+
begin
|
154
|
+
with_redis do |redis|
|
155
|
+
redis.multi do |multi_redis|
|
156
|
+
Thread.current[:super_settings_transaction_redis] = multi_redis
|
157
|
+
Thread.current[:super_settings_transaction_after_commit] = []
|
158
|
+
block.call(multi_redis)
|
159
|
+
end
|
160
|
+
after_commits = Thread.current[:super_settings_transaction_after_commit]
|
161
|
+
Thread.current[:super_settings_transaction_after_commit] = nil
|
162
|
+
after_commits.each(&:call)
|
163
|
+
end
|
164
|
+
ensure
|
165
|
+
Thread.current[:super_settings_transaction_redis] = nil
|
166
|
+
Thread.current[:super_settings_transaction_after_commit] = nil
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
protected
|
172
|
+
|
173
|
+
def default_load_asynchronous?
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def load_from_json(json)
|
180
|
+
attributes = JSON.parse(json)
|
181
|
+
setting = new(attributes)
|
182
|
+
setting.send(:set_persisted!)
|
183
|
+
setting
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def history(limit: nil, offset: 0)
|
188
|
+
HistoryStorage.find_all_by_key(key: key, limit: limit, offset: offset).collect do |record|
|
189
|
+
HistoryItem.new(key: key, value: record.value, changed_by: record.changed_by, created_at: record.created_at, deleted: record.deleted?)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def create_history(changed_by:, created_at:, value: nil, deleted: false)
|
194
|
+
HistoryStorage.create!(key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at)
|
195
|
+
end
|
196
|
+
|
197
|
+
def save!
|
198
|
+
self.updated_at ||= Time.now
|
199
|
+
self.created_at ||= updated_at
|
200
|
+
self.class.transaction do |redis|
|
201
|
+
redis.hset(SETTINGS_KEY, key, payload_json)
|
202
|
+
redis.zadd(UPDATED_KEY, updated_at.to_f, key)
|
203
|
+
set_persisted!
|
204
|
+
end
|
205
|
+
true
|
206
|
+
end
|
207
|
+
|
208
|
+
def destroy
|
209
|
+
self.class.transaction do |redis|
|
210
|
+
redis.hdel(SETTINGS_KEY, key)
|
211
|
+
redis.zrem(UPDATED_KEY, key)
|
212
|
+
HistoryStorage.destroy_all_by_key(key)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def key=(value)
|
217
|
+
@key = (Coerce.blank?(value) ? nil : value.to_s)
|
218
|
+
end
|
219
|
+
|
220
|
+
def raw_value=(value)
|
221
|
+
@raw_value = (Coerce.blank?(value) ? nil : value.to_s)
|
222
|
+
end
|
223
|
+
|
224
|
+
def value_type=(value)
|
225
|
+
@value_type = (Coerce.blank?(value) ? nil : value.to_s)
|
226
|
+
end
|
227
|
+
|
228
|
+
def description=(value)
|
229
|
+
@description = (Coerce.blank?(value) ? nil : value.to_s)
|
230
|
+
end
|
231
|
+
|
232
|
+
def deleted=(value)
|
233
|
+
@deleted = Coerce.boolean(value)
|
234
|
+
end
|
235
|
+
|
236
|
+
def created_at=(value)
|
237
|
+
@created_at = SuperSettings::Coerce.time(value)
|
238
|
+
end
|
239
|
+
|
240
|
+
def updated_at=(value)
|
241
|
+
@updated_at = SuperSettings::Coerce.time(value)
|
242
|
+
end
|
243
|
+
|
244
|
+
def deleted?
|
245
|
+
!!(defined?(@deleted) && @deleted)
|
246
|
+
end
|
247
|
+
|
248
|
+
def persisted?
|
249
|
+
!!(defined?(@persisted) && @persisted)
|
250
|
+
end
|
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
|
+
private
|
266
|
+
|
267
|
+
def after_commit(&block)
|
268
|
+
if Thread.current[:super_settings_transaction_after_commit]
|
269
|
+
Thread.current[:super_settings_transaction_after_commit] << block
|
270
|
+
else
|
271
|
+
block.call
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def set_persisted!
|
276
|
+
@persisted = true
|
277
|
+
end
|
278
|
+
|
279
|
+
def payload_json
|
280
|
+
payload = {
|
281
|
+
key: key,
|
282
|
+
raw_value: raw_value,
|
283
|
+
value_type: value_type,
|
284
|
+
description: description,
|
285
|
+
created_at: created_at.to_f,
|
286
|
+
updated_at: updated_at.to_f
|
287
|
+
}
|
288
|
+
payload[:deleted] = true if deleted?
|
289
|
+
payload.to_json
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|