all_futures 1.0.3 → 2.0.0

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.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +272 -61
  4. data/LICENSE.txt +1 -1
  5. data/README.md +59 -79
  6. data/Rakefile +13 -1
  7. data/SUMMARY.md +22 -2
  8. data/bin/console +8 -0
  9. data/bin/loc +0 -0
  10. data/bin/setup +0 -0
  11. data/bin/standardize +0 -0
  12. data/lib/all_futures/association.rb +42 -0
  13. data/lib/all_futures/association_index.rb +69 -0
  14. data/lib/all_futures/attributes.rb +28 -0
  15. data/lib/all_futures/base.rb +66 -0
  16. data/lib/all_futures/callbacks.rb +71 -0
  17. data/lib/all_futures/configuration.rb +9 -0
  18. data/lib/all_futures/dirty.rb +116 -0
  19. data/lib/all_futures/embeds.rb +40 -0
  20. data/lib/all_futures/errors.rb +37 -0
  21. data/lib/all_futures/finder.rb +214 -0
  22. data/lib/all_futures/locale/en.yml +49 -0
  23. data/lib/all_futures/persistence.rb +595 -0
  24. data/lib/all_futures/presenter.rb +35 -0
  25. data/lib/all_futures/railtie.rb +26 -0
  26. data/lib/all_futures/translation.rb +22 -0
  27. data/lib/all_futures/validations.rb +10 -0
  28. data/lib/all_futures/version.rb +3 -1
  29. data/lib/all_futures/versions.rb +80 -0
  30. data/lib/all_futures.rb +24 -1
  31. data/lib/vendor/ACTIVE_ENTITY_MIT-LICENSE +42 -0
  32. data/lib/vendor/README.md +8 -0
  33. data/lib/vendor/active_entity/aggregations.rb +277 -0
  34. data/lib/vendor/active_entity/associations/embeds/association.rb +134 -0
  35. data/lib/vendor/active_entity/associations/embeds/builder/association.rb +105 -0
  36. data/lib/vendor/active_entity/associations/embeds/builder/collection_association.rb +71 -0
  37. data/lib/vendor/active_entity/associations/embeds/builder/embedded_in.rb +38 -0
  38. data/lib/vendor/active_entity/associations/embeds/builder/embeds_many.rb +13 -0
  39. data/lib/vendor/active_entity/associations/embeds/builder/embeds_one.rb +16 -0
  40. data/lib/vendor/active_entity/associations/embeds/builder/singular_association.rb +28 -0
  41. data/lib/vendor/active_entity/associations/embeds/collection_association.rb +209 -0
  42. data/lib/vendor/active_entity/associations/embeds/collection_proxy.rb +310 -0
  43. data/lib/vendor/active_entity/associations/embeds/embedded_in_association.rb +31 -0
  44. data/lib/vendor/active_entity/associations/embeds/embeds_many_association.rb +15 -0
  45. data/lib/vendor/active_entity/associations/embeds/embeds_one_association.rb +26 -0
  46. data/lib/vendor/active_entity/associations/embeds/singular_association.rb +35 -0
  47. data/lib/vendor/active_entity/associations.rb +168 -0
  48. data/lib/vendor/active_entity/attribute_assignment.rb +87 -0
  49. data/lib/vendor/active_entity/attribute_decorators.rb +90 -0
  50. data/lib/vendor/active_entity/attribute_methods/before_type_cast.rb +78 -0
  51. data/lib/vendor/active_entity/attribute_methods/dirty.rb +180 -0
  52. data/lib/vendor/active_entity/attribute_methods/primary_key.rb +96 -0
  53. data/lib/vendor/active_entity/attribute_methods/query.rb +38 -0
  54. data/lib/vendor/active_entity/attribute_methods/read.rb +44 -0
  55. data/lib/vendor/active_entity/attribute_methods/serialization.rb +90 -0
  56. data/lib/vendor/active_entity/attribute_methods/time_zone_conversion.rb +93 -0
  57. data/lib/vendor/active_entity/attribute_methods/write.rb +54 -0
  58. data/lib/vendor/active_entity/attribute_methods.rb +833 -0
  59. data/lib/vendor/active_entity/attributes.rb +240 -0
  60. data/lib/vendor/active_entity/base.rb +293 -0
  61. data/lib/vendor/active_entity/callbacks.rb +21 -0
  62. data/lib/vendor/active_entity/coders/json.rb +15 -0
  63. data/lib/vendor/active_entity/coders/yaml_column.rb +50 -0
  64. data/lib/vendor/active_entity/core.rb +348 -0
  65. data/lib/vendor/active_entity/enum.rb +260 -0
  66. data/lib/vendor/active_entity/errors.rb +69 -0
  67. data/lib/vendor/active_entity/gem_version.rb +17 -0
  68. data/lib/vendor/active_entity/inheritance.rb +176 -0
  69. data/lib/vendor/active_entity/integration.rb +78 -0
  70. data/lib/vendor/active_entity/locale/en.yml +45 -0
  71. data/lib/vendor/active_entity/model_schema.rb +118 -0
  72. data/lib/vendor/active_entity/nested_attributes.rb +554 -0
  73. data/lib/vendor/active_entity/persistence.rb +20 -0
  74. data/lib/vendor/active_entity/railtie.rb +76 -0
  75. data/lib/vendor/active_entity/readonly_attributes.rb +55 -0
  76. data/lib/vendor/active_entity/reflection.rb +447 -0
  77. data/lib/vendor/active_entity/serialization.rb +28 -0
  78. data/lib/vendor/active_entity/store.rb +291 -0
  79. data/lib/vendor/active_entity/translation.rb +24 -0
  80. data/lib/vendor/active_entity/type/date.rb +9 -0
  81. data/lib/vendor/active_entity/type/date_time.rb +9 -0
  82. data/lib/vendor/active_entity/type/internal/timezone.rb +17 -0
  83. data/lib/vendor/active_entity/type/json.rb +30 -0
  84. data/lib/vendor/active_entity/type/modifiers/array.rb +72 -0
  85. data/lib/vendor/active_entity/type/modifiers/array_without_blank.rb +19 -0
  86. data/lib/vendor/active_entity/type/registry.rb +112 -0
  87. data/lib/vendor/active_entity/type/serialized.rb +71 -0
  88. data/lib/vendor/active_entity/type/text.rb +11 -0
  89. data/lib/vendor/active_entity/type/time.rb +21 -0
  90. data/lib/vendor/active_entity/type/unsigned_integer.rb +17 -0
  91. data/lib/vendor/active_entity/type.rb +75 -0
  92. data/lib/vendor/active_entity/validate_embeds_association.rb +300 -0
  93. data/lib/vendor/active_entity/validations/absence.rb +25 -0
  94. data/lib/vendor/active_entity/validations/associated.rb +60 -0
  95. data/lib/vendor/active_entity/validations/length.rb +26 -0
  96. data/lib/vendor/active_entity/validations/presence.rb +68 -0
  97. data/lib/vendor/active_entity/validations/subset.rb +76 -0
  98. data/lib/vendor/active_entity/validations/uniqueness_in_embeds.rb +91 -0
  99. data/lib/vendor/active_entity/validations/uniqueness_on_active_record.rb +234 -0
  100. data/lib/vendor/active_entity/validations.rb +47 -0
  101. data/lib/vendor/active_entity/version.rb +10 -0
  102. data/lib/vendor/active_entity.rb +99 -0
  103. data/lib/vendor/core_ext/array_without_blank.rb +48 -0
  104. metadata +175 -21
  105. data/lib/all_futures/all_futures.rb +0 -28
  106. data/lib/all_futures/config.rb +0 -14
