super_settings 2.0.3 → 2.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: deffd163ccbba074051f5e7df4138216f24da7c8fa068c5a25fdaa7c83326799
4
- data.tar.gz: af00d68c45251327f0e73867c16bdb4171110929f02290db374178c88718f0ec
3
+ metadata.gz: c245924ff453f5cd0bde098003e6dc05d54b21aba4a42ddaf7c614c63233a542
4
+ data.tar.gz: fb8672624d5c688794f8492be263534ed4e46ac49684f76d9bbf3bd120b62579
5
5
  SHA512:
6
- metadata.gz: d496dfbfeaba9952c624f15680323d83fe055a03f48f17c660a5dee8e3512a4692bbe249212bfdf1830026ec0e726cb312acc0134a8c8260e95433b6c325abc8
7
- data.tar.gz: de6887cbbe6ec06ba1cb20e75769377008a5d7fa4eb1a55bcbb6be87e3cdff508890209a0984dc3cdf4ae293d895ce946eb807582097c65e052a9f0b0fcef081
6
+ metadata.gz: 605db9f00bbd1865203caaf7515c3d5f1ad83911653dd9410c69344dda0b21b8e58167806186969a6d2b27d4867a61d97b29333715c3f139741f16a3d3b0a635
7
+ data.tar.gz: b1565e3ace970a26a8d126ec27e3bee6786b5da85e33e25bbde5a947af12e8067a23d143870b7012dd449b86ab5658fbeb99fb365bc8f738276cf8672ffc318d
data/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 2.1.1
8
+
9
+ ### Fixed
10
+
11
+ - Added check to ensure that ActiveRecord has a connection to the database to avoid error when settings are checked before the database is connected or when the database doesn't yet exist.
12
+
13
+ ### Added
14
+
15
+ - Added `:null` storage engine that doesn't store settings at all. This is useful for testing or when the storage engine is no available in your continuous integration environment.
16
+
17
+ ## 2.1.0
18
+
19
+ ## Fixed
20
+
21
+ - More robust handling of history tracking when keys are deleted and then reused. Previously, the history was not fully recorded when a key was reused. Now the history on the old key is recorded as a delete and the history on the new key is recorded as being an update.
22
+
23
+ ## Changed
24
+
25
+ - Times are now consistently encoded in UTC in ISO-8601 format with microseconds whenever they are serialized to JSON.
26
+
7
27
  ## 2.0.3
8
28
 
9
29
  ### Fixed
