activeitem 0.0.1 → 0.0.3

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: a08a2b03026d0380bc6232ad035c49ca911bb5ee96c8e7d55de3dd23ebc3dcba
4
- data.tar.gz: 1778c6afe64631e5f40ef13b69a761109475befffdea301cb4144a5ea0abc7bb
3
+ metadata.gz: 8c3c7d26fc3228203838e82ee2819b5bfe3be9269429e5e4fa0f9fc0f895dca1
4
+ data.tar.gz: 3972d267e94e926212ecfd8dac239daa4b1043cf6a1f567da8c45fcc8ff38403
5
5
  SHA512:
6
- metadata.gz: c03b044f31d8a843fd37f066a6f5ae25a406165db25541a717e547bee044f20f09f45399dc4beaa133fb696611d86ee0938d09d9e67d3cdb50616e9493005c8b
7
- data.tar.gz: 024f823d6e9d76a4e7f9b9a77ac1e9212d213a549e9fc735051e7fdc2c5ac507f28c894aa9d2d209b589a772f5db534063c3e33d7f8db64cc8eefeea02b8955d
6
+ metadata.gz: 02ab351109c3b188a103114e8b051e8f073771aa75769ee315f05f12d4fd0162ec4584113b3e2982f59fdbc6b71bb96a7a8d58ec2e01ae256bb1fc24ee7b014e
7
+ data.tar.gz: d6649e5310e72e084504eca05b61253dde0eb8722149031acd1dfa1d2ba63a9191d70c10cc7f375de06cadfafd991c5b5e1204b6b17dc379159f8bf35bf1c86d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.0.3
4
+
5
+ ### Changed
6
+
7
+ - Replace custom `validates_length_of`, `validates_numericality_of`, `validates_format_of` with ActiveModel built-ins
8
+ - Replace hand-rolled dirty tracking (`@pending_changes`/`@previously_changed`) with `ActiveModel::Dirty`
9
+ - Replace manual `ActiveSupport::Callbacks` DSL with `ActiveModel::Callbacks` + `define_model_callbacks`
10
+
11
+ ### Improved
12
+
13
+ - Add `limit` to `UniquenessValidator` queries (limit 2 when excluding self, `.first` for new records)
14
+ - `execute_count_query` now respects `limit_value` and short-circuits pagination
15
+ - `check_dependent_associations` uses `limit(1).any?` for restrict checks
16
+
17
+ ## 0.0.2
18
+
19
+ ### Security
20
+
21
+ - **[Critical]** Pagination cursor validation — decoded JSON is now validated to only contain flat key/value pairs with alphanumeric keys and string/numeric values. Prevents partition traversal via crafted cursors.
22
+ - **[Critical]** Remove arbitrary file require from `model_loader.rb` — `safe_constantize_model` now uses `safe_constantize` with class name format validation instead of requiring files from disk.
23
+ - **[Medium]** Add jitter to exponential backoff in batch operations to prevent thundering herd.
24
+ - **[Low]** Replace `Object.const_get` with `safe_constantize` in `composed_of` to prevent constant hierarchy traversal.
25
+
26
+ ### Fixed
27
+
28
+ - Fix `set_created_timestamp` callback not setting `@created_at`, causing DynamoDB `Invalid attribute value type` errors on create
29
+ - Fix duplicate `id=` method definition (Lint/DuplicateMethods) by using `attr_reader :id` with a custom setter
30
+ - Fix duplicate `last` method definition in QueryHelpers
31
+ - Fix duplicate branch in Relation `includes` case statement (Lint/DuplicateBranch)
32
+ - Use `Comparable#clamp` in Pagination and Relation (Style/ComparableClamp)
33
+
34
+ ### Added
35
+
36
+ - Documentation comments for all public modules and classes (Style/Documentation)
37
+ - `--workdir` option to CI DynamoDB service for `act` compatibility
38
+
3
39
  ## 0.0.1
4
40
 
