super_settings 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +110 -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/controller_actions.rb +2 -2
  29. data/lib/super_settings/engine.rb +1 -1
  30. data/lib/super_settings/history_item.rb +1 -1
  31. data/lib/super_settings/http_client.rb +165 -0
  32. data/lib/super_settings/rack_application.rb +3 -3
  33. data/lib/super_settings/rest_api.rb +5 -4
  34. data/lib/super_settings/setting.rb +13 -2
  35. data/lib/super_settings/storage/active_record_storage.rb +7 -0
  36. data/lib/super_settings/storage/history_attributes.rb +31 -0
  37. data/lib/super_settings/storage/http_storage.rb +60 -184
  38. data/lib/super_settings/storage/json_storage.rb +201 -0
  39. data/lib/super_settings/storage/mongodb_storage.rb +238 -0
  40. data/lib/super_settings/storage/redis_storage.rb +49 -111
  41. data/lib/super_settings/storage/s3_storage.rb +165 -0
  42. data/lib/super_settings/storage/storage_attributes.rb +64 -0
  43. data/lib/super_settings/storage/test_storage.rb +3 -5
  44. data/lib/super_settings/storage/transaction.rb +67 -0
  45. data/lib/super_settings/storage.rb +13 -6
  46. data/lib/super_settings/time_precision.rb +36 -0
  47. data/lib/super_settings.rb +11 -0
  48. data/super_settings.gemspec +4 -2
  49. metadata +22 -9
  50. data/lib/super_settings/application/images/edit.svg +0 -1
  51. data/lib/super_settings/application/images/info.svg +0 -1
  52. data/lib/super_settings/application/images/slash.svg +0 -1
  53. data/lib/super_settings/application/images/trash.svg +0 -1
@@ -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,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
3
  require "redis"
5
4
 
6
5
  module SuperSettings
@@ -23,20 +22,15 @@ module SuperSettings
23
22
  #
24
23
  # @example
25
24
  # SuperSettings::Storage::RedisStorage.redis = ConnectionPool.new(size: 5) { Redis.new(url: ENV["REDIS_URL"]) }
26
- class RedisStorage
27
- include Storage
25
+ class RedisStorage < StorageAttributes
26
+ include Transaction
28
27
 
29
28
  SETTINGS_KEY = "SuperSettings.settings"
30
29
  UPDATED_KEY = "SuperSettings.order_by_updated_at"
31
30
 
32
- class HistoryStorage
31
+ class HistoryStorage < HistoryAttributes
33
32
  HISTORY_KEY_PREFIX = "SuperSettings.history"
34
33
 
35
- include SuperSettings::Attributes
36
-
37
- attr_accessor :key, :value, :changed_by, :deleted
38
- attr_reader :created_at
39
-
40
34
  class << self
41
35
  def find_all_by_key(key:, offset: 0, limit: nil)
42
36
  end_index = (limit.nil? ? -1 : offset + limit - 1)
@@ -55,10 +49,8 @@ module SuperSettings
55
49
  record
56
50
  end
57
51
 
58
- def destroy_all_by_key(key)
59
- RedisStorage.transaction do |redis|
60
- redis.del("#{HISTORY_KEY_PREFIX}.#{key}")
61
- end
52
+ def destroy_all_by_key(key, redis)
53
+ redis.del("#{HISTORY_KEY_PREFIX}.#{key}")
62
54
  end
63
55
 
64
56
  def redis_key(key)
@@ -66,24 +58,20 @@ module SuperSettings
66
58
  end
67
59
  end
68
60
 
69
- def initialize(*)
70
- @deleted = false
71
- super
72
- end
73
-
74
- def created_at=(val)
75
- @created_at = SuperSettings::Coerce.time(val)
76
- end
77
-
78
61
  def save!
79
62
  raise ArgumentError.new("Missing key") if Coerce.blank?(key)
80
- RedisStorage.transaction do |redis|
81
- redis.lpush(self.class.redis_key(key), payload_json.to_json)
63
+
64
+ RedisStorage.transaction do |changes|
65
+ changes << self
82
66
  end
83
67
  end
84
68
 
85
- def deleted?
86
- !!@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)
87
75
  end
88
76
 
89
77
  private
