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.
- checksums.yaml +4 -4
- data/Gemfile +4 -1
- data/Gemfile.lock +272 -61
- data/LICENSE.txt +1 -1
- data/README.md +59 -79
- data/Rakefile +13 -1
- data/SUMMARY.md +22 -2
- data/bin/console +8 -0
- data/bin/loc +0 -0
- data/bin/setup +0 -0
- data/bin/standardize +0 -0
- data/lib/all_futures/association.rb +42 -0
- data/lib/all_futures/association_index.rb +69 -0
- data/lib/all_futures/attributes.rb +28 -0
- data/lib/all_futures/base.rb +66 -0
- data/lib/all_futures/callbacks.rb +71 -0
- data/lib/all_futures/configuration.rb +9 -0
- data/lib/all_futures/dirty.rb +116 -0
- data/lib/all_futures/embeds.rb +40 -0
- data/lib/all_futures/errors.rb +37 -0
- data/lib/all_futures/finder.rb +214 -0
- data/lib/all_futures/locale/en.yml +49 -0
- data/lib/all_futures/persistence.rb +595 -0
- data/lib/all_futures/presenter.rb +35 -0
- data/lib/all_futures/railtie.rb +26 -0
- data/lib/all_futures/translation.rb +22 -0
- data/lib/all_futures/validations.rb +10 -0
- data/lib/all_futures/version.rb +3 -1
- data/lib/all_futures/versions.rb +80 -0
- data/lib/all_futures.rb +24 -1
- data/lib/vendor/ACTIVE_ENTITY_MIT-LICENSE +42 -0
- data/lib/vendor/README.md +8 -0
- data/lib/vendor/active_entity/aggregations.rb +277 -0
- data/lib/vendor/active_entity/associations/embeds/association.rb +134 -0
- data/lib/vendor/active_entity/associations/embeds/builder/association.rb +105 -0
- data/lib/vendor/active_entity/associations/embeds/builder/collection_association.rb +71 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embedded_in.rb +38 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embeds_many.rb +13 -0
- data/lib/vendor/active_entity/associations/embeds/builder/embeds_one.rb +16 -0
- data/lib/vendor/active_entity/associations/embeds/builder/singular_association.rb +28 -0
- data/lib/vendor/active_entity/associations/embeds/collection_association.rb +209 -0
- data/lib/vendor/active_entity/associations/embeds/collection_proxy.rb +310 -0
- data/lib/vendor/active_entity/associations/embeds/embedded_in_association.rb +31 -0
- data/lib/vendor/active_entity/associations/embeds/embeds_many_association.rb +15 -0
- data/lib/vendor/active_entity/associations/embeds/embeds_one_association.rb +26 -0
- data/lib/vendor/active_entity/associations/embeds/singular_association.rb +35 -0
- data/lib/vendor/active_entity/associations.rb +168 -0
- data/lib/vendor/active_entity/attribute_assignment.rb +87 -0
- data/lib/vendor/active_entity/attribute_decorators.rb +90 -0
- data/lib/vendor/active_entity/attribute_methods/before_type_cast.rb +78 -0
- data/lib/vendor/active_entity/attribute_methods/dirty.rb +180 -0
- data/lib/vendor/active_entity/attribute_methods/primary_key.rb +96 -0
- data/lib/vendor/active_entity/attribute_methods/query.rb +38 -0
- data/lib/vendor/active_entity/attribute_methods/read.rb +44 -0
- data/lib/vendor/active_entity/attribute_methods/serialization.rb +90 -0
- data/lib/vendor/active_entity/attribute_methods/time_zone_conversion.rb +93 -0
- data/lib/vendor/active_entity/attribute_methods/write.rb +54 -0
- data/lib/vendor/active_entity/attribute_methods.rb +833 -0
- data/lib/vendor/active_entity/attributes.rb +240 -0
- data/lib/vendor/active_entity/base.rb +293 -0
- data/lib/vendor/active_entity/callbacks.rb +21 -0
- data/lib/vendor/active_entity/coders/json.rb +15 -0
- data/lib/vendor/active_entity/coders/yaml_column.rb +50 -0
- data/lib/vendor/active_entity/core.rb +348 -0
- data/lib/vendor/active_entity/enum.rb +260 -0
- data/lib/vendor/active_entity/errors.rb +69 -0
- data/lib/vendor/active_entity/gem_version.rb +17 -0
- data/lib/vendor/active_entity/inheritance.rb +176 -0
- data/lib/vendor/active_entity/integration.rb +78 -0
- data/lib/vendor/active_entity/locale/en.yml +45 -0
- data/lib/vendor/active_entity/model_schema.rb +118 -0
- data/lib/vendor/active_entity/nested_attributes.rb +554 -0
- data/lib/vendor/active_entity/persistence.rb +20 -0
- data/lib/vendor/active_entity/railtie.rb +76 -0
- data/lib/vendor/active_entity/readonly_attributes.rb +55 -0
- data/lib/vendor/active_entity/reflection.rb +447 -0
- data/lib/vendor/active_entity/serialization.rb +28 -0
- data/lib/vendor/active_entity/store.rb +291 -0
- data/lib/vendor/active_entity/translation.rb +24 -0
- data/lib/vendor/active_entity/type/date.rb +9 -0
- data/lib/vendor/active_entity/type/date_time.rb +9 -0
- data/lib/vendor/active_entity/type/internal/timezone.rb +17 -0
- data/lib/vendor/active_entity/type/json.rb +30 -0
- data/lib/vendor/active_entity/type/modifiers/array.rb +72 -0
- data/lib/vendor/active_entity/type/modifiers/array_without_blank.rb +19 -0
- data/lib/vendor/active_entity/type/registry.rb +112 -0
- data/lib/vendor/active_entity/type/serialized.rb +71 -0
- data/lib/vendor/active_entity/type/text.rb +11 -0
- data/lib/vendor/active_entity/type/time.rb +21 -0
- data/lib/vendor/active_entity/type/unsigned_integer.rb +17 -0
- data/lib/vendor/active_entity/type.rb +75 -0
- data/lib/vendor/active_entity/validate_embeds_association.rb +300 -0
- data/lib/vendor/active_entity/validations/absence.rb +25 -0
- data/lib/vendor/active_entity/validations/associated.rb +60 -0
- data/lib/vendor/active_entity/validations/length.rb +26 -0
- data/lib/vendor/active_entity/validations/presence.rb +68 -0
- data/lib/vendor/active_entity/validations/subset.rb +76 -0
- data/lib/vendor/active_entity/validations/uniqueness_in_embeds.rb +91 -0
- data/lib/vendor/active_entity/validations/uniqueness_on_active_record.rb +234 -0
- data/lib/vendor/active_entity/validations.rb +47 -0
- data/lib/vendor/active_entity/version.rb +10 -0
- data/lib/vendor/active_entity.rb +99 -0
- data/lib/vendor/core_ext/array_without_blank.rb +48 -0
- metadata +175 -21
- data/lib/all_futures/all_futures.rb +0 -28
- data/lib/all_futures/config.rb +0 -14
data/bin/console
CHANGED
|
@@ -4,4 +4,12 @@ require "bundler/setup"
|
|
|
4
4
|
require "all_futures"
|
|
5
5
|
require "pry"
|
|
6
6
|
|
|
7
|
+
# Configure Kredis to allow for testing without Redis in order to avoid exception:
|
|
8
|
+
# NoMethodError: undefined method `config_for' for nil:NilClass
|
|
9
|
+
Kredis.configurator = Class.new {
|
|
10
|
+
def config_for(name)
|
|
11
|
+
{db: "1"}
|
|
12
|
+
end
|
|
13
|
+
}.new
|
|
14
|
+
|
|
7
15
|
Pry.start
|
data/bin/loc
CHANGED
|
File without changes
|
data/bin/setup
CHANGED
|
File without changes
|
data/bin/standardize
CHANGED
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
module Association
|
|
5
|
+
def initialize_attributes(record, attributes = {})
|
|
6
|
+
record.assign_attributes attributes if attributes.any?
|
|
7
|
+
record._write_attribute(reflection.options[:foreign_key], owner.id) if owner.is_a?(AllFutures::Base)
|
|
8
|
+
set_inverse_instance(record)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module CollectionAssociationHydration
|
|
13
|
+
def reader
|
|
14
|
+
AssociationIndex.hydrate(self)
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def size
|
|
19
|
+
AssociationIndex.hydrate(self)
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module SingularAssociationHydration
|
|
25
|
+
def reader
|
|
26
|
+
AssociationIndex.hydrate(self)
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ActiveEntity::Associations::Embeds::Association
|
|
33
|
+
prepend AllFutures::Association
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class ActiveEntity::Associations::Embeds::CollectionAssociation
|
|
37
|
+
prepend AllFutures::CollectionAssociationHydration
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class ActiveEntity::Associations::Embeds::SingularAssociation
|
|
41
|
+
prepend AllFutures::SingularAssociationHydration
|
|
42
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
# Maintains Redis SETs that index embedded children by owner so associations
|
|
5
|
+
# can be lazy-loaded after find without scanning every key of the child class.
|
|
6
|
+
#
|
|
7
|
+
# Key shape: "OwnerClass:owner_id:association_name" => SET of child ids
|
|
8
|
+
module AssociationIndex
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def key(owner_class, owner_id, association_name)
|
|
12
|
+
"#{owner_class.name}:#{owner_id}:#{association_name}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add(owner_class, owner_id, association_name, child_id)
|
|
16
|
+
return if owner_id.blank? || child_id.blank?
|
|
17
|
+
Kredis.redis.sadd(key(owner_class, owner_id, association_name), child_id.to_s)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def remove(owner_class, owner_id, association_name, child_id)
|
|
21
|
+
return if owner_id.blank? || child_id.blank?
|
|
22
|
+
Kredis.redis.srem(key(owner_class, owner_id, association_name), child_id.to_s)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def clear(owner_class, owner_id, association_name)
|
|
26
|
+
return if owner_id.blank?
|
|
27
|
+
Kredis.redis.del(key(owner_class, owner_id, association_name))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def member_ids(owner_class, owner_id, association_name)
|
|
31
|
+
return [] if owner_id.blank?
|
|
32
|
+
Array(Kredis.redis.smembers(key(owner_class, owner_id, association_name)))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def hydrate(association)
|
|
36
|
+
return if association.instance_variable_get(:@af_hydrated)
|
|
37
|
+
association.instance_variable_set(:@af_hydrated, true)
|
|
38
|
+
|
|
39
|
+
owner = association.owner
|
|
40
|
+
return unless owner.is_a?(AllFutures::Base) && owner.persisted?
|
|
41
|
+
|
|
42
|
+
reflection = association.reflection
|
|
43
|
+
return unless [:embeds_many, :embeds_one].include?(reflection.macro)
|
|
44
|
+
|
|
45
|
+
ids = member_ids(owner.class, owner.id, reflection.name)
|
|
46
|
+
return if ids.empty?
|
|
47
|
+
|
|
48
|
+
existing_ids = Array.wrap(association.target).compact.map { |r| r.id.to_s }
|
|
49
|
+
|
|
50
|
+
ids.each do |child_id|
|
|
51
|
+
next if existing_ids.include?(child_id.to_s)
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
record = reflection.klass.find(child_id)
|
|
55
|
+
rescue AllFutures::RecordNotFound
|
|
56
|
+
remove(owner.class, owner.id, reflection.name, child_id)
|
|
57
|
+
next
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
if reflection.macro == :embeds_many
|
|
61
|
+
association.add_to_target(record, true)
|
|
62
|
+
else
|
|
63
|
+
association.target = record
|
|
64
|
+
association.set_inverse_instance(record)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
module Attributes
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
def has_future(name, klass, **options)
|
|
9
|
+
ivar_symbol = :"@#{name}_all_futures"
|
|
10
|
+
|
|
11
|
+
define_method(name) do
|
|
12
|
+
if instance_variable_defined?(ivar_symbol)
|
|
13
|
+
instance_variable_get(ivar_symbol)
|
|
14
|
+
else
|
|
15
|
+
af_key = if options[:key]
|
|
16
|
+
options[:key]
|
|
17
|
+
else
|
|
18
|
+
record_id = try(:id) or raise AllFutures::ParentModelNotSavedYet, "AllFutures requires a unique key. Either save the parent model before accessing #{name}, or pass a custom key."
|
|
19
|
+
"#{self.class.name.tableize.tr("/", ":")}:#{record_id}:#{name}"
|
|
20
|
+
end
|
|
21
|
+
af = klass.exists?(af_key) ? klass.find(af_key) : klass.new(id: af_key)
|
|
22
|
+
instance_variable_set(ivar_symbol, af)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
class Base < ActiveEntity::Base
|
|
5
|
+
prepend ::AllFutures::Callbacks
|
|
6
|
+
extend ::ActiveModel::Naming
|
|
7
|
+
extend ::AllFutures::Translation
|
|
8
|
+
include ::AllFutures::Persistence
|
|
9
|
+
include ::AllFutures::Dirty
|
|
10
|
+
include ::AllFutures::Validations
|
|
11
|
+
include ::AllFutures::Presenter
|
|
12
|
+
include ::AllFutures::Versions
|
|
13
|
+
include ::AllFutures::Finder
|
|
14
|
+
include ::AllFutures::Embeds
|
|
15
|
+
include ::ActiveModel::Conversion
|
|
16
|
+
include ::ActiveModel::SecurePassword
|
|
17
|
+
include ::ActiveRecord::Integration
|
|
18
|
+
include ::Kredis::Attributes
|
|
19
|
+
|
|
20
|
+
attr_reader :created_at, :updated_at
|
|
21
|
+
|
|
22
|
+
def initialize(attributes = {}, &block)
|
|
23
|
+
attributes ||= {}
|
|
24
|
+
attributes = attributes.attributes.transform_keys(&:to_sym) if attributes.is_a?(ActiveRecord::Base)
|
|
25
|
+
attributes_for_super = attributes.key?(:id) ? attributes.except(:id).except(:created_at).except(:updated_at) : attributes
|
|
26
|
+
|
|
27
|
+
super(attributes_for_super) do
|
|
28
|
+
@id = attributes&.fetch(:id, nil) || ULID.generate
|
|
29
|
+
@created_at = Time.current
|
|
30
|
+
@updated_at = attributes&.fetch(:updated_at, Time.current)
|
|
31
|
+
@redis_key = "#{self.class.name}:#{@id}"
|
|
32
|
+
@new_record = !self.class.exists?(@id)
|
|
33
|
+
@destroyed = false
|
|
34
|
+
@previously_new_record = false
|
|
35
|
+
@_versioning_enabled = self.class.versioning
|
|
36
|
+
@_versions = {}
|
|
37
|
+
@_current_version = nil
|
|
38
|
+
@_id_set = attributes.key?(:id)
|
|
39
|
+
|
|
40
|
+
@attributes.keys.each do |attr|
|
|
41
|
+
define_singleton_method("#{attr}_changed?") { attribute_changed?(attr) }
|
|
42
|
+
define_singleton_method("#{attr}_valid?") { attribute_valid?(attr) }
|
|
43
|
+
define_singleton_method("rollback_#{attr}") { rollback_attribute(attr) }
|
|
44
|
+
define_singleton_method("rollback_#{attr}!") { rollback_attribute!(attr) }
|
|
45
|
+
define_singleton_method("restore_#{attr}") { restore_attribute(attr) }
|
|
46
|
+
define_singleton_method("restore_#{attr}!") { restore_attribute(attr) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
block&.call(self)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def id
|
|
54
|
+
(!new_record? || @_id_set) ? @id.to_s : nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def id=(value)
|
|
58
|
+
raise FrozenError.new("can't modify id when persisted") unless new_record?
|
|
59
|
+
@id = value.to_s
|
|
60
|
+
@redis_key = "#{self.class.name}:#{@id}"
|
|
61
|
+
@_id_set = true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ActiveSupport.run_load_hooks(:all_futures, Base)
|
|
66
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/callbacks"
|
|
4
|
+
|
|
5
|
+
module AllFutures
|
|
6
|
+
module Callbacks
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
CALLBACKS = [:before_save, :after_save, :before_update, :after_update, :before_destroy, :after_destroy]
|
|
10
|
+
|
|
11
|
+
def self.prepended(base)
|
|
12
|
+
base.include(ActiveSupport::Callbacks)
|
|
13
|
+
|
|
14
|
+
base.define_callbacks(:save, :update, :destroy, skip_after_callbacks_if_terminated: true) unless base.respond_to?(:_perform_callbacks) && base._perform_callbacks.present?
|
|
15
|
+
|
|
16
|
+
class << base
|
|
17
|
+
prepend ClassMethods
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def destroy
|
|
22
|
+
perform_operation(:destroy)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def destroy!
|
|
26
|
+
perform_operation(:destroy!)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def save
|
|
30
|
+
perform_operation(:save)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def save!
|
|
34
|
+
perform_operation(:save!)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update(attrs = {})
|
|
38
|
+
perform_operation(:update, attrs)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update!(attrs = {})
|
|
42
|
+
perform_operation(:update!, attrs)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def perform_operation(operation, *args)
|
|
46
|
+
if respond_to?(:run_callbacks)
|
|
47
|
+
callback = operation.to_s.sub("!", "").to_sym
|
|
48
|
+
run_callbacks callback do
|
|
49
|
+
method(operation).super_method.call(*args)
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
method(operation).super_method.call(*args)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
module ClassMethods
|
|
57
|
+
def method_missing(name, *filters, &blk)
|
|
58
|
+
if CALLBACKS.include? name
|
|
59
|
+
callback_context, method_name = name.to_s.split("_")
|
|
60
|
+
set_callback(method_name.to_sym, callback_context.to_sym, *filters, &blk)
|
|
61
|
+
else
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def respond_to_missing?(name, include_all)
|
|
67
|
+
CALLBACKS.include?(name) || super
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
# When true, versioned models save via a Redis Lua compare-and-set so two
|
|
5
|
+
# processes cannot both pass a stale check and silently clobber each other.
|
|
6
|
+
# Default false: the in-process guard rail (GET, check, SET) is enough for
|
|
7
|
+
# most apps and avoids an EVAL on every write.
|
|
8
|
+
mattr_accessor :atomic_locking, instance_accessor: false, default: false
|
|
9
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
module Dirty
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
def attribute_change(attribute)
|
|
8
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
9
|
+
(attribute_was(attribute) == self[attribute]) ? nil : [attribute_was(attribute), self[attribute]]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def attribute_changed?(attribute)
|
|
13
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
14
|
+
changes.key? attribute.to_s
|
|
15
|
+
end
|
|
16
|
+
alias_method :attribute_will_change?, :attribute_changed?
|
|
17
|
+
alias_method :will_save_change_to_attribute?, :attribute_changed?
|
|
18
|
+
|
|
19
|
+
def attribute_changed!(attribute)
|
|
20
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
21
|
+
public_send("#{attribute}_will_change!")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def attribute_previous_change(attribute)
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def attribute_will_change!(attribute)
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clear_attribute_change(attribute)
|
|
33
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def clear_attribute_changes(attr_names = changed)
|
|
38
|
+
super
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def dirty?
|
|
42
|
+
changes.any?
|
|
43
|
+
end
|
|
44
|
+
alias_method :changed_attributes?, :dirty?
|
|
45
|
+
alias_method :has_changes_to_save?, :dirty?
|
|
46
|
+
|
|
47
|
+
def previous_attributes
|
|
48
|
+
attribute_names.each_with_object({}) do |attribute, hash|
|
|
49
|
+
hash[attribute] = attribute_previously_was(attribute)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def mutations_before_last_save
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def restore_attribute(attribute)
|
|
58
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
59
|
+
attribute = attribute.to_s
|
|
60
|
+
if attribute_changed?(attribute)
|
|
61
|
+
self[attribute] = attribute_was(attribute)
|
|
62
|
+
clear_attribute_change(attribute)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def restore_attributes(attr_names = changed)
|
|
67
|
+
attr_names.each { |attribute| _raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s) }.each { |attribute| restore_attribute(attribute) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def rollback_attribute(attribute)
|
|
71
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
72
|
+
if self[attribute] != attribute_previously_was(attribute)
|
|
73
|
+
self[attribute] = attribute_previously_was(attribute)
|
|
74
|
+
clear_attribute_change(attribute)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def rollback_attribute!(attribute)
|
|
79
|
+
_raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s)
|
|
80
|
+
# unlike rollback_attribute, keep the mutation tracked so save sees a dirty record
|
|
81
|
+
self[attribute] = attribute_previously_was(attribute) if self[attribute] != attribute_previously_was(attribute)
|
|
82
|
+
save
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def rollback_attributes(attr_names = changed)
|
|
86
|
+
attr_names.each { |attribute| _raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s) }.each { |attribute| rollback_attribute(attribute) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def rollback_attributes!(attr_names = changed)
|
|
90
|
+
attr_names.each { |attribute| _raise_unknown_attribute_error(attribute) unless attributes.key?(attribute.to_s) }
|
|
91
|
+
attr_names.each { |attribute| self[attribute] = attribute_previously_was(attribute) if self[attribute] != attribute_previously_was(attribute) }
|
|
92
|
+
save
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def saved_changes?
|
|
96
|
+
saved_changes.any?
|
|
97
|
+
end
|
|
98
|
+
alias_method :previous_changes?, :saved_changes?
|
|
99
|
+
|
|
100
|
+
def saved_changes
|
|
101
|
+
attributes.select { |attribute| attribute_previously_changed? attribute }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
module ClassMethods
|
|
105
|
+
def set_previous_attributes(model, record)
|
|
106
|
+
tracker = ActiveModel::AttributeMutationTracker.new(model.instance_variable_get(:@attributes))
|
|
107
|
+
previous_values = tracker.instance_variable_get(:@attributes).instance_variable_get(:@attributes)
|
|
108
|
+
previous_values.each do |key, attribute|
|
|
109
|
+
original = attribute.instance_variable_get(:@original_attribute)
|
|
110
|
+
original&.instance_variable_set(:@value_before_type_cast, record["previous_attributes"][key])
|
|
111
|
+
end
|
|
112
|
+
model.instance_variable_set :@mutations_before_last_save, tracker
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
module Embeds
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
def embedded_in(name, **options)
|
|
9
|
+
autosave = options.delete(:autosave)
|
|
10
|
+
dependent = options.delete(:dependent)
|
|
11
|
+
foreign_key = options.delete(:foreign_key)
|
|
12
|
+
embeds = super
|
|
13
|
+
embeds[name.to_s].options[:autosave] = autosave
|
|
14
|
+
embeds[name.to_s].options[:dependent] = dependent
|
|
15
|
+
# FK lives on the child and points at the parent: government_id, not spy_id
|
|
16
|
+
embeds[name.to_s].options[:foreign_key] = foreign_key || "#{name}_id"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def embeds_one(name, **options)
|
|
20
|
+
autosave = options.delete(:autosave)
|
|
21
|
+
dependent = options.delete(:dependent)
|
|
22
|
+
foreign_key = options.delete(:foreign_key)
|
|
23
|
+
embeds = super
|
|
24
|
+
embeds[name.to_s].options[:autosave] = autosave
|
|
25
|
+
embeds[name.to_s].options[:dependent] = dependent
|
|
26
|
+
embeds[name.to_s].options[:foreign_key] = foreign_key || model_name.singular + "_id"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def embeds_many(name, **options)
|
|
30
|
+
autosave = options.delete(:autosave)
|
|
31
|
+
dependent = options.delete(:dependent)
|
|
32
|
+
foreign_key = options.delete(:foreign_key)
|
|
33
|
+
embeds = super
|
|
34
|
+
embeds[name.to_s].options[:autosave] = autosave
|
|
35
|
+
embeds[name.to_s].options[:dependent] = dependent
|
|
36
|
+
embeds[name.to_s].options[:foreign_key] = foreign_key || model_name.singular + "_id"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module AllFutures
|
|
4
|
+
class DeleteRestrictionError < StandardError
|
|
5
|
+
def initialize(name = nil)
|
|
6
|
+
if name
|
|
7
|
+
super("Cannot delete record because of dependent #{name}")
|
|
8
|
+
else
|
|
9
|
+
super("Delete restriction error.")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class InvalidAttribute < StandardError; end
|
|
15
|
+
|
|
16
|
+
class InvalidDependentOption < StandardError; end
|
|
17
|
+
|
|
18
|
+
class MissingForeignKeyError < StandardError; end
|
|
19
|
+
|
|
20
|
+
class ParentModelNotSavedYet < StandardError; end
|
|
21
|
+
|
|
22
|
+
class ReadOnlyRecord < StandardError; end
|
|
23
|
+
|
|
24
|
+
class RecordInvalid < StandardError; end
|
|
25
|
+
|
|
26
|
+
class RecordNotFound < StandardError; end
|
|
27
|
+
|
|
28
|
+
class RecordNotDestroyed < StandardError; end
|
|
29
|
+
|
|
30
|
+
class RecordNotSaved < StandardError; end
|
|
31
|
+
|
|
32
|
+
class RecordStale < StandardError; end
|
|
33
|
+
|
|
34
|
+
class SoleRecordExceeded < StandardError; end
|
|
35
|
+
|
|
36
|
+
class VersionNotFound < StandardError; end
|
|
37
|
+
end
|