5
41
  - Initial release
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 Andy Davis
3
+ Copyright (c) 2026 Stowzilla Inc.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -5,6 +5,9 @@ require 'active_support/inflector'
5
5
  require_relative 'model_loader'
6
6
 
7
7
  module ActiveItem
8
+ # Provides has_many and belongs_to association macros with lazy loading,
9
+ # preloading support, and dependent record handling (destroy, nullify,
10
+ # restrict).
8
11
  module Associations
9
12
  extend ActiveSupport::Concern
10
13
  include ModelLoader
@@ -17,25 +20,22 @@ module ActiveItem
17
20
  self.class._associations.each do |name, config|
18
21
  next unless config[:type] == :has_many && config[:dependent]
19
22
 
20
- associated_records = send(name)
21
- has_records = associated_records.respond_to?(:any?) ? associated_records.any? : false
22
-
23
- next unless has_records
24
-
25
23
  case config[:dependent]
26
24
  when :restrict_with_exception
27
- raise DeleteRestrictionError.new(name)
25
+ raise DeleteRestrictionError, name if send(name).limit(1).any?
28
26
  when :restrict_with_error
29
- error_message = config[:message] || "Cannot delete #{self.class.name} because dependent #{name} exist"
30
- errors.add(:base, error_message)
31
- throw(:abort)
27
+ if send(name).limit(1).any?
28
+ error_message = config[:message] || "Cannot delete #{self.class.name} because dependent #{name} exist"
29
+ errors.add(:base, error_message)
30
+ throw(:abort)
31
+ end
32
32
  when :destroy
33
- associated_records.each(&:destroy)
33
+ send(name).each(&:destroy)
34
34
  when :delete_all
35
- associated_records.each(&:delete)
35
+ send(name).each(&:delete)
36
36
  when :nullify
37
37
  foreign_key = config[:foreign_key]
38
- associated_records.each { |record| record.update(foreign_key => nil) }
38
+ send(name).each { |record| record.update(foreign_key => nil) }
39
39
  end
40
40
  end
41
41
  end
@@ -87,9 +87,7 @@ module ActiveItem
87
87
  )
88
88
 
89
89
  foreign_key_sym = foreign_key.to_sym
90
- unless method_defined?(foreign_key_sym) || private_method_defined?(foreign_key_sym)
91
- attr_accessor foreign_key_sym
92
- end
90
+ attr_accessor foreign_key_sym unless method_defined?(foreign_key_sym) || private_method_defined?(foreign_key_sym)
93
91
 
94
92
  validates foreign_key_sym, presence: true unless optional
95
93
 
@@ -98,10 +96,10 @@ module ActiveItem
98
96
  define_method("#{association_name}=") { |record| set_belongs_to_association(association_name, record) }
99
97
 
100
98
  default_foreign_key = "#{association_name}_id"
101
- if foreign_key.to_s != default_foreign_key
102
- define_method(default_foreign_key) { send(foreign_key) }
103
- define_method("#{default_foreign_key}=") { |value| send("#{foreign_key}=", value) }
104
- end
99
+ return unless foreign_key.to_s != default_foreign_key
100
+
101
+ define_method(default_foreign_key) { send(foreign_key) }
102
+ define_method("#{default_foreign_key}=") { |value| send("#{foreign_key}=", value) }
105
103
  end
106
104
  end
107
105
 
@@ -111,19 +109,17 @@ module ActiveItem
111
109
  config = self.class._associations[name]
112
110
  return Relation.new(Object, conditions: { _empty: true }) unless config
113
111
 
114
- if _preloaded_associations.key?(name)
115
- return Relation.new(nil, preloaded_records: _preloaded_associations[name], class_name: config[:class_name])
116
- end
112
+ return Relation.new(nil, preloaded_records: _preloaded_associations[name], class_name: config[:class_name]) if _preloaded_associations.key?(name)
117
113
 
118
114
  local_key_value = send(config[:primary_key])
119
115
  return Relation.new(nil, conditions: { _empty: true }, class_name: config[:class_name]) if local_key_value.nil?
120
116
 