@@ -92,16 +80,13 @@ module SuperSettings
92
80
  payload = {
93
81
  value: value,
94
82
  changed_by: changed_by,
95
- created_at: created_at.to_f
83
+ created_at: SuperSettings::Storage::RedisStorage.microseconds(created_at)
96
84
  }
97
85
  payload[:deleted] = true if deleted?
98
86
  payload
99
87
  end
100
88
  end
101
89
 
102
- attr_reader :key, :raw_value, :description, :value_type, :updated_at, :created_at
103
- attr_accessor :changed_by
104
-
105
90
  class << self
106
91
  def all
107
92
  with_redis do |redis|
@@ -110,15 +95,15 @@ module SuperSettings
110
95
  end
111
96
 
112
97
  def updated_since(time)
113
- time = SuperSettings::Coerce.time(time)
98
+ min_score = microseconds(time)
114
99
  with_redis do |redis|
115
- min_score = time.to_f
116
100
  keys = redis.zrangebyscore(UPDATED_KEY, min_score, "+inf")
117
101
  return [] if keys.empty?
118
102
 
119
103
  settings = []
120
104
  redis.hmget(SETTINGS_KEY, *keys).each do |json|
121
- settings << load_from_json(json) if json
105
+ setting = load_from_json(json) if json
106
+ settings << setting if setting && setting.updated_at > time
122
107
  end
123
108
  settings
124
109
  end
@@ -134,7 +119,7 @@ module SuperSettings
134
119
  def last_updated_at
135
120
  result = with_redis { |redis| redis.zrevrange(UPDATED_KEY, 0, 1, withscores: true).first }
136
121
  return nil unless result
137
- Time.at(result[1])
122
+ time_at_microseconds(result[1])
138
123
  end
139
124
 
140
125
  def destroy_all
@@ -152,26 +137,23 @@ module SuperSettings
152
137
  end
153
138
  end
154
139
 
155
- def transaction(&block)
156
- if Thread.current[:super_settings_transaction_redis]
157
- block.call(Thread.current[:super_settings_transaction_redis])
158
- else
159
- begin
160
- with_redis do |redis|
161
- redis.multi do |multi_redis|
162
- Thread.current[:super_settings_transaction_redis] = multi_redis
163
- Thread.current[:super_settings_transaction_after_commit] = []
164
- block.call(multi_redis)
165
- end
166
- after_commits = Thread.current[:super_settings_transaction_after_commit]
167
- Thread.current[:super_settings_transaction_after_commit] = nil
168
- 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)
169
145
  end
170
- ensure
171
- Thread.current[:super_settings_transaction_redis] = nil
172
- Thread.current[:super_settings_transaction_after_commit] = nil
173
146
  end
174
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
175
157
  end
176
158
 
177
159
  protected
@@ -185,7 +167,7 @@ module SuperSettings
185
167
  def load_from_json(json)
186
168
  attributes = JSON.parse(json)
187
169
  setting = new(attributes)
188
- setting.send(:set_persisted!)
170
+ setting.persisted = true
189
171
  setting
190
172
  end
191
173
  end
@@ -206,83 +188,39 @@ module SuperSettings
206
188
  HistoryStorage.create!(key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at)
207
189
  end
208
190
 
209
- def save!
210
- self.updated_at ||= Time.now
211
- self.created_at ||= updated_at
212
- self.class.transaction do |redis|
213
- redis.hset(SETTINGS_KEY, key, payload_json)
214
- redis.zadd(UPDATED_KEY, updated_at.to_f, key)
215
- set_persisted!
216
- end
217
- 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)
218
194
  end
219
195
 
220
196
  def destroy
221
- self.class.transaction do |redis|
222
- redis.hdel(SETTINGS_KEY, key)
223
- redis.zrem(UPDATED_KEY, key)
224
- 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
225
203
  end
226
204
  end
227
205
 
228
- def key=(value)
229
- @key = (Coerce.blank?(value) ? nil : value.to_s)
206
+ def created_at
207
+ self.class.time_at_microseconds(super)
230
208
  end
231
209
 