data/README.md CHANGED
@@ -154,6 +154,8 @@ This gem abstracts out the storage engine and can support multiple storage mecha
154
154
  * `SuperSettings::Storage::RedisStorage` - Stores the settings in a Redis database using the [redis](https://github.com/redis/redis-rb) gem.
155
155
  * `SuperSettings::Storage::HttpStorage` - Uses the SuperSettings REST API running on another server. This is useful in a microservices architecture so you can have a central settings server used by all the services.
156
156
  * `SuperSettings::Storage::S3Storage` - Stores the settings in JSON in an S3 object. This is useful for applications running on AWS that want to store settings in a central location since it does not require a dedicated database. It is possible to read the settings directly from S3 from another application.
157
+ * `SuperSettings::Storage::MongoDBStorage` - Stores the settings in a MongoDB database using the [mongo](https://github.com/mongodb/mongo-ruby-driver) gem.
158
+ * `SuperSettings::Storage::NullStorage` - Does not store settings at all. This is useful for testing or when the storage engine is not available in your continuous integration environment.
157
159
 
158
160
  Additional storage engines can be built by creating a class that includes `SuperSettings::Storage` and implements the unimplemented methods in that module.
159
161
 
@@ -391,10 +393,12 @@ bundle exec rackup
391
393
 
392
394
  By default this will use Redis for storage using the default Redis URL. You can change the storage engine with the `STORAGE` environment variable.
393
395
 
396
+ - `active_record` - Use the ActiveRecord storage engine.
394
397
  - `redis` - Use the Redis storage engine. The Redis URL can be set with the `REDIS_URL` environment variable.
395
398
  - `http` - Use the HTTP storage engine. The URL for the REST API can be set with the `REST_API_URL` environment variable.
396
399
  - `s3` - Use the S3 storage engine. The S3 URL can be set with the `S3_URL` environment variable.
397
400
  - `mongodb` - Use the MongoDB storage engine. The MongoDB URL can be set with the `MONGODB_URL` environment variable.
401
+ - `null` - Use the null storage engine. This storage engine does not store settings at all.
398
402
 
399
403
  You can bring up all of these storage engines locally with the included docker-compose configuration.
400
404
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.3
1
+ 2.1.1
@@ -384,7 +384,7 @@
384
384
  payload.histories.forEach(function(history) {
385
385
  const date = new Date(Date.parse(history.created_at));
386
386
  const dateString = dateFormatter().format(date);
387
- const value = (payload.encrypted ? "<em>n/a</em>" : escapeHTML(history.value));
387
+ const value = (history.deleted ? '<em class="super-settings-text-danger">deleted</em>' : escapeHTML(history.value));
388
388
  rowsHTML += `<tr><td class="super-settings-text-nowrap">${escapeHTML(dateString)}</td><td>${escapeHTML(history.changed_by)}</td><td>${value}</td></tr>`;
389
389
  });
390
390
  tbody.insertAdjacentHTML("beforeend", rowsHTML);
@@ -36,6 +36,7 @@ module SuperSettings
36
36
  def time(value)
37
37
  value = nil if value.nil? || value.to_s.empty?
38
38
  return nil if value.nil?
39
+
39
40
  time = if value.is_a?(Numeric)
40
41
  Time.at(value)
41
42
  elsif value.respond_to?(:to_time)
@@ -52,6 +53,7 @@ module SuperSettings
52
53
  # @return [Boolean] true if the value is nil or empty.
53
54
  def blank?(value)
54
55
  return true if value.nil?
56
+
55
57
  if value.respond_to?(:empty?)
56
58
  value.empty?
57
59
  else
@@ -27,6 +27,10 @@ module SuperSettings
27
27
  @web_ui_enabled = true
28
28
  @color_scheme = false
29
29
  @changed_by_block = nil
30
+ @enhancement = nil
31
+ @application_name = nil
32
+ @application_logo = nil
33
+ @application_link = nil
30
34
  end
31
35
 
32
36
  def superclass
@@ -119,6 +123,7 @@ module SuperSettings
119
123
  @storage = :active_record
120
124
  @after_save_blocks = []
121
125
  @changed_by_display = nil
126
+ @cache = nil
122
127
  end
123
128
 
124
129
  # Specify the storage engine to use for persisting settings. The value can either be specified
@@ -34,5 +34,14 @@ module SuperSettings
34
34
  changed_by
35
35
  end
36
36
  end
37
+
38
+ def as_json
39
+ {
40
+ value: value,
41
+ changed_by: changed_by,
42
+ created_at: created_at&.utc&.iso8601(6),
43
+ deleted: deleted?
44
+ }
45
+ end
37
46
  end
38
47
  end
@@ -145,7 +145,7 @@ module SuperSettings
145
145
  end
146
146
 
147
147
  payload[:histories] = histories.collect do |history|
148
- history_values = {value: history.value, changed_by: history.changed_by_display, created_at: history.created_at}
148
+ history_values = {value: history.value, changed_by: history.changed_by_display, created_at: history.created_at.utc.iso8601(6)}
149
149
  history_values[:deleted] = true if history.deleted?
150
150
  history_values
151
151
  end
@@ -163,7 +163,7 @@ module SuperSettings
163
163
  # last_updated_at: iso8601 string
164
164
  # }
165
165
  def last_updated_at
166
- {last_updated_at: Setting.last_updated_at.utc.iso8601}
166
+ {last_updated_at: Setting.last_updated_at.utc.iso8601(6)}
167
167
  end
168
168
 
169
169
  # Return settings that have been updated since a specified timestamp.
@@ -446,18 +446,17 @@ module SuperSettings
446
446
  #
447
447
  # @return [void]
448
448
  def save!
449
- record_value_change
450
-
451
449
  unless valid?
452
450
  raise InvalidRecordError.new(errors.values.join("; "))
453
451
  end
454
452
 
455
453
  timestamp = Time.now
456
454
  self.created_at ||= timestamp
457
- self.updated_at = timestamp unless updated_at && changed?(:updated_at)
455
+ self.updated_at = timestamp if updated_at.nil? || !changed?(:updated_at)
458
456
 
459
457
  self.class.storage.with_connection do
460
458
  self.class.storage.transaction do
459
+ record_value_change
461
460
  @record.save!
462
461
  end
463
462
 
@@ -525,8 +524,8 @@ module SuperSettings
525
524
  value: value,
526
525
  value_type: value_type,
527
526
  description: description,
528
- created_at: created_at,
529
- updated_at: updated_at
527
+ created_at: created_at&.utc&.iso8601(6),
528
+ updated_at: updated_at&.utc&.iso8601(6)
530
529
  }
531
530
  attributes[:deleted] = true if deleted?
532
531
  attributes
@@ -592,8 +591,16 @@ module SuperSettings
592
591
  # Update the histories association whenever the value or key is changed.
593
592
  def record_value_change
594
593
  return unless changed?(:raw_value) || changed?(:deleted) || changed?(:key)
594
+
595
595
  recorded_value = (deleted? ? nil : raw_value)
596
- @record.create_history(value: recorded_value, deleted: deleted?, changed_by: changed_by, created_at: Time.now)
596
+ @record.class.create_history(key: key, value: recorded_value, deleted: deleted?, changed_by: changed_by, created_at: updated_at)
597
+
598
+ if changed?(:key)
599
+ key_was = @changes["key"][0]
600
+ if key_was
601
+ @record.class.create_history(key: key_was, changed_by: changed_by, created_at: updated_at, deleted: true)
602
+ end
603
+ end
597
604
  end
598
605
 
599
606
  def clear_changes
@@ -12,6 +12,13 @@ module SuperSettings
12
12
  self.table_name = "super_settings"
13
13
 
14
14
  has_many :history_items, class_name: "SuperSettings::Storage::ActiveRecordStorage::HistoryModel", foreign_key: :key, primary_key: :key
15
+
16
+ class << self
17
+ # ActiveRecord storage is only available if the connection pool is connected and the table exists.
18
+ def available?
19
+ connection_pool&.connected? && table_exists?
20
+ end
21
+ end
15
22
  end
16
23
 
17
24
  class HistoryModel < ApplicationRecord
@@ -18,7 +18,7 @@ module SuperSettings
18
18
 
19
19
  class << self
20
20
  def all
21
- if Model.table_exists?
21
+ if Model.available?
22
22
  Model.all.collect { |model| new(model) }
23
23
  else
24
24
  []
@@ -26,7 +26,7 @@ module SuperSettings
26
26
  end
27
27
 
28
28
  def active
29
- if Model.table_exists?
29
+ if Model.available?
30
30
  Model.where(deleted: false).collect { |model| new(model) }
31
31
  else
32
32
  []
@@ -34,7 +34,7 @@ module SuperSettings
34
34
  end
35
35
 
36
36
  def updated_since(time)
37
- if Model.table_exists?
37
+ if Model.available?
38
38
  Model.where("updated_at > ?", time).collect { |model| new(model) }
39
39
  else
40
40
  []
@@ -42,16 +42,20 @@ module SuperSettings
42
42
  end
43
43
 
44
44
  def find_by_key(key)
45
- model = Model.where(deleted: false).find_by(key: key) if Model.table_exists?
45
+ model = Model.where(deleted: false).find_by(key: key) if Model.available?
46
46
  new(model) if model
47
47
  end
48
48
 
49
49
  def last_updated_at
50
- if Model.table_exists?
50
+ if Model.available?
51
51
  Model.maximum(:updated_at)
52
52
  end
53
53
  end
54
54
 
55
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
56
+ HistoryModel.create!(key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at)
57
+ end
58
+
55
59
  def with_connection(&block)
56
60
  Model.connection_pool.with_connection(&block)
57
61
  end
@@ -117,15 +121,6 @@ module SuperSettings
117
121
  HistoryItem.new(key: key, value: record.value, changed_by: record.changed_by, created_at: record.created_at, deleted: record.deleted?)
118
122
  end
119
123
  end
120
-
121
- def create_history(changed_by:, created_at:, value: nil, deleted: false)
122
- history_attributes = {value: value, deleted: deleted, changed_by: changed_by, created_at: created_at}
123
- if @model.persisted?
124
- @model.history_items.create!(history_attributes)
125
- else
126
- @model.history_items.build(history_attributes)
127
- end
128
- end
129
124
  end
130
125
  end
131
126
  end
@@ -71,6 +71,10 @@ module SuperSettings
71
71
  SuperSettings::Coerce.time(value)
72
72
  end
73
73
 
74
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
75
+ # No-op since history is maintained by the source system.
76
+ end
77
+
74
78
  def save_all(changes)
75
79
  payload = []
76
80
  changes.each do |setting|
@@ -132,10 +136,6 @@ module SuperSettings
132
136
  end
133
137
  end
134
138
 
135
- def create_history(changed_by:, created_at:, value: nil, deleted: false)
136
- # No-op since history is maintained by the source system.
137
- end
138
-
139
139
  def reload
140
140
  self.class.find_by_key(key)
141
141
  self.attributes = self.class.find_by_key(key).attributes
@@ -19,9 +19,32 @@ module SuperSettings
19
19
  include Transaction
20
20
 
21
21
  class HistoryStorage < HistoryAttributes
22
+ class << self
23
+ def create!(attributes)
24
+ record = new(attributes)
25
+ record.save!
26
+ record
27
+ end
28
+ end
29
+
30
+ def initialize(*)
31
+ @storage = nil
32
+ super
33
+ end
34
+
35
+ attr_writer :storage
36
+
22
37
  def created_at=(val)
23
38
  super(TimePrecision.new(val).time)
24
39
  end
40
+
41
+ def save!
42
+ raise ArgumentError.new("Missing key") if Coerce.blank?(key)
43
+
44
+ @storage.transaction do |changes|
45
+ changes << self
46
+ end
47
+ end
25
48
  end
26
49
 
27
50
  class << self
@@ -37,36 +60,54 @@ module SuperSettings
37
60
  active.detect { |setting| setting.key == key }
38
61
  end
39
62
 
63
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
64
+ HistoryStorage.create!(key: key, value: value, changed_by: changed_by, created_at: created_at, deleted: deleted, storage: self)
65
+ end
66
+
40
67
  def save_all(changes)
41
68
  existing = {}
42
69
  parse_settings(settings_json_payload).each do |setting|
43
70
  existing[setting.key] = setting
44
71
  end
45
72
 
46
- changes.each do |setting|
47
- existing[setting.key] = setting
73
+ history_items = []
74
+ changes.each do |record|
75
+ if record.is_a?(HistoryStorage)
76
+ history_items << record
77
+ else
78
+ existing[record.key] = record
79
+ end
48
80
  end
49
81
 
50
82
  settings = existing.values.sort_by(&:key)
83
+
51
84
  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
- }
85
+ history_items.each do |history_item|
86
+ setting = existing[history_item.key]
87
+ next unless setting
88
+
89
+ history = changed_histories[history_item.key]
90
+ unless history
91
+ history = setting.history.dup
92
+ changed_histories[history_item.key] = history
61
93
  end