@@ -0,0 +1,595 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AllFutures
4
+ module Persistence
5
+ extend ActiveSupport::Concern
6
+
7
+ def decrement(attribute, by = 1)
8
+ increment attribute, -by
9
+ end
10
+
11
+ def decrement!(attribute, by = 1)
12
+ increment! attribute, -by
13
+ end
14
+
15
+ def delete
16
+ catch(:abort) do
17
+ _destroy_associations
18
+ _destroy_association_indexes
19
+ _remove_from_parent_indexes!
20
+ _delete_record if persisted?
21
+ @destroyed = true
22
+ return freeze
23
+ end
24
+ false
25
+ end
26
+
27
+ def destroy
28
+ _raise_readonly_record_error if readonly?
29
+ delete
30
+ end
31
+
32
+ def destroy!
33
+ _raise_readonly_record_error if readonly?
34
+ delete || _raise_record_not_destroyed_error
35
+ end
36
+
37
+ def destroyed?
38
+ @destroyed
39
+ end
40
+
41
+ def increment(attribute, by = 1)
42
+ _raise_invalid_attribute_error(attribute) unless attributes.key?(attribute.to_s)
43
+ self[attribute] ||= 0
44
+ self[attribute] += by
45
+ self
46
+ end
47
+
48
+ def increment!(attribute, by = 1)
49
+ increment attribute, by
50
+ save
51
+ self
52
+ end
53
+
54
+ def new_record?
55
+ @new_record
56
+ end
57
+
58
+ def previously_new_record?
59
+ @previously_new_record
60
+ end
61
+
62
+ def persisted?
63
+ !(@new_record || @destroyed)
64
+ end
65
+
66
+ def reload
67
+ raise AllFutures::RecordNotSaved.new("Can't load model that hasn't been saved") unless persisted?
68
+ record = self.class.send(:load_model, id)
69
+ attributes.each do |key, value|
70
+ self[key] = record["attributes"][key]
71
+ end
72
+ @new_record = false
73
+ @previously_new_record = false
74
+ if versioning_enabled?
75
+ @_current_version = record["current_version"]
76
+ @_versions = record["versions"].transform_keys(&:to_i)
77
+ end
78
+ changes_applied # reloaded values are not user-supplied changes; keeps future dirty tracking alive
79
+ instance_variable_set :@updated_at, Time.zone.parse(record["updated_at"])
80
+ instance_variable_set :@marked_for_destruction, false
81
+ instance_variable_set :@destroyed_by_association, nil
82
+ self.class.send(:set_previous_attributes, self, record)
83
+ self
84
+ end
85
+
86
+ def save
87
+ _create_or_update
88
+ rescue AllFutures::RecordInvalid
89
+ false
90
+ end
91
+
92
+ def save!
93
+ _create_or_update || _raise_record_not_saved_error
94
+ end
95
+
96
+ def toggle(attribute)
97
+ _raise_invalid_attribute_error(attribute) unless attributes.key?(attribute.to_s)
98
+ self[attribute] = !public_send("#{attribute}?")
99
+ self
100
+ end
101
+
102
+ def toggle!(attribute)
103
+ _raise_invalid_attribute_error(attribute) unless attributes.key?(attribute.to_s)
104
+ toggle attribute
105
+ update_attribute attribute, self[attribute]
106
+ end
107
+
108
+ def update(attrs)
109
+ assign_attributes attrs
110
+ save
111
+ end
112
+
113
+ def update!(attrs)
114
+ assign_attributes attrs
115
+ save!
116
+ end
117
+
118
+ def update_attribute(attribute, value)
119
+ _raise_invalid_attribute_error(attribute) unless attributes.key?(attribute.to_s)
120
+ _raise_readonly_attribute_error(attribute) if attr_readonly_enabled? && readonly_attribute?(attribute) && attribute_will_change?(attribute)
121
+ write_attribute attribute, value
122
+
123
+ touch
124
+
125
+ save
126
+ end
127
+
128
+ def touch
129
+ @updated_at = Time.current
130
+ end
131
+
132
+ # treat as private
133
+
134
+ def _changed_for_autosave?
135
+ new_record? || has_changes_to_save? || marked_for_destruction? || _nested_records_changed_for_autosave?
136
+ end
137
+
138
+ def _nested_records_changed_for_autosave?
139
+ @_nested_records_changed_for_autosave_already_called ||= false
140
+ return false if @_nested_records_changed_for_autosave_already_called
141
+ begin
142
+ @_nested_records_changed_for_autosave_already_called = true
143
+ _reflections.values.any? do |reflection|
144
+ if reflection.options[:autosave]
145
+ association = association_instance_get(reflection.name)
146
+ association && Array.wrap(association.target).any?(&:_changed_for_autosave?)
147
+ end
148
+ end
149
+ ensure
150
+ @_nested_records_changed_for_autosave_already_called = false
151
+ end
152
+ end
153
+
154
+ private
155
+
156
+ def _association_foreign_key_changed?(reflection, record, key)
157
+ record._has_attribute?(reflection.options[:foreign_key]) && record._read_attribute(reflection.options[:foreign_key]) != key
158
+ end
159
+
160
+ def _associated_records_to_validate_or_save(association, new_record, autosave)
161
+ if new_record || _custom_validation_context?
162
+ association&.target
163
+ elsif autosave
164
+ association.target.find_all(&:_changed_for_autosave?)
165
+ else
166
+ association.target.find_all(&:new_record?)
167
+ end
168
+ end
169
+
170
+ def _association_valid?(reflection, record, index = nil)
171
+ return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
172
+
173
+ context = validation_context if _custom_validation_context?
174
+
175
+ unless (valid = record.valid?(context))
176
+ if reflection.options[:autosave]
177
+ record.errors.group_by_attribute.each do |attribute, errors|
178
+ errors.each do |error|
179
+ self.errors.import(error, attribute: "#{reflection.name}.#{attribute}")
180
+ end
181
+ end
182
+ else
183
+ errors.add(reflection.name)
184
+ end
185
+ end
186
+ valid
187
+ end
188
+
189
+ def _create_or_update
190
+ _raise_readonly_record_error if readonly?
191
+ attributes.each_key { |attribute| _raise_readonly_attribute_error(attribute) if attr_readonly_enabled? && readonly_attribute?(attribute) && attribute_will_change?(attribute) }
192
+ return false if destroyed?
193
+
194
+ # capture before changes_applied resets dirty tracking; _save_record uses
195
+ # this to skip the Redis write (and version bump) when nothing changed
196
+ @_pending_write = new_record? || dirty?
197
+
198
+ changes_applied
199
+
200
+ previously_new_record_before_save = (@new_record_before_save ||= false)
201
+ @new_record_before_save = !previously_new_record_before_save && new_record?
202
+
203
+ _reflections.values.select { |reflection| reflection.macro == :embedded_in }.each do |reflection|
204
+ send(:_save_embedded_in, reflection)
205
+ end
206
+
207
+ result = new_record? ? _create_record : _update_record
208
+
209
+ _reflections.values.reject { |reflection| reflection.macro == :embedded_in }.each do |reflection|
210
+ send("_save_#{reflection.macro}", reflection)
211
+ end
212
+
213
+ @new_record_before_save = previously_new_record_before_save
214
+
215
+ result != false
216
+ end
217
+
218
+ def _create_record
219
+ _save_record
220
+ @new_record = false
221
+ @previously_new_record = true
222
+ _sync_embed_indexes!
223
+ true
224
+ end
225
+
226
+ def _custom_validation_context?
227
+ validation_context && [:create, :update].exclude?(validation_context)
228
+ end
229
+
230
+ def _delete_record
231
+ Kredis.redis.del(@redis_key)
232
+ end
233
+
234
+ def _destroy_associations
235
+ _reflections.values.each do |reflection|
236
+ if reflection.options[:dependent]
237
+ association = association_instance_get(reflection.name)
238
+ next unless association
239
+
240
+ case reflection.options[:dependent]
241
+ when :delete
242
+ Array.wrap(association.target).each(&:delete)
243
+ when :destroy
244
+ Array.wrap(association.target).each do |record|
245
+ record.destroyed_by_association = reflection
246
+ record.destroy
247
+ end
248
+ when :nullify
249
+ raise AllFutures::InvalidDependentOption.new(:nullify) if reflection.macro == :embedded_in
250
+ Array.wrap(association.target).each do |record|
251
+ record.update_attribute(reflection.options[:foreign_key], nil) if record.persisted?
252
+ end
253
+ when :restrict_with_exception
254
+ raise AllFutures::InvalidDependentOption.new(:restrict_with_exception) if reflection.macro == :embedded_in
255
+ raise AllFutures::DeleteRestrictionError.new(reflection.name) unless association.empty?
256
+ when :restrict_with_error
257
+ raise AllFutures::InvalidDependentOption.new(:restrict_with_error) if reflection.macro == :embedded_in
258
+ unless association.empty?
259
+ record = association.owner.class.human_attribute_name(reflection.name).downcase
260
+ association.owner.errors.add(:base, :"restrict_dependent_destroy.#{reflection.macro}", record: record)
261
+ throw(:abort)
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ def _record_changed?(reflection, record, key)
269
+ record.new_record? ||
270
+ _association_foreign_key_changed?(reflection, record, key) ||
271
+ record.will_save_change_to_attribute?(reflection.options[:foreign_key])
272
+ end
273
+
274
+ def _save_embeds_many(reflection)
275
+ if (association = association_instance_get(reflection.name))
276
+ _raise_missing_foreign_key_error(reflection) unless reflection.klass.has_attribute?(reflection.options[:foreign_key])
277
+ autosave = reflection.options[:autosave]
278
+
279
+ new_record_before_save = @new_record_before_save
280
+
281
+ if (records = _associated_records_to_validate_or_save(association, new_record_before_save, autosave))
282
+ if autosave
283
+ records_to_destroy = records.select(&:marked_for_destruction?)
284
+ records_to_destroy.each { |record| association.destroy(record) }
285
+ records -= records_to_destroy
286
+ end
287
+
288
+ records.each do |record|
289
+ next if record.destroyed?
290
+
291
+ saved = true
292
+ if autosave != false && (new_record_before_save || record.new_record?)
293
+ association.set_inverse_instance(record)
294
+
295
+ record.without_versioning do
296
+ if autosave
297
+ record._write_attribute(reflection.options[:foreign_key], id)
298
+ saved = record.save
299
+ elsif !reflection.nested?
300
+ record._write_attribute(reflection.options[:foreign_key], id)
301
+ association_saved = record.save
302
+
303
+ if reflection.validate?
304
+ errors.add(reflection.name) unless association_saved
305
+ saved = association_saved
306
+ end
307
+ end
308
+ end
309
+ elsif autosave
310
+ saved = record.save
311
+ end
312
+
313
+ raise(AllFutures::RecordInvalid.new(association.owner)) unless saved
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ def _save_embeds_one(reflection)
320
+ association = association_instance_get(reflection.name)
321
+ _raise_missing_foreign_key_error(reflection) unless reflection.klass.has_attribute?(reflection.options[:foreign_key])
322
+
323
+ record = association&.target
324
+
325
+ if record && !record.destroyed?
326
+ autosave = reflection.options[:autosave]
327
+ if autosave && record.marked_for_destruction?
328
+ record.destroy
329
+ elsif autosave != false
330
+ if (autosave && record._changed_for_autosave?) || _record_changed?(reflection, record, id)
331
+ record._write_attribute(reflection.options[:foreign_key], id)
332
+ association.set_inverse_instance(record)
333
+ record.without_versioning { record.save }
334
+ end
335
+ end
336
+ end
337
+ end
338
+
339
+ def _save_embedded_in(reflection)
340
+ association = association_instance_get(reflection.name)
341
+ return unless association&.loaded?
342
+
343
+ record = association.target
344
+
345
+ if record && !record.destroyed?
346
+ autosave = reflection.options[:autosave]
347
+
348
+ if autosave && record.marked_for_destruction?
349
+ record._write_attribute(reflection.options[:foreign_key], nil)
350
+ record.destroy
351
+ elsif autosave != false
352
+ saved = record.save if record.new_record? || (autosave && record.changed_for_autosave?)
353
+
354
+ saved if autosave
355
+ end
356
+ end
357
+ end
358
+
359
+ # Redis Lua: compare current_version then SET, or return STALE.
360
+ # KEYS[1] = redis key, ARGV[1] = expected version ("" if new), ARGV[2] = JSON payload
361
+ ATOMIC_SAVE_LUA = <<~LUA.freeze
362
+ local key = KEYS[1]
363
+ local expected = ARGV[1]
364
+ local payload = ARGV[2]
365
+ local raw = redis.call('GET', key)
366
+
367
+ if not raw then
368
+ if expected == '' then
369
+ redis.call('SET', key, payload)
370
+ return 'OK'
371
+ end
372
+ return 'STALE'
373
+ end
374
+
375
+ local data = cjson.decode(raw)
376
+ local current = data['current_version']
377
+ if current == cjson.null or current == nil then
378
+ current = ''
379
+ else
380
+ current = tostring(current)
381
+ end
382
+
383
+ if current == expected then
384
+ redis.call('SET', key, payload)
385
+ return 'OK'
386
+ end
387
+ return 'STALE'
388
+ LUA
389
+
390
+ def _save_record
391
+ return true unless @_pending_write
392
+
393
+ if versioning_enabled? && AllFutures.atomic_locking
394
+ _save_record_atomically
395
+ else
396
+ _save_version if versioning_enabled?
397
+ touch
398
+ Kredis.json(@redis_key).value = _snapshot
399
+ end
400
+ true
401
+ end
402
+
403
+ def _save_record_atomically
404
+ expected = @_current_version
405
+ previous_version = @_current_version
406
+ previous_versions = @_versions.dup
407
+
408
+ if new_record?
409
+ @_current_version = 1
410
+ else
411
+ @_current_version = @_current_version ? @_current_version.next : 1
412
+ end
413
+ @_versions[current_version] = {
414
+ "attributes" => attributes,
415
+ "updated_at" => Time.current
416
+ }
417
+ touch
418
+ payload = ActiveSupport::JSON.encode(_snapshot)
419
+ expected_arg = expected.nil? ? "" : expected.to_s
420
+
421
+ result = Kredis.redis.eval(ATOMIC_SAVE_LUA, keys: [@redis_key], argv: [expected_arg, payload])
422
+ if result == "STALE"
423
+ @_current_version = previous_version
424
+ @_versions = previous_versions
425
+ _raise_record_stale_error
426
+ end
427
+ end
428
+
429
+ def _sync_embed_indexes!
430
+ _reflections.values.select { |reflection| reflection.macro == :embedded_in }.each do |reflection|
431
+ inverse = reflection.inverse_of
432
+ next unless inverse
433
+
434
+ parent_class = reflection.klass
435
+ next unless parent_class < AllFutures::Base
436
+
437
+ fk = reflection.options[:foreign_key]
438
+ current_parent_id = _read_attribute(fk)
439
+ previous_parent_id = begin
440
+ attribute_previously_was(fk)
441
+ rescue
442
+ nil
443
+ end
444
+
445
+ if previous_parent_id.present? && previous_parent_id.to_s != current_parent_id.to_s
446
+ AssociationIndex.remove(parent_class, previous_parent_id, inverse.name, id)
447
+ end
448
+
449
+ if current_parent_id.present?
450
+ AssociationIndex.add(parent_class, current_parent_id, inverse.name, id)
451
+ end
452
+ end
453
+ end
454
+
455
+ def _remove_from_parent_indexes!
456
+ _reflections.values.select { |reflection| reflection.macro == :embedded_in }.each do |reflection|
457
+ inverse = reflection.inverse_of
458
+ next unless inverse
459
+
460
+ parent_class = reflection.klass
461
+ next unless parent_class < AllFutures::Base
462
+
463
+ parent_id = _read_attribute(reflection.options[:foreign_key])
464
+ AssociationIndex.remove(parent_class, parent_id, inverse.name, id) if parent_id.present?
465
+ end
466
+ end
467
+
468
+ def _destroy_association_indexes
469
+ return unless id
470
+
471
+ _reflections.values.select { |reflection| [:embeds_many, :embeds_one].include?(reflection.macro) }.each do |reflection|
472
+ AssociationIndex.clear(self.class, id, reflection.name)
473
+ end
474
+ end
475
+
476
+ def _save_version
477
+ if new_record?
478
+ @_current_version = 1
479
+ else
480
+ record = Kredis.json(@redis_key).value
481
+ _raise_record_stale_error if record&.dig("current_version") != @_current_version
482
+ @_current_version = @_current_version ? @_current_version.next : 1
483
+ end
484
+ @_versions[current_version] = {
485
+ "attributes" => attributes,
486
+ "updated_at" => Time.current
487
+ }
488
+ end
489
+
490
+ def _snapshot
491
+ {
492
+ attributes: attributes,
493
+ created_at: created_at,
494
+ updated_at: @updated_at,
495
+ previous_attributes: previous_attributes,
496
+ current_version: current_version,
497
+ versions: versions.transform_keys(&:to_i)
498
+ }.deep_transform_keys { |key| key.is_a?(Integer) ? key : key.to_sym }
499
+ end
500
+
501
+ def _raise_invalid_attribute_error(attribute)
502
+ raise AllFutures::InvalidAttribute.new(self, attribute)
503
+ end
504
+
505
+ def _raise_missing_foreign_key_error(reflection)
506
+ raise AllFutures::MissingForeignKeyError, "#{reflection.klass} missing foreign key #{reflection.options[:foreign_key]}"
507
+ end
508
+
509
+ def _raise_readonly_attribute_error(attribute)
510
+ raise AllFutures::ReadOnlyRecord, "#{attribute} is marked as readonly"
511
+ end
512
+
513
+ def _raise_readonly_record_error
514
+ raise AllFutures::ReadOnlyRecord, "#{self.class} is marked as readonly"
515
+ end
516
+
517
+ def _raise_record_not_destroyed_error
518
+ raise AllFutures::RecordNotDestroyed, "Failed to destroy the record"
519
+ end
520
+
521
+ def _raise_record_not_saved_error
522
+ raise AllFutures::RecordNotSaved, "Failed to save the record"
523
+ end
524
+
525
+ def _raise_record_stale_error
526
+ raise AllFutures::RecordStale, "Failed to save the record because it has been modified by another process"
527
+ end
528
+
529
+ def _update_record
530
+ _save_record
531
+ @previously_new_record = false
532
+ _sync_embed_indexes!
533
+ true
534
+ end
535
+
536
+ def _validate_single_association(reflection)
537
+ association = association_instance_get(reflection.name)
538
+ record = association&.reader
539
+ _association_valid?(reflection, record) if record && (record._changed_for_autosave? || _custom_validation_context?)
540
+ end
541
+
542
+ def _validate_collection_association(reflection)
543
+ if (association = association_instance_get(reflection.name))
544
+ if (records = _associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave]))
545
+ records.each_with_index { |record, index| _association_valid?(reflection, record, index) }
546
+ end
547
+ end
548
+ end
549
+
550
+ module ClassMethods
551
+ def create(attributes = {}, &block)
552
+ new(attributes).tap do |record|
553
+ block&.call(record)
554
+ record.save
555
+ end
556
+ end
557
+
558
+ def delete_all
559
+ Kredis.redis.del Kredis.redis.keys("#{name}:*")
560
+ end
561
+
562
+ def delete_by(attributes = {}, &block)
563
+ Kredis.redis.del where(attributes, &block).map { |record| "#{name}:#{record.id}" }
564
+ end
565
+
566
+ def destroy_all
567
+ all.each(&:destroy)
568
+ end
569
+
570
+ def destroy_by(attributes = {}, &block)
571
+ where(attributes, &block).each(&:destroy)
572
+ end
573
+
574
+ def readonly_attribute?(name)
575
+ _attr_readonly.include?(name.to_s)
576
+ end
577
+
578
+ private
579
+
580
+ def load_model(id)
581
+ record = Kredis.json("#{name}:#{id}").value
582
+ raise AllFutures::RecordNotFound.new("Couldn't find #{name} with id #{id}") unless record
583
+ attribute_names.each do |attribute|
584
+ case attribute_types[attribute]
585
+ when ActiveEntity::Type::Date
586
+ record[attribute] = Date.parse(record[attribute]) if record[attribute]
587
+ when ActiveEntity::Type::DateTime, ActiveEntity::Type::Time
588
+ record[attribute] = Time.zone.parse(record[attribute]) if record[attribute]
589
+ end
590
+ end
591
+ record
592
+ end
593
+ end
594
+ end
595
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AllFutures
4
+ module Presenter
5
+ extend ActiveSupport::Concern
6
+
7
+ attr_reader :updated_at
8
+
9
+ def max_updated_column_timestamp
10
+ @updated_at&.to_time
11
+ end
12
+
13
+ def reject
14
+ attributes
15
+ end
16
+
17
+ def to_dom_id
18
+ [self.class.name.underscore.dasherize.gsub("/", "--"), id].join("-")
19
+ end
20
+
21
+ def to_h
22
+ attributes
23
+ end
24
+
25
+ def to_s
26
+ inspect
27
+ end
28
+
29
+ module ClassMethods
30
+ def valid_attribute?(attribute)
31
+ (attribute_names + ["id"]).include?(attribute.to_s)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AllFutures::Railtie < ::Rails::Railtie
4
+ config.all_futures = ActiveSupport::OrderedOptions.new
5
+ config.all_futures.atomic_locking = false
6
+
7
+ initializer "all_futures.configure" do |app|
8
+ AllFutures.atomic_locking = app.config.all_futures.atomic_locking
9
+ end
10
+
11
+ initializer "all_futures.attributes" do
12
+ config.after_initialize do
13
+ ActiveModel::Model.include AllFutures::Attributes if defined?(ActiveModel::Model)
14
+ end
15
+
16
+ ActiveSupport.on_load(:active_record) do
17
+ include AllFutures::Attributes
18
+ end
19
+ end
20
+
21
+ initializer "all_futures.i18n" do
22
+ ActiveSupport.on_load(:i18n) do
23
+ I18n.load_path << File.expand_path("locale/en.yml", __dir__)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AllFutures
4
+ module Translation
5
+ include ActiveModel::Translation
6
+
7
+ def lookup_ancestors
8
+ klass = self
9
+ classes = [klass]
10
+ return classes if klass == AllFutures::Base
11
+
12
+ until klass.base_class?
13
+ classes << klass = klass.superclass
14
+ end
15
+ classes
16
+ end
17
+
18
+ def i18n_scope
19
+ :allfutures
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AllFutures
4
+ module Validations
5
+ def attribute_valid?(attribute)
6
+ validate
7
+ errors[attribute].empty?
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- VERSION = "1.0.3"
3
+ module AllFutures
4
+ VERSION = "2.0.0"
5
+ end