232
- def raw_value=(value)
233
- @raw_value = (Coerce.blank?(value) ? nil : value.to_s)
234
- end
235
-
236
- def value_type=(value)
237
- @value_type = (Coerce.blank?(value) ? nil : value.to_s)
238
- end
239
-
240
- def description=(value)
241
- @description = (Coerce.blank?(value) ? nil : value.to_s)
242
- end
243
-
244
- def deleted=(value)
245
- @deleted = Coerce.boolean(value)
246
- end
247
-
248
- def created_at=(value)
249
- @created_at = SuperSettings::Coerce.time(value)
250
- end
251
-
252
- def updated_at=(value)
253
- @updated_at = SuperSettings::Coerce.time(value)
254
- end
255
-
256
- def deleted?
257
- !!@deleted
258
- end
259
-
260
- def persisted?
261
- !!@persisted
210
+ def updated_at
211
+ self.class.time_at_microseconds(super)
262
212
  end
263
213
 
264
214
  private
265
215
 
266
- def after_commit(&block)
267
- if Thread.current[:super_settings_transaction_after_commit]
268
- Thread.current[:super_settings_transaction_after_commit] << block
269
- else
270
- block.call
271
- end
272
- end
273
-
274
- def set_persisted!
275
- @persisted = true
276
- end
277
-
278
216
  def payload_json
279
217
  payload = {
280
218
  key: key,
281
219
  raw_value: raw_value,
282
220
  value_type: value_type,
283
221
  description: description,
284
- created_at: created_at.to_f,
285
- updated_at: updated_at.to_f
222
+ created_at: self.class.microseconds(created_at),
223
+ updated_at: self.class.microseconds(updated_at)
286
224
  }
287
225
  payload[:deleted] = true if deleted?