62
- setting.new_history.clear
94
+ history.unshift(history_item)
63
95
  end
64
96
 
65
97
  settings_json = JSON.dump(settings.collect(&:as_json))
66
98
  save_settings_json(settings_json)
67
99
 
68
100
  changed_histories.each do |setting_key, setting_history|
69
- history_json = JSON.dump(setting_history)
101
+ ordered_history = setting_history.sort_by { |history_item| history_item.created_at }.reverse
102
+ payload = ordered_history.collect do |history_item|
103
+ {
104
+ value: history_item.value,
105
+ changed_by: history_item.changed_by,
106
+ created_at: history_item.created_at.iso8601(6),
107
+ deleted: history_item.deleted?
108
+ }
109
+ end
110
+ history_json = JSON.dump(payload)
70
111
  save_history_json(setting_key, history_json)
71
112
  end
72
113
  end
@@ -128,11 +169,6 @@ module SuperSettings
128
169
  end
129
170
  end
130
171
 
131
- def initialize(*)
132
- @new_history = []
133
- super
134
- end
135
-
136
172
  def created_at=(val)
137
173
  super(TimePrecision.new(val).time)
138
174
  end
@@ -149,14 +185,6 @@ module SuperSettings
149
185
  end
150
186
  end
151
187
 
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
188
  def as_json