121
117
  conditions = { config[:foreign_key].to_sym => local_key_value }
122
118
  relation = Relation.new(nil, conditions: conditions, index_name: config[:index],
123
- class_name: config[:class_name], owner: self)
119
+ class_name: config[:class_name], owner: self)
124
120
 
125
121
  if config[:scope]
126
- if config[:scope].arity == 0
122
+ if config[:scope].arity.zero?
127
123
  relation.instance_exec(&config[:scope]) || relation
128
124
  else
129
125
  config[:scope].call(relation)
@@ -149,6 +145,7 @@ module ActiveItem
149
145
  record = klass.find(foreign_key_value)
150
146
  rescue ActiveItem::RecordNotFound
151
147
  raise unless config[:optional]
148
+
152
149
  record = nil
153
150
  end
154
151
  instance_variable_set(cache_var, record)
@@ -4,18 +4,22 @@ require 'aws-sdk-dynamodb'
4
4
  require 'active_support/core_ext/string/inflections'
5
5
  require 'active_support/core_ext/hash/indifferent_access'
6
6
  require 'active_support/core_ext/array/extract_options'
7
- require 'active_support/callbacks'
8
- require 'active_support/concern'
9
7
  require 'active_model'
10
8
  require 'securerandom'
11
9
 
12
10
  module ActiveItem
11
+ # Base class for all ActiveItem models. Provides persistence, callbacks,
12
+ # validations, dirty tracking, and an ActiveRecord-like interface for
13
+ # DynamoDB tables.
13
14
  class Base
14
15
  include ActiveModel::Validations
15
- include ActiveSupport::Callbacks
16
+ include ActiveModel::Dirty
17
+ extend ActiveModel::Callbacks
16
18
  include Associations
17
19
  include Logging
18
20
 
21
+ define_model_callbacks :save, :create, :update, :destroy, :initialize, :validation
22
+
19
23
  def self.const_missing(name)
20
24
  ActiveItem.const_defined?(name) ? ActiveItem.const_get(name) : super
21
25
  end
@@ -26,32 +30,30 @@ module ActiveItem
26
30
  extend QueryHelpers
27
31
  extend Validations
28
32
 
29
- define_callbacks :save, :create, :update, :destroy, :validation
30
- define_model_callbacks :initialize, only: :after
31
-
32
- attr_accessor :id, :created_at, :updated_at, :dbrecord
33
+ attr_reader :id
34
+ attr_accessor :created_at, :updated_at, :dbrecord
33
35
 
34
36
  def id=(value)
35
37
  @id = (value.to_s.strip.empty? ? nil : value)
36
38
  end
37
39
 
38
- set_callback :create, :before, :generate_primary_key
39
- set_callback :create, :before, :set_created_timestamp
40
- set_callback :destroy, :before, :check_dependent_associations
40
+ before_create :generate_primary_key
41
+ before_create :assign_created_timestamp
42
+ before_destroy :check_dependent_associations
41
43
 
42
44
  def initialize(attributes = {})
43
- @previously_changed = {}
44
- @pending_changes = {}
45
45
  @_preloaded_counts = {}
46
46
  @_preloaded_associations = {}
47
47
  @new_record = true
48
48
 
49
- if attributes.is_a?(Hash)
50
- attributes.each do |key, value|
51
- setter = "#{key}="
52
- send(setter, value) if respond_to?(setter)
53
- end
49
+ return unless attributes.is_a?(Hash)
50
+
51
+ attributes.each do |key, value|
52
+ setter = "#{key}="
53
+ send(setter, value) if respond_to?(setter)
54
54
  end
55
+
56
+ clear_changes_information
55
57
  end
56
58
 
57
59
  def _preloaded_counts
@@ -63,9 +65,7 @@ module ActiveItem
63
65
  end
64
66
 
65
67
  def self.attribute_names