288
226
  payload.to_json
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-s3"
4
+
5
+ module SuperSettings
6
+ module Storage
7
+ # Storage backend for storing the settings in an S3 object. This should work with any S3-compatible
8
+ # storage service.
9
+ class S3Storage < JSONStorage
10
+ SETTINGS_FILE = "settings.json"
11
+ HISTORY_FILE_SUFFIX = ".history.json"
12
+ DEFAULT_PATH = "super_settings"
13
+
14
+ # Configuration for the S3 storage backend.
15
+ #
16
+ # * access_key_id - The AWS access key ID. Defaults to the SUPER_SETTINGS_AWS_ACCESS_KEY_ID
17
+ # or AWS_ACCESS_KEY_ID environment variable or whatever is set in the `Aws.config` object.
18
+ # * secret_access_key - The AWS secret access key. Defaults to the SUPER_SETTINGS_AWS_SECRET_ACCESS_KEY
19
+ # or AWS_SECRET_ACCESS_KEY environment variable or whatever is set in the `Aws.config` object.
20
+ # * region - The AWS region. Defaults to the SUPER_SETTINGS_AWS_REGION or AWS_REGION environment variable.
21
+ # This is required for AWS S3 but may be optional for S3-compatible services.
22
+ # * endpoint - The S3 endpoint URL. This is optional and should only be used for S3-compatible services.
23
+ # Defaults to the SUPER_SETTINGS_AWS_ENDPOINT or AWS_ENDPOINT environment variable.
24
+ # * bucket - The S3 bucket name. Defaults to the SUPER_SETTINGS_S3_BUCKET or AWS_S3_BUCKET
25
+ # environment variable.
26
+ # * object - The S3 object key. Defaults to "super_settings.json.gz" or the value set in the
27
+ # SUPER_SETTINGS_S3_OBJECT environment variable.
28
+ #
29
+ # You can also specify the configuration using a URL in the format using the SUPER_SETTINGS_S3_URL
30
+ # environment variable. The URL should be in the format:
31
+ #
32
+ # ```
33
+ # s3://access_key_id:secret_access_key@region/bucket/object
34
+ # ```
35
+ class Configuration
36
+ attr_accessor :access_key_id, :secret_access_key, :region, :endpoint, :bucket
37
+ attr_reader :path
38
+
39
+ def initialize
40
+ @access_key_id ||= ENV.fetch("SUPER_SETTINGS_AWS_ACCESS_KEY_ID", ENV["AWS_ACCESS_KEY_ID"])
41
+ @secret_access_key ||= ENV.fetch("SUPER_SETTINGS_AWS_SECRET_ACCESS_KEY", ENV["AWS_SECRET_ACCESS_KEY"])
42
+ @region ||= ENV.fetch("SUPER_SETTINGS_AWS_REGION", ENV["AWS_REGION"])
43
+ @endpoint ||= ENV.fetch("SUPER_SETTINGS_AWS_ENDPOINT", ENV["AWS_ENDPOINT"])
44
+ @bucket ||= ENV.fetch("SUPER_SETTINGS_S3_BUCKET", ENV["AWS_S3_BUCKET"])
45
+ @path ||= ENV.fetch("SUPER_SETTINGS_S3_OBJECT", DEFAULT_PATH)
46
+ self.url = ENV["SUPER_SETTINGS_S3_URL"] unless ENV["SUPER_SETTINGS_S3_URL"].to_s.empty?
47
+ end
48
+
49
+ def url=(url)
50
+ return if url.to_s.empty?
51
+
52
+ uri = URI.parse(url)
53
+ raise ArgumentError, "Invalid S3 URL" unless uri.scheme == "s3"
54
+
55
+ self.access_key_id = uri.user if uri.user
56
+ self.secret_access_key = uri.password if uri.password
57
+ self.region = uri.host if uri.host
58
+ _, bucket, path = uri.path.split("/", 3) if uri.path
59
+ self.bucket = bucket if bucket
60
+ self.path = path if path
61
+ end
62
+
63
+ def path=(value)
64
+ @path = "#{value}.chomp('/')/"
65
+ end
66
+
67
+ def hash
68
+ [self.class, access_key_id, secret_access_key, region, endpoint, bucket, path].hash
69
+ end
70
+ end
71
+
72
+ @bucket = nil
73
+ @bucket_hash = nil
74
+
75
+ class << self
76
+ def last_updated_at
77
+ all.collect(&:updated_at).compact.max
78
+ end
79
+
80
+ def configuration
81
+ @config ||= Configuration.new
82
+ end
83
+
84
+ def destroy_all
85
+ s3_bucket.objects(prefix: configuration.path).each do |object|
86
+ if object.key == file_path(SETTINGS_FILE) || object.key.end_with?(HISTORY_FILE_SUFFIX)
87
+ object.delete
88
+ end
89
+ end
90
+ end
91
+
92
+ protected
93
+
94
+ def default_load_asynchronous?
95
+ true
96
+ end
97
+
98
+ def settings_json_payload
99
+ object = settings_object
100
+ return nil unless object.exists?
101
+
102
+ object.get.body.read
103
+ end
104
+
105
+ def save_settings_json(json)
106
+ object = settings_object
107
+ object.put(body: json)
108
+ end
109
+
110
+ def save_history_json(key, json)
111
+ object = history_object(key)
112
+ object.put(body: json)
113
+ end
114
+
115
+ private
116
+
117
+ def s3_bucket
118
+ if configuration.hash != @bucket_hash
119
+ @bucket_hash = configuration.hash
120
+ options = {
121
+ endpoint: configuration.endpoint,
122
+ access_key_id: configuration.access_key_id,
123
+ secret_access_key: configuration.secret_access_key,
124
+ region: configuration.region
125
+ }
126
+ options[:force_path_style] = true if configuration.endpoint
127
+ options.compact!
128
+
129
+ @bucket = Aws::S3::Resource.new(options).bucket(configuration.bucket)
130
+ end
131
+ @bucket
132
+ end
133
+
134
+ def s3_object(filename)
135
+ s3_bucket.object(file_path(filename))
136
+ end
137
+
138
+ def file_path(filename)
139
+ "#{configuration.path}#{filename}"
140
+ end
141
+
142
+ def settings_object
143
+ s3_object(SETTINGS_FILE)
144
+ end
145
+
146
+ def history_object(key)
147
+ s3_object("#{key}#{HISTORY_FILE_SUFFIX}")
148
+ end
149
+
150
+ def history_json(key)
151
+ object = history_object(key)
152
+ return nil unless object.exists?
153
+
154
+ object.get.body.read
155
+ end
156
+ end
157
+
158
+ protected
159
+
160
+ def fetch_history_json
161
+ self.class.send(:history_json, key)
162
+ end
163
+ end
164
+ end
165
+ end