161
189
  {
162
190
  key: key,
@@ -26,6 +26,26 @@ module SuperSettings
26
26
  @mutex = Mutex.new
27
27
 
28
28
  class HistoryStorage < HistoryAttributes
29
+ class << self
30
+ def create!(attributes)
31
+ record = new(attributes)
32
+ record.save!
33
+ record
34
+ end
35
+ end
36
+
37
+ def created_at=(value)
38
+ super(TimePrecision.new(value, :millisecond).time)
39
+ end
40
+
41
+ def save!
42
+ raise ArgumentError.new("Missing key") if Coerce.blank?(key)
43
+
44
+ MongoDBStorage.transaction do |changes|
45
+ changes << self
46
+ end
47
+ end
48
+
29
49
  def as_bson
30
50
  attributes = {
31
51
  value: value,
@@ -89,13 +109,16 @@ module SuperSettings
89
109
  last_updated_setting["updated_at"] if last_updated_setting
90
110
  end
91
111
 
112
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
113
+ HistoryStorage.create!(key: key, value: value, changed_by: changed_by, created_at: created_at, deleted: deleted)
114
+ end
115
+
92
116
  def destroy_all
93
117
  settings_collection.delete_many({})
94
118
  end
95
119
 
96
120
  def save_all(changes)
97
121
  upserts = changes.collect { |setting| upsert(setting) }
98
- changes.each { |setting| setting.new_history.clear }
99
122
  settings_collection.bulk_write(upserts)
100
123
  true
101
124
  end
@@ -108,17 +131,18 @@ module SuperSettings
108
131
 
109
132
  private
110
133
 
111
- def upsert(setting)
112
- doc = setting.as_bson
113
- history = setting.new_history.collect(&:as_bson)
134
+ def upsert(record)
135
+ update = {"$setOnInsert": {key: record.key}}
136
+ if record.is_a?(MongoDBStorage::HistoryStorage)
137
+ update["$push"] = {history: record.as_bson}
138
+ else
139
+ update["$set"] = record.as_bson.except(:key)
140
+ end
141
+
114
142
  {
115
143
  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
- },
144
+ filter: {key: record.key},
145
+ update: update,
122
146
  upsert: true
123
147
  }
124
148
  }