66
- @attribute_names ||= begin
67
- instance_methods.grep(/\A[a-z_][a-z0-9_]*=\z/).map { |m| m.to_s.chomp('=') }.sort
68
- end
68
+ @attribute_names ||= instance_methods.grep(/\A[a-z_][a-z0-9_]*=\z/).map { |m| m.to_s.chomp('=') }.sort
69
69
  end
70
70
 
71
71
  def populate_attributes_from_item(item)
@@ -75,11 +75,11 @@ module ActiveItem
75
75
  value = nil
76
76
  found = false
77
77
  self.class.dynamo_key_variants(attr_name).each do |key|
78
- if item.key?(key)
79
- value = item[key]
80
- found = true
81
- break
82
- end
78
+ next unless item.key?(key)
79
+
80
+ value = item[key]
81
+ found = true
82
+ break
83
83
  end
84
84
 
85
85
  instance_variable_set("@#{attr_name}", value) if found
@@ -97,19 +97,18 @@ module ActiveItem
97
97
  attrs.each do |attr|
98
98
  attr_name = attr.to_s
99
99
 
100
+ define_attribute_methods attr_name
101
+
100
102
  define_method(attr_name) do
101
103
  instance_variable_get("@#{attr_name}")
102
104
  end
103
105
 
104
106
  define_method("#{attr_name}=") do |value|
105
107
  old_value = instance_variable_get("@#{attr_name}")
106
- instance_variable_set("@#{attr_name}", value)
107
-
108
- if old_value != value && instance_variable_defined?(:@pending_changes)
109
- @pending_changes ||= {}
110
- @pending_changes[attr_name] ||= [old_value, nil]
111
- @pending_changes[attr_name][1] = value
108
+ if old_value != value
109
+ send("#{attr_name}_will_change!") unless changed_attributes.key?(attr_name)
112
110
  end
111
+ instance_variable_set("@#{attr_name}", value)
113
112
  end
114
113
  end
115
114
  end
@@ -120,12 +119,12 @@ module ActiveItem
120
119
 
121
120
  def primary_key=(value)
122
121
  remove_method primary_key.to_sym
123
- remove_method "#{primary_key}=".to_sym
122
+ remove_method :"#{primary_key}="
124
123
 
125
124
  @primary_key = value.to_s
126
125
 
127
126
  alias_method primary_key.to_sym, :id
128
- alias_method "#{primary_key}=".to_sym, :id=
127
+ alias_method :"#{primary_key}=", :id=
129
128
  end
130
129
 
131
130
  def table_name
@@ -140,9 +139,7 @@ module ActiveItem
140
139
  @dynamodb ||= Aws::DynamoDB::Client.new(http_wire_trace: false)
141
140
  end
142
141
 
143
- def dynamodb=(client)
144
- @dynamodb = client
145
- end
142
+ attr_writer :dynamodb
146
143
 
147
144
  def dynamo_attribute_map(mappings = nil)
148
145
  if mappings
@@ -155,6 +152,7 @@ module ActiveItem
155
152
  def to_dynamo_key(attr_name)
156
153
  attr_str = attr_name.to_s
157
154
  return dynamo_attribute_map[attr_str] if dynamo_attribute_map.key?(attr_str)
155
+
158
156
  attr_str.camelize(:lower)
159
157
  end
160
158
 
@@ -162,6 +160,7 @@ module ActiveItem
162
160
  key_str = dynamo_key.to_s
163
161
  reverse_map = dynamo_attribute_map.invert
164
162
  return reverse_map[key_str] if reverse_map.key?(key_str)
163
+
165
164
  key_str.underscore
166
165
  end
167
166
 
@@ -176,12 +175,12 @@ module ActiveItem
176
175
  normalized_item = normalize_dynamodb_values(item)
177
176
 
178
177
  record = allocate
179
- record.instance_variable_set(:@id, normalized_item[self.primary_key])
178
+ record.instance_variable_set(:@id, normalized_item[primary_key])
180
179
  record.send(:populate_attributes_from_item, normalized_item)
181
180
  record.instance_variable_set(:@new_record, false)
