Class: ActiveItem::Base
Constant Summary
QueryHelpers::RECENT_INDEX
Instance Attribute Summary collapse
Class Method Summary
collapse
-
._scopes ⇒ Object
-
.after_create(*args, &block) ⇒ Object
-
.after_destroy(*args, &block) ⇒ Object
-
.after_save(*args, &block) ⇒ Object
-
.after_update(*args, &block) ⇒ Object
-
.after_validation(*args, &block) ⇒ Object
-
.attr_accessor(*attrs) ⇒ Object
-
.attribute_names ⇒ Object
-
.before_create(*args, &block) ⇒ Object
-
.before_destroy(*args, &block) ⇒ Object
-
.before_save(*args, &block) ⇒ Object
-
.before_update(*args, &block) ⇒ Object
-
.before_validation(*args, &block) ⇒ Object
-
.const_missing(name) ⇒ Object
-
.create(attributes = {}) ⇒ Object
-
.create!(attributes = {}) ⇒ Object
-
.dynamo_attribute_map(mappings = nil) ⇒ Object
-
.dynamo_key_variants(attr_name) ⇒ Object
-
.dynamodb ⇒ Object
-
.dynamodb=(client) ⇒ Object
-
.find_or_create_by(attributes, &block) ⇒ Object
-
.from_dynamo_key(dynamo_key) ⇒ Object
-
.instantiate(item) ⇒ Object
-
.normalize_dynamodb_values(obj) ⇒ Object
-
.primary_key ⇒ Object
-
.primary_key=(value) ⇒ Object
-
.scope(name, body) ⇒ Object
-
.table_name ⇒ Object
-
.table_name=(value) ⇒ Object
-
.to_dynamo_key(attr_name) ⇒ Object
-
.transaction {|txn| ... } ⇒ Object
-
.transaction_find(items) ⇒ Object
Instance Method Summary
collapse
exists?, get, put, query, scan
all, all_records, batch_find, batch_write, count, delete_all, exists?, find, find_by, first, includes, indexes, last, none, recent, where
validates_format_of, validates_length_of, validates_numericality_of, validates_uniqueness_of
#check_dependent_associations
#safe_constantize_model
Methods included from ComposedOf
#populate_composed_attributes_from_item
Constructor Details
#initialize(attributes = {}) ⇒ Base
Returns a new instance of Base.
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
# File 'lib/active_item/base.rb', line 42
def initialize(attributes = {})
@previously_changed = {}
@pending_changes = {}
@_preloaded_counts = {}
@_preloaded_associations = {}
@new_record = true
if attributes.is_a?(Hash)
attributes.each do |key, value|
setter = "#{key}="
send(setter, value) if respond_to?(setter)
end
end
end
|
Instance Attribute Details
#created_at ⇒ Object
Returns the value of attribute created_at.
32
33
34
|
# File 'lib/active_item/base.rb', line 32
def created_at
@created_at
end
|
#dbrecord ⇒ Object
Returns the value of attribute dbrecord.
32
33
34
|
# File 'lib/active_item/base.rb', line 32
def dbrecord
@dbrecord
end
|
#id ⇒ Object
Returns the value of attribute id.
32
33
34
|
# File 'lib/active_item/base.rb', line 32
def id
@id
end
|
#updated_at ⇒ Object
Returns the value of attribute updated_at.
32
33
34
|
# File 'lib/active_item/base.rb', line 32
def updated_at
@updated_at
end
|
Class Method Details
._scopes ⇒ Object
253
254
255
|
# File 'lib/active_item/base.rb', line 253
def _scopes
@_scopes ||= {}
end
|
.after_create(*args, &block) ⇒ Object
239
|
# File 'lib/active_item/base.rb', line 239
def after_create(*args, &block) = set_callback(:create, :after, *args, &block)
|
.after_destroy(*args, &block) ⇒ Object
245
|
# File 'lib/active_item/base.rb', line 245
def after_destroy(*args, &block) = set_callback(:destroy, :after, *args, &block)
|
.after_save(*args, &block) ⇒ Object
225
226
227
228
229
230
231
232
233
234
235
236
|
# File 'lib/active_item/base.rb', line 225
def after_save(*args, &block)
options = args.
if options[:on]
case options[:on].to_sym
when :create then set_callback(:create, :after, *args, &block)
when :update then set_callback(:update, :after, *args, &block)
else raise ArgumentError, "Invalid on: option '#{options[:on]}'. Must be :create or :update"
end
else
set_callback(:save, :after, *args, &block)
end
end
|
.after_update(*args, &block) ⇒ Object
241
|
# File 'lib/active_item/base.rb', line 241
def after_update(*args, &block) = set_callback(:update, :after, *args, &block)
|
.after_validation(*args, &block) ⇒ Object
243
|
# File 'lib/active_item/base.rb', line 243
def after_validation(*args, &block) = set_callback(:validation, :after, *args, &block)
|
.attr_accessor(*attrs) ⇒ Object
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
# File 'lib/active_item/base.rb', line 96
def attr_accessor(*attrs)
attrs.each do |attr|
attr_name = attr.to_s
define_method(attr_name) do
instance_variable_get("@#{attr_name}")
end
define_method("#{attr_name}=") do |value|
old_value = instance_variable_get("@#{attr_name}")
instance_variable_set("@#{attr_name}", value)
if old_value != value && instance_variable_defined?(:@pending_changes)
@pending_changes ||= {}
@pending_changes[attr_name] ||= [old_value, nil]
@pending_changes[attr_name][1] = value
end
end
end
end
|
.attribute_names ⇒ Object
65
66
67
68
69
|
# File 'lib/active_item/base.rb', line 65
def self.attribute_names
@attribute_names ||= begin
instance_methods.grep(/\A[a-z_][a-z0-9_]*=\z/).map { |m| m.to_s.chomp('=') }.sort
end
end
|
.before_create(*args, &block) ⇒ Object
238
|
# File 'lib/active_item/base.rb', line 238
def before_create(*args, &block) = set_callback(:create, :before, *args, &block)
|
.before_destroy(*args, &block) ⇒ Object
244
|
# File 'lib/active_item/base.rb', line 244
def before_destroy(*args, &block) = set_callback(:destroy, :before, *args, &block)
|
.before_save(*args, &block) ⇒ Object
212
213
214
215
216
217
218
219
220
221
222
223
|
# File 'lib/active_item/base.rb', line 212
def before_save(*args, &block)
options = args.
if options[:on]
case options[:on].to_sym
when :create then set_callback(:create, :before, *args, &block)
when :update then set_callback(:update, :before, *args, &block)
else raise ArgumentError, "Invalid on: option '#{options[:on]}'. Must be :create or :update"
end
else
set_callback(:save, :before, *args, &block)
end
end
|
.before_update(*args, &block) ⇒ Object
240
|
# File 'lib/active_item/base.rb', line 240
def before_update(*args, &block) = set_callback(:update, :before, *args, &block)
|
.before_validation(*args, &block) ⇒ Object
242
|
# File 'lib/active_item/base.rb', line 242
def before_validation(*args, &block) = set_callback(:validation, :before, *args, &block)
|
.const_missing(name) ⇒ Object
19
20
21
|
# File 'lib/active_item/base.rb', line 19
def self.const_missing(name)
ActiveItem.const_defined?(name) ? ActiveItem.const_get(name) : super
end
|
.create(attributes = {}) ⇒ Object
370
371
372
373
374
|
# File 'lib/active_item/base.rb', line 370
def self.create(attributes = {})
obj = new(attributes)
obj.save
obj
end
|
.create!(attributes = {}) ⇒ Object
376
377
378
379
380
|
# File 'lib/active_item/base.rb', line 376
def self.create!(attributes = {})
obj = new(attributes)
obj.save!
obj
end
|
.dynamo_attribute_map(mappings = nil) ⇒ Object
147
148
149
150
151
152
153
|
# File 'lib/active_item/base.rb', line 147
def dynamo_attribute_map(mappings = nil)
if mappings
@dynamo_attribute_map = mappings.transform_keys(&:to_s)
else
@dynamo_attribute_map || {}
end
end
|
.dynamo_key_variants(attr_name) ⇒ Object
168
169
170
171
172
173
|
# File 'lib/active_item/base.rb', line 168
def dynamo_key_variants(attr_name)
attr_str = attr_name.to_s
primary_key = to_dynamo_key(attr_str)
camel_case = attr_str.camelize(:lower)
[primary_key, camel_case, attr_str].uniq
end
|
.dynamodb ⇒ Object
139
140
141
|
# File 'lib/active_item/base.rb', line 139
def dynamodb
@dynamodb ||= Aws::DynamoDB::Client.new(http_wire_trace: false)
end
|
.dynamodb=(client) ⇒ Object
143
144
145
|
# File 'lib/active_item/base.rb', line 143
def dynamodb=(client)
@dynamodb = client
end
|
.find_or_create_by(attributes, &block) ⇒ Object
201
202
203
204
205
206
207
208
209
|
# File 'lib/active_item/base.rb', line 201
def find_or_create_by(attributes, &block)
record = find_by(**attributes)
return record if record
record = new(**attributes)
block.call(record) if block_given?
record.save
record
end
|
.from_dynamo_key(dynamo_key) ⇒ Object
161
162
163
164
165
166
|
# File 'lib/active_item/base.rb', line 161
def from_dynamo_key(dynamo_key)
key_str = dynamo_key.to_s
reverse_map = dynamo_attribute_map.invert
return reverse_map[key_str] if reverse_map.key?(key_str)
key_str.underscore
end
|
.instantiate(item) ⇒ Object
175
176
177
178
179
180
181
182
183
184
185
186
|
# File 'lib/active_item/base.rb', line 175
def instantiate(item)
normalized_item = normalize_dynamodb_values(item)
record = allocate
record.instance_variable_set(:@id, normalized_item[self.primary_key])
record.send(:populate_attributes_from_item, normalized_item)
record.instance_variable_set(:@new_record, false)
record.instance_variable_set(:@previously_changed, {})
record.instance_variable_set(:@pending_changes, {})
record.instance_variable_set(:@dbrecord, normalized_item)
record
end
|
.normalize_dynamodb_values(obj) ⇒ Object
188
189
190
191
192
193
194
195
196
197
198
199
|
# File 'lib/active_item/base.rb', line 188
def normalize_dynamodb_values(obj)
case obj
when BigDecimal
obj.frac.zero? ? obj.to_i : obj.to_f
when Hash
obj.transform_values { |v| normalize_dynamodb_values(v) }
when Array
obj.map { |v| normalize_dynamodb_values(v) }
else
obj
end
end
|
.primary_key ⇒ Object
117
118
119
|
# File 'lib/active_item/base.rb', line 117
def primary_key
@primary_key ||= 'id'
end
|
.primary_key=(value) ⇒ Object
121
122
123
124
125
126
127
128
129
|
# File 'lib/active_item/base.rb', line 121
def primary_key=(value)
remove_method primary_key.to_sym
remove_method "#{primary_key}=".to_sym
@primary_key = value.to_s
alias_method primary_key.to_sym, :id
alias_method "#{primary_key}=".to_sym, :id=
end
|
.scope(name, body) ⇒ Object
247
248
249
250
251
|
# File 'lib/active_item/base.rb', line 247
def scope(name, body)
raise ArgumentError, "scope body must be callable (Proc/Lambda)" unless body.respond_to?(:call)
_scopes[name.to_sym] = body
define_singleton_method(name) { all.instance_exec(&body) }
end
|
.table_name ⇒ Object
131
132
133
|
# File 'lib/active_item/base.rb', line 131
def table_name
@table_name || default_table_name
end
|
.table_name=(value) ⇒ Object
135
136
137
|
# File 'lib/active_item/base.rb', line 135
def table_name=(value)
@table_name = value.to_s
end
|
.to_dynamo_key(attr_name) ⇒ Object
155
156
157
158
159
|
# File 'lib/active_item/base.rb', line 155
def to_dynamo_key(attr_name)
attr_str = attr_name.to_s
return dynamo_attribute_map[attr_str] if dynamo_attribute_map.key?(attr_str)
attr_str.camelize(:lower)
end
|
.transaction {|txn| ... } ⇒ Object
382
383
384
385
386
|
# File 'lib/active_item/base.rb', line 382
def self.transaction
txn = Transaction.new
yield txn
txn.execute!
end
|
.transaction_find(items) ⇒ Object
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
|
# File 'lib/active_item/base.rb', line 388
def self.transaction_find(items)
return [] if items.empty?
raise TransactionError, "DynamoDB transactions are limited to 100 items (got #{items.length})" if items.length > 100
transact_items = items.map do |item|
{ get: { table_name: item[:model].table_name, key: { item[:model].primary_key.to_s => item[:key] } } }
end
client = items.first[:model].dynamodb
response = client.transact_get_items(transact_items: transact_items)
response.responses.each_with_index.map do |resp, idx|
items[idx][:model].instantiate(resp.item) if resp.item
end
rescue Aws::DynamoDB::Errors::TransactionCanceledException => e
raise TransactionError, "Transaction read cancelled: #{e.message}"
end
|
Instance Method Details
#_preloaded_associations ⇒ Object
61
62
63
|
# File 'lib/active_item/base.rb', line 61
def _preloaded_associations
@_preloaded_associations ||= {}
end
|
#_preloaded_counts ⇒ Object
57
58
59
|
# File 'lib/active_item/base.rb', line 57
def _preloaded_counts
@_preloaded_counts ||= {}
end
|
#assign_attributes(attributes) ⇒ Object
426
427
428
429
430
431
432
433
434
435
|
# File 'lib/active_item/base.rb', line 426
def assign_attributes(attributes)
attributes.each do |key, value|
setter = "#{key}="
if respond_to?(setter)
old_value = send(key)
@pending_changes[key.to_s] = [old_value, value] if old_value != value
send(setter, value)
end
end
end
|
#attribute_changed?(attr_name) ⇒ Boolean
437
438
439
|
# File 'lib/active_item/base.rb', line 437
def attribute_changed?(attr_name)
@pending_changes.key?(attr_name.to_s)
end
|
#attribute_was(attr_name) ⇒ Object
441
442
443
|
# File 'lib/active_item/base.rb', line 441
def attribute_was(attr_name)
@pending_changes.dig(attr_name.to_s, 0)
end
|
#attributes ⇒ Object
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
|
# File 'lib/active_item/base.rb', line 308
def attributes
attrs = {}
pk_name = self.class.primary_key
pk_value = send(pk_name) rescue instance_variable_get("@#{pk_name}")
attrs['id'] = pk_value
attrs[pk_name] = pk_value
self.class.attribute_names.each do |attr_name|
next if attr_name == 'dbrecord'
value = instance_variable_get("@#{attr_name}")
attrs[attr_name] = value unless value.nil?
end
attrs['created_at'] = @created_at
attrs['updated_at'] = @updated_at
attrs
end
|
#changes ⇒ Object
445
446
447
|
# File 'lib/active_item/base.rb', line 445
def changes
@pending_changes
end
|
#changes_applied ⇒ Object
453
454
455
456
457
|
# File 'lib/active_item/base.rb', line 453
def changes_applied
@previously_changed = @pending_changes.dup
@pending_changes = {}
@new_record = false
end
|
#delete ⇒ Object
418
419
420
421
422
423
424
|
# File 'lib/active_item/base.rb', line 418
def delete
perform_destroy
true
rescue => e
dynamo_logger.error("Failed to delete #{self.class.name}: #{e.message}")
false
end
|
#destroy ⇒ Object
406
407
408
409
410
411
412
413
414
415
416
|
# File 'lib/active_item/base.rb', line 406
def destroy
result = run_callbacks(:destroy) { perform_destroy }
return false if result == false
true
rescue DeleteRestrictionError
false
rescue => e
dynamo_logger.error("Failed to destroy #{self.class.name}: #{e.message}")
errors.add(:base, e.message)
false
end
|
#has_changes_to_save? ⇒ Boolean
300
301
302
|
# File 'lib/active_item/base.rb', line 300
def has_changes_to_save?
changes.any?
end
|
#inspect ⇒ Object
326
327
328
329
330
331
332
333
334
335
|
# File 'lib/active_item/base.rb', line 326
def inspect
pk_value = send(self.class.primary_key) rescue id
attr_strs = self.class.attribute_names.filter_map do |attr|
next if attr == 'dbrecord'
value = instance_variable_get("@#{attr}")
next if value.nil?
"#{attr}: #{value.inspect}"
end
"#<#{self.class.name} #{attr_strs.join(', ')}>"
end
|
#new_record? ⇒ Boolean
273
274
275
|
# File 'lib/active_item/base.rb', line 273
def new_record?
@new_record != false
end
|
#persisted? ⇒ Boolean
277
278
279
|
# File 'lib/active_item/base.rb', line 277
def persisted?
!new_record?
end
|
#populate_attributes_from_item(item) ⇒ Object
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
# File 'lib/active_item/base.rb', line 71
def populate_attributes_from_item(item)
self.class.attribute_names.each do |attr_name|
next if attr_name == 'id'
value = nil
found = false
self.class.dynamo_key_variants(attr_name).each do |key|
if item.key?(key)
value = item[key]
found = true
break
end
end
instance_variable_set("@#{attr_name}", value) if found
end
@created_at = item['createdAt'] || item['created_at']
@updated_at = item['updatedAt'] || item['updated_at']
populate_custom_attributes_from_item(item) if respond_to?(:populate_custom_attributes_from_item, true)
populate_composed_attributes_from_item(item) if self.class.respond_to?(:compositions) && self.class.compositions.any?
end
|
#previous_changes ⇒ Object
449
450
451
|
# File 'lib/active_item/base.rb', line 449
def previous_changes
@previously_changed
end
|
#reload ⇒ Object
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
|
# File 'lib/active_item/base.rb', line 281
def reload
raise "Cannot reload a new record" if new_record?
fresh_record = self.class.find(id)
raise "Record not found: #{self.class.name} with id #{id}" unless fresh_record
self.class.attribute_names.each do |attr_name|
next if attr_name == 'dbrecord'
value = fresh_record.instance_variable_get("@#{attr_name}")
instance_variable_set("@#{attr_name}", value)
end
@created_at = fresh_record.created_at
@updated_at = fresh_record.updated_at
@dbrecord = fresh_record.dbrecord
@pending_changes = {}
@previously_changed = {}
self
end
|
#save(validate: true) ⇒ Object
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
|
# File 'lib/active_item/base.rb', line 347
def save(validate: true)
return false if validate && !run_validations
result = run_callbacks :save do
if new_record?
run_callbacks(:create) { perform_create }
else
run_callbacks(:update) { perform_update }
end
end
return false if result == false
changes_applied
true
rescue => e
dynamo_logger.error("Failed to save #{self.class.name}: #{e.message}")
raise e
end
|
#save! ⇒ Object
366
367
368
|
# File 'lib/active_item/base.rb', line 366
def save!
raise StandardError, "Validation failed: #{errors.full_messages.join(', ')}" unless save
end
|
#to_h ⇒ Object
304
305
306
|
# File 'lib/active_item/base.rb', line 304
def to_h
attributes.with_indifferent_access
end
|
#update(attributes) ⇒ Object
337
338
339
340
|
# File 'lib/active_item/base.rb', line 337
def update(attributes)
assign_attributes(attributes)
save
end
|
#update!(attributes) ⇒ Object
342
343
344
345
|
# File 'lib/active_item/base.rb', line 342
def update!(attributes)
assign_attributes(attributes)
save!
end
|
#valid?(context = nil) ⇒ Boolean
459
460
461
462
463
464
465
466
467
468
|
# File 'lib/active_item/base.rb', line 459
def valid?(context = nil)
return super(context) if defined?(@running_validations) && @running_validations
@running_validations = true
begin
run_callbacks(:validation) { super(context) }
ensure
@running_validations = false
end
end
|