@@ -146,13 +170,6 @@ module SuperSettings
146
170
  end
147
171
  end
148
172
 
149
- attr_reader :new_history
150
-
151
- def initialize(*)
152
- @new_history = []
153
- super
154
- end
155
-
156
173
  def history(limit: nil, offset: 0)
157
174
  pipeline = [
158
175
  {
@@ -195,13 +212,6 @@ module SuperSettings
195
212
  end
196
213
  end
197
214
 
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
215
  def created_at=(val)
206
216
  super(TimePrecision.new(val, :millisecond).time)
207
217
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SuperSettings
4
+ module Storage
5
+ # No-op implementation of the SuperSettings::Storage model. You can use this model if there
6
+ # is no other storage engine available. It can be useful for situations where the normal
7
+ # storage engine is not available, such as in a continious integration environment.
8
+ class NullStorage
9
+ # :nocov:
10
+
11
+ include Storage
12
+
13
+ attr_accessor :key, :raw_value, :description, :value_type, :updated_at, :created_at, :changed_by
14
+
15
+ class << self
16
+ attr_reader :settings
17
+
18
+ def history(key)
19
+ []
20
+ end
21
+
22
+ def destroy_all
23
+ end
24
+
25
+ def all
26
+ []
27
+ end
28
+
29
+ def updated_since(time)
30
+ []
31
+ end
32
+
33
+ def find_by_key(key)
34
+ nil
35
+ end
36
+
37
+ def last_updated_at
38
+ nil
39
+ end
40
+
41
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
42
+ nil
43
+ end
44
+
45
+ def load_asynchronous?
46
+ false
47
+ end
48
+ end
49
+
50
+ def history(limit: nil, offset: 0)
51
+ []
52
+ end
53
+
54
+ def save!
55
+ true
56
+ end
57
+
58
+ def deleted=(value)
59
+ end
60
+
61
+ def deleted?
62
+ false
63
+ end
64
+
65
+ def persisted?
66
+ false
67
+ end
68
+
69
+ # :nocov:
70
+ end
71
+ end
72
+ end
@@ -116,6 +116,10 @@ module SuperSettings
116
116
  record unless record.deleted?
117
117
  end
118
118
 
119
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
120
+ HistoryStorage.create!(key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at)
121
+ end
122
+
119
123
  def last_updated_at
120
124
  result = with_redis { |redis| redis.zrevrange(UPDATED_KEY, 0, 1, withscores: true).first }
121
125
  return nil unless result
@@ -184,10 +188,6 @@ module SuperSettings
184
188
  end
185
189
  end
186
190
 
187
- def create_history(changed_by:, created_at:, value: nil, deleted: false)
188
- HistoryStorage.create!(key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at)
189
- end
190
-
191
191
  def save_to_redis(redis)
192
192
  redis.hset(SETTINGS_KEY, key, payload_json)
193
193
  redis.zadd(UPDATED_KEY, self.class.microseconds(updated_at), key)
@@ -59,6 +59,20 @@ module SuperSettings
59
59
  settings.values.collect { |attributes| attributes[:updated_at] }.max
60
60
  end
61
61
 
62
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
63
+ history = @history[key]
64
+ unless history
65
+ history = []
66
+ @history[key] = history
67
+ end
68
+
69
+ created_at = SuperSettings::TimePrecision.new(created_at).time if created_at
70
+ item = {key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at}
71
+ history.unshift(item)
72
+
73
+ item
74
+ end
75
+
62
76
  protected
63
77
 
64
78
  def default_load_asynchronous?
@@ -85,11 +99,6 @@ module SuperSettings
85
99
  end
86
100
  end
87
101
 
88
- def create_history(changed_by:, created_at:, value: nil, deleted: false)
89
- item = {key: key, value: value, deleted: deleted, changed_by: changed_by, created_at: created_at}
90
- self.class.history(key).unshift(item)
91
- end
92
-
93
102
  def save!
94
103
  self.updated_at ||= Time.now
95
104
  self.created_at ||= updated_at
@@ -123,11 +132,15 @@ module SuperSettings
123
132
  end
124
133
 
125
134
  def created_at=(value)
126
- @created_at = SuperSettings::Coerce.time(value)
135
+ time = SuperSettings::Coerce.time(value)
136
+ time = SuperSettings::TimePrecision.new(time).time if time
137
+ @created_at = time
127
138
  end
128
139
 
129
140
  def updated_at=(value)
130
- @updated_at = SuperSettings::Coerce.time(value)
141
+ time = SuperSettings::Coerce.time(value)
142
+ time = SuperSettings::TimePrecision.new(time).time if time
143
+ @updated_at = time
131
144
  end
132
145
 
133
146
  def deleted?
@@ -53,8 +53,9 @@ module SuperSettings
53
53
  end
54
54
 
55
55
  def save!
56
- self.updated_at ||= Time.now
57
- self.created_at ||= updated_at
56
+ timestamp = Time.now
57
+ self.updated_at ||= timestamp if respond_to?(:updated_at)
58
+ self.created_at ||= (respond_to?(:updated_at) ? updated_at : timestamp)
58
59
 
59
60
  self.class.transaction do |changes|
60
61
  changes << self
@@ -13,6 +13,7 @@ module SuperSettings
13
13
  autoload :JSONStorage, File.join(__dir__, "storage/json_storage")
14
14
  autoload :S3Storage, File.join(__dir__, "storage/s3_storage")
15
15
  autoload :MongoDBStorage, File.join(__dir__, "storage/mongodb_storage")
16
+ autoload :NullStorage, File.join(__dir__, "storage/null_storage")
16
17
 
17
18
  def self.included(base)
18
19
  base.extend(ClassMethods)
@@ -67,6 +68,15 @@ module SuperSettings
67
68
  # :nocov:
68
69
  end
69
70
 
71
+ # Create a history item for the setting.
72
+ #
73
+ # @return [void]
74
+ def create_history(key:, changed_by:, created_at:, value: nil, deleted: false)
75
+ # :nocov:
76
+ raise NotImplementedError
77
+ # :nocov:
78
+ end
79
+
70
80
  # Implementing classes can override this method to setup a thread safe connection within a block.
71
81
  #
72
82
  # @return [void]
@@ -253,9 +263,7 @@ module SuperSettings
253
263
  #
254
264
  # @return [void]
255
265
  def create_history(changed_by:, created_at:, value: nil, deleted: false)
256
- # :nocov:
257
- raise NotImplementedError
258
- # :nocov:
266
+ self.class.create_history(key: key, changed_by: changed_by, created_at: created_at, value: value, deleted: deleted)
259
267
  end
260
268
 
261
269
  # Persist the record to storage.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: super_settings
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-22 00:00:00.000000000 Z
11
+ date: 2024-12-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -79,6 +79,7 @@ files:
79
79
  - lib/super_settings/storage/http_storage.rb
80
80
  - lib/super_settings/storage/json_storage.rb
81
81
  - lib/super_settings/storage/mongodb_storage.rb
82
+ - lib/super_settings/storage/null_storage.rb
82
83
  - lib/super_settings/storage/redis_storage.rb
83
84
  - lib/super_settings/storage/s3_storage.rb
84
85
  - lib/super_settings/storage/storage_attributes.rb