182
- record.instance_variable_set(:@previously_changed, {})
183
- record.instance_variable_set(:@pending_changes, {})
181
+ record.instance_variable_set(:@mutations_from_database, nil)
184
182
  record.instance_variable_set(:@dbrecord, normalized_item)
183
+ record.send(:clear_changes_information)
185
184
  record
186
185
  end
187
186
 
@@ -208,44 +207,36 @@ module ActiveItem
208
207
  record
209
208
  end
210
209
 
211
- # Callback DSL
212
- def before_save(*args, &block)
210
+ # Callback DSL — :on option routes before_save/after_save to create/update
211
+ def before_save(*args, &)
213
212
  options = args.extract_options!
214
213
  if options[:on]
215
214
  case options[:on].to_sym
216
- when :create then set_callback(:create, :before, *args, &block)
217
- when :update then set_callback(:update, :before, *args, &block)
215
+ when :create then set_callback(:create, :before, *args, &)
216
+ when :update then set_callback(:update, :before, *args, &)
218
217
  else raise ArgumentError, "Invalid on: option '#{options[:on]}'. Must be :create or :update"
219
218
  end
220
219
  else
221
- set_callback(:save, :before, *args, &block)
220
+ set_callback(:save, :before, *args, &)
222
221
  end
223
222
  end
224
223
 
225
- def after_save(*args, &block)
224
+ def after_save(*args, &)
226
225
  options = args.extract_options!
227
226
  if options[:on]
228
227
  case options[:on].to_sym
229
- when :create then set_callback(:create, :after, *args, &block)
230
- when :update then set_callback(:update, :after, *args, &block)
228
+ when :create then set_callback(:create, :after, *args, &)
229
+ when :update then set_callback(:update, :after, *args, &)
231
230
  else raise ArgumentError, "Invalid on: option '#{options[:on]}'. Must be :create or :update"
232
231
  end
233
232
  else
234
- set_callback(:save, :after, *args, &block)
233
+ set_callback(:save, :after, *args, &)
235
234
  end
236
235
  end
237
236
 
238
- def before_create(*args, &block) = set_callback(:create, :before, *args, &block)
239
- def after_create(*args, &block) = set_callback(:create, :after, *args, &block)
240
- def before_update(*args, &block) = set_callback(:update, :before, *args, &block)
241
- def after_update(*args, &block) = set_callback(:update, :after, *args, &block)
242
- def before_validation(*args, &block) = set_callback(:validation, :before, *args, &block)
243
- def after_validation(*args, &block) = set_callback(:validation, :after, *args, &block)
244
- def before_destroy(*args, &block) = set_callback(:destroy, :before, *args, &block)
245
- def after_destroy(*args, &block) = set_callback(:destroy, :after, *args, &block)
246
-
247
237
  def scope(name, body)
248
- raise ArgumentError, "scope body must be callable (Proc/Lambda)" unless body.respond_to?(:call)
238
+ raise ArgumentError, 'scope body must be callable (Proc/Lambda)' unless body.respond_to?(:call)
239
+
249
240
  _scopes[name.to_sym] = body
250
241
  define_singleton_method(name) { all.instance_exec(&body) }
251
242
  end
@@ -257,7 +248,8 @@ module ActiveItem
257
248
  private
258
249
 
259
250
  def default_table_name
260
- raise "Cannot generate table name for anonymous class" unless name
251
+ raise 'Cannot generate table name for anonymous class' unless name
252
+
261
253
  ActiveItem.configuration.table_name_for(name)
262
254
  end
263
255
 
@@ -265,7 +257,7 @@ module ActiveItem
265
257
  super
266
258
  subclass.class_eval do
267
259
  alias_method primary_key.to_sym, :id
268
- alias_method "#{primary_key}=".to_sym, :id=
260
+ alias_method :"#{primary_key}=", :id=
269
261
  end
270
262
  end
271
263
  end
@@ -279,12 +271,14 @@ module ActiveItem
279
271
  end
280
272
 
281
273
  def reload
282
- raise "Cannot reload a new record" if new_record?
274
+ raise 'Cannot reload a new record' if new_record?
275
+
283
276
  fresh_record = self.class.find(id)
284
277
  raise "Record not found: #{self.class.name} with id #{id}" unless fresh_record
285
278
 
286
279
  self.class.attribute_names.each do |attr_name|
287
280
  next if attr_name == 'dbrecord'
281
+
288
282
  value = fresh_record.instance_variable_get("@#{attr_name}")
289
283
  instance_variable_set("@#{attr_name}", value)
290
284
  end
@@ -292,13 +286,12 @@ module ActiveItem
292
286
  @created_at = fresh_record.created_at
293
287
  @updated_at = fresh_record.updated_at
294
288
  @dbrecord = fresh_record.dbrecord
295
- @pending_changes = {}
296
- @previously_changed = {}
289
+ clear_changes_information
297
290
  self
298
291
  end
299
292
 
300
293
  def has_changes_to_save?
301
- changes.any?
294
+ changed?
302
295
  end
303
296
 
304
297
  def to_h
@@ -308,12 +301,17 @@ module ActiveItem
308
301
  def attributes
309
302
  attrs = {}
310
303
  pk_name = self.class.primary_key
311
- pk_value = send(pk_name) rescue instance_variable_get("@#{pk_name}")
304
+ pk_value = begin
305
+ send(pk_name)
306
+ rescue StandardError
307
+ instance_variable_get("@#{pk_name}")
308
+ end
312
309
  attrs['id'] = pk_value
313
310
  attrs[pk_name] = pk_value
314
311
 
315
312
  self.class.attribute_names.each do |attr_name|
316
313
  next if attr_name == 'dbrecord'
314
+
317
315
  value = instance_variable_get("@#{attr_name}")
318
316
  attrs[attr_name] = value unless value.nil?
319
317
  end
@@ -324,11 +322,17 @@ module ActiveItem
324
322
  end
325
323
 
326
324
  def inspect
327
- pk_value = send(self.class.primary_key) rescue id
325
+ begin
326
+ send(self.class.primary_key)
327
+ rescue StandardError
328
+ id
329
+ end
328
330
  attr_strs = self.class.attribute_names.filter_map do |attr|
329
331
  next if attr == 'dbrecord'
332
+
330
333
  value = instance_variable_get("@#{attr}")
331
334
  next if value.nil?
335
+
332
336
  "#{attr}: #{value.inspect}"
333
337
  end
334
338
  "#<#{self.class.name} #{attr_strs.join(', ')}>"
@@ -356,9 +360,11 @@ module ActiveItem
356
360
  end
357
361
 
358
362
  return false if result == false
363
+
359
364
  changes_applied
365
+ @new_record = false
360
366
  true
361
- rescue => e
367
+ rescue StandardError => e
362
368
  dynamo_logger.error("Failed to save #{self.class.name}: #{e.message}")
363
369
  raise e
364
370
  end
@@ -406,10 +412,11 @@ module ActiveItem
406
412
  def destroy
407
413
  result = run_callbacks(:destroy) { perform_destroy }
408
414
  return false if result == false
415
+
409
416
  true
410
417
  rescue DeleteRestrictionError
411
418
  false
412
- rescue => e
419
+ rescue StandardError => e
413
420
  dynamo_logger.error("Failed to destroy #{self.class.name}: #{e.message}")
414
421
  errors.add(:base, e.message)
415
422
  false
@@ -418,7 +425,7 @@ module ActiveItem
418
425
  def delete
419
426
  perform_destroy
420
427
  true
421
- rescue => e
428
+ rescue StandardError => e
422
429
  dynamo_logger.error("Failed to delete #{self.class.name}: #{e.message}")
423
430
  false
424
431
  end
@@ -426,38 +433,20 @@ module ActiveItem
426
433
  def assign_attributes(attributes)
427
434
  attributes.each do |key, value|
428
435
  setter = "#{key}="
429
- if respond_to?(setter)
430
- old_value = send(key)
431
- @pending_changes[key.to_s] = [old_value, value] if old_value != value
432
- send(setter, value)
433
- end
436
+ send(setter, value) if respond_to?(setter)
434
437
  end
435
438
  end
436
439
 
437
440
  def attribute_changed?(attr_name)
438
- @pending_changes.key?(attr_name.to_s)
441
+ super(attr_name.to_s)
439
442
  end
440
443
 
441
444
  def attribute_was(attr_name)
442
- @pending_changes.dig(attr_name.to_s, 0)
443
- end
444
-
445
- def changes
446
- @pending_changes
447
- end
448
-
449
- def previous_changes
450
- @previously_changed
451
- end
452
-
453
- def changes_applied
454
- @previously_changed = @pending_changes.dup
455
- @pending_changes = {}
456
- @new_record = false
445
+ attribute_in_database(attr_name.to_s)
457
446
  end
458
447
 
459
448
  def valid?(context = nil)
460
- return super(context) if defined?(@running_validations) && @running_validations
449
+ return super if defined?(@running_validations) && @running_validations
461
450
 
462
451
  @running_validations = true
463
452
  begin
@@ -477,8 +466,8 @@ module ActiveItem
477
466
  instance_variable_set("@#{pk}", @id) if pk != 'id'
478
467
  end
479
468
 
480
- def set_created_timestamp
481
- @created_at ||= Time.now.utc.iso8601
469
+ def assign_created_timestamp
470
+ @created_at ||= Time.now.utc.iso8601 # rubocop:disable Naming/MemoizedInstanceVariableName
482
471
  end
483
472
 
484
473
  def dynamodb
@@ -508,11 +497,11 @@ module ActiveItem
508
497
 
509
498
  dynamo_logger.info("#{self.class.name} created (#{self.class.primary_key}: #{id})")
510
499
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
511
- errors.add(:id, "already exists")
500
+ errors.add(:id, 'already exists')
512
501
  false
513
502
  rescue Aws::DynamoDB::Errors::AccessDeniedException => e
514
503
  raise ActiveItem::AccessDeniedError.new(model_name: self.class.name, table: table_name,
515
- operation: 'PutItem', original_error: e)
504
+ operation: 'PutItem', original_error: e)
516
505
  end
517
506
 
518
507
  def build_dynamodb_item
@@ -521,6 +510,7 @@ module ActiveItem
521
510
  dynamodb_attributes.each do |attr|
522
511
  value = instance_variable_get("@#{attr}")
523
512
  next if value.nil?
513
+
524
514
  dynamo_key = self.class.to_dynamo_key(attr)
525
515
  item[dynamo_key] = value
526
516
  end
@@ -559,7 +549,7 @@ module ActiveItem
559
549
  end
560
550
  end
561
551
 
562
- update_parts << "updatedAt = :updatedAt"
552
+ update_parts << 'updatedAt = :updatedAt'
563
553
  attr_values[':updatedAt'] = Time.now.utc.iso8601
564
554
 
565
555
  update_expression = "SET #{update_parts.join(', ')}"
@@ -576,7 +566,7 @@ module ActiveItem
576
566
  dynamodb.update_item(params)
577
567
  rescue Aws::DynamoDB::Errors::AccessDeniedException => e
578
568
  raise ActiveItem::AccessDeniedError.new(model_name: self.class.name, table: table_name,
579
- operation: 'UpdateItem', original_error: e)
569
+ operation: 'UpdateItem', original_error: e)
580
570
  end
581
571
 
582
572
  def perform_destroy
@@ -585,7 +575,7 @@ module ActiveItem
585
575
  dynamo_logger.info("#{self.class.name} deleted (#{key}: #{send(key)})")
586
576
  rescue Aws::DynamoDB::Errors::AccessDeniedException => e
587
577
  raise ActiveItem::AccessDeniedError.new(model_name: self.class.name, table: table_name,
588
- operation: 'DeleteItem', original_error: e)
578
+ operation: 'DeleteItem', original_error: e)
589
579
  end
590
580
  end
591
581
  end