activeentity 0.0.1.beta1
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 +7 -0
 - data/MIT-LICENSE +42 -0
 - data/README.md +145 -0
 - data/Rakefile +29 -0
 - data/lib/active_entity.rb +73 -0
 - data/lib/active_entity/aggregations.rb +276 -0
 - data/lib/active_entity/associations.rb +146 -0
 - data/lib/active_entity/associations/embedded/association.rb +134 -0
 - data/lib/active_entity/associations/embedded/builder/association.rb +100 -0
 - data/lib/active_entity/associations/embedded/builder/collection_association.rb +69 -0
 - data/lib/active_entity/associations/embedded/builder/embedded_in.rb +38 -0
 - data/lib/active_entity/associations/embedded/builder/embeds_many.rb +13 -0
 - data/lib/active_entity/associations/embedded/builder/embeds_one.rb +16 -0
 - data/lib/active_entity/associations/embedded/builder/singular_association.rb +28 -0
 - data/lib/active_entity/associations/embedded/collection_association.rb +188 -0
 - data/lib/active_entity/associations/embedded/collection_proxy.rb +310 -0
 - data/lib/active_entity/associations/embedded/embedded_in_association.rb +31 -0
 - data/lib/active_entity/associations/embedded/embeds_many_association.rb +15 -0
 - data/lib/active_entity/associations/embedded/embeds_one_association.rb +19 -0
 - data/lib/active_entity/associations/embedded/singular_association.rb +35 -0
 - data/lib/active_entity/attribute_assignment.rb +85 -0
 - data/lib/active_entity/attribute_decorators.rb +90 -0
 - data/lib/active_entity/attribute_methods.rb +330 -0
 - data/lib/active_entity/attribute_methods/before_type_cast.rb +78 -0
 - data/lib/active_entity/attribute_methods/primary_key.rb +98 -0
 - data/lib/active_entity/attribute_methods/query.rb +35 -0
 - data/lib/active_entity/attribute_methods/read.rb +47 -0
 - data/lib/active_entity/attribute_methods/serialization.rb +90 -0
 - data/lib/active_entity/attribute_methods/time_zone_conversion.rb +91 -0
 - data/lib/active_entity/attribute_methods/write.rb +63 -0
 - data/lib/active_entity/attributes.rb +165 -0
 - data/lib/active_entity/base.rb +303 -0
 - data/lib/active_entity/coders/json.rb +15 -0
 - data/lib/active_entity/coders/yaml_column.rb +50 -0
 - data/lib/active_entity/core.rb +281 -0
 - data/lib/active_entity/define_callbacks.rb +17 -0
 - data/lib/active_entity/enum.rb +234 -0
 - data/lib/active_entity/errors.rb +80 -0
 - data/lib/active_entity/gem_version.rb +17 -0
 - data/lib/active_entity/inheritance.rb +278 -0
 - data/lib/active_entity/integration.rb +78 -0
 - data/lib/active_entity/locale/en.yml +45 -0
 - data/lib/active_entity/model_schema.rb +115 -0
 - data/lib/active_entity/nested_attributes.rb +592 -0
 - data/lib/active_entity/readonly_attributes.rb +47 -0
 - data/lib/active_entity/reflection.rb +441 -0
 - data/lib/active_entity/serialization.rb +25 -0
 - data/lib/active_entity/store.rb +242 -0
 - data/lib/active_entity/translation.rb +24 -0
 - data/lib/active_entity/type.rb +73 -0
 - data/lib/active_entity/type/date.rb +9 -0
 - data/lib/active_entity/type/date_time.rb +9 -0
 - data/lib/active_entity/type/decimal_without_scale.rb +15 -0
 - data/lib/active_entity/type/hash_lookup_type_map.rb +25 -0
 - data/lib/active_entity/type/internal/timezone.rb +17 -0
 - data/lib/active_entity/type/json.rb +30 -0
 - data/lib/active_entity/type/modifiers/array.rb +72 -0
 - data/lib/active_entity/type/registry.rb +92 -0
 - data/lib/active_entity/type/serialized.rb +71 -0
 - data/lib/active_entity/type/text.rb +11 -0
 - data/lib/active_entity/type/time.rb +21 -0
 - data/lib/active_entity/type/type_map.rb +62 -0
 - data/lib/active_entity/type/unsigned_integer.rb +17 -0
 - data/lib/active_entity/validate_embedded_association.rb +305 -0
 - data/lib/active_entity/validations.rb +50 -0
 - data/lib/active_entity/validations/absence.rb +25 -0
 - data/lib/active_entity/validations/associated.rb +60 -0
 - data/lib/active_entity/validations/length.rb +26 -0
 - data/lib/active_entity/validations/presence.rb +68 -0
 - data/lib/active_entity/validations/subset.rb +76 -0
 - data/lib/active_entity/validations/uniqueness_in_embedding.rb +99 -0
 - data/lib/active_entity/version.rb +10 -0
 - data/lib/tasks/active_entity_tasks.rake +6 -0
 - metadata +155 -0
 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Type
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Json < ActiveModel::Type::Value
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include ActiveModel::Type::Helpers::Mutable
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 9 
     | 
    
         
            +
                    :json
         
     | 
| 
      
 10 
     | 
    
         
            +
                  end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def deserialize(value)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    return value unless value.is_a?(::String)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    ActiveSupport::JSON.decode(value) rescue nil
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  def serialize(value)
         
     | 
| 
      
 18 
     | 
    
         
            +
                    ActiveSupport::JSON.encode(value) unless value.nil?
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                  def changed_in_place?(raw_old_value, new_value)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    deserialize(raw_old_value) != new_value
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def accessor
         
     | 
| 
      
 26 
     | 
    
         
            +
                    ActiveEntity::Store::StringKeyedHashAccessor
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,72 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Type
         
     | 
| 
      
 5 
     | 
    
         
            +
                module Modifiers
         
     | 
| 
      
 6 
     | 
    
         
            +
                  class Array < ActiveModel::Type::Value # :nodoc:
         
     | 
| 
      
 7 
     | 
    
         
            +
                    include ActiveModel::Type::Helpers::Mutable
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                    attr_reader :subtype, :delimiter
         
     | 
| 
      
 10 
     | 
    
         
            +
                    delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                    def initialize(subtype, delimiter = ",")
         
     | 
| 
      
 13 
     | 
    
         
            +
                      @subtype = subtype
         
     | 
| 
      
 14 
     | 
    
         
            +
                      @delimiter = delimiter
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                    def deserialize(value)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      case value
         
     | 
| 
      
 19 
     | 
    
         
            +
                      when ::String
         
     | 
| 
      
 20 
     | 
    
         
            +
                        type_cast_array(value.split(@delimiter), :deserialize)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      else
         
     | 
| 
      
 22 
     | 
    
         
            +
                        super
         
     | 
| 
      
 23 
     | 
    
         
            +
                      end
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    def cast(value)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      if value.is_a?(::String)
         
     | 
| 
      
 28 
     | 
    
         
            +
                        value = value.split(@delimiter)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      end
         
     | 
| 
      
 30 
     | 
    
         
            +
                      type_cast_array(value, :cast)
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    def serialize(value)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      if value.is_a?(::Array)
         
     | 
| 
      
 35 
     | 
    
         
            +
                        casted_values = type_cast_array(value, :serialize)
         
     | 
| 
      
 36 
     | 
    
         
            +
                        casted_values.join(@delimiter)
         
     | 
| 
      
 37 
     | 
    
         
            +
                      else
         
     | 
| 
      
 38 
     | 
    
         
            +
                        super
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    def ==(other)
         
     | 
| 
      
 43 
     | 
    
         
            +
                      other.is_a?(Array) &&
         
     | 
| 
      
 44 
     | 
    
         
            +
                        subtype == other.subtype &&
         
     | 
| 
      
 45 
     | 
    
         
            +
                        delimiter == other.delimiter
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    def map(value, &block)
         
     | 
| 
      
 49 
     | 
    
         
            +
                      value.map(&block)
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                    def changed_in_place?(raw_old_value, new_value)
         
     | 
| 
      
 53 
     | 
    
         
            +
                      deserialize(raw_old_value) != new_value
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                    def force_equality?(value)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      value.is_a?(::Array)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    private
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                      def type_cast_array(value, method)
         
     | 
| 
      
 63 
     | 
    
         
            +
                        if value.is_a?(::Array)
         
     | 
| 
      
 64 
     | 
    
         
            +
                          value.map { |item| type_cast_array(item, method) }
         
     | 
| 
      
 65 
     | 
    
         
            +
                        else
         
     | 
| 
      
 66 
     | 
    
         
            +
                          @subtype.public_send(method, value)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        end
         
     | 
| 
      
 68 
     | 
    
         
            +
                      end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,92 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "active_model/type/registry"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 6 
     | 
    
         
            +
              # :stopdoc:
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Type
         
     | 
| 
      
 8 
     | 
    
         
            +
                class Registry < ActiveModel::Type::Registry
         
     | 
| 
      
 9 
     | 
    
         
            +
                  def add_modifier(options, klass)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    registrations << DecorationRegistration.new(options, klass)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  private
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    def registration_klass
         
     | 
| 
      
 16 
     | 
    
         
            +
                      Registration
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    def find_registration(symbol, *args)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      registrations
         
     | 
| 
      
 21 
     | 
    
         
            +
                        .select { |registration| registration.matches?(symbol, *args) }
         
     | 
| 
      
 22 
     | 
    
         
            +
                        .max
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                class Registration
         
     | 
| 
      
 27 
     | 
    
         
            +
                  def initialize(name, block, override: nil)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @name = name
         
     | 
| 
      
 29 
     | 
    
         
            +
                    @block = block
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @override = override
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def call(_registry, *args, **kwargs)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
         
     | 
| 
      
 35 
     | 
    
         
            +
                      block.call(*args, **kwargs)
         
     | 
| 
      
 36 
     | 
    
         
            +
                    else
         
     | 
| 
      
 37 
     | 
    
         
            +
                      block.call(*args)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def matches?(type_name, *args, **kwargs)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    type_name == name
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  def <=>(other)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    priority <=> other.priority
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  protected
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                    attr_reader :name, :block, :override
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    def priority
         
     | 
| 
      
 54 
     | 
    
         
            +
                      override ? 1 : 0
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                class DecorationRegistration < Registration
         
     | 
| 
      
 59 
     | 
    
         
            +
                  def initialize(options, klass)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @options = options
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @klass = klass
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                  def call(registry, *args, **kwargs)
         
     | 
| 
      
 65 
     | 
    
         
            +
                    subtype = registry.lookup(*args, **kwargs.except(*options.keys))
         
     | 
| 
      
 66 
     | 
    
         
            +
                    klass.new(subtype)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  def matches?(*args, **kwargs)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    matches_options?(**kwargs)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def priority
         
     | 
| 
      
 74 
     | 
    
         
            +
                    super | 4
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
                  private
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                    attr_reader :options, :klass
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                    def matches_options?(**kwargs)
         
     | 
| 
      
 82 
     | 
    
         
            +
                      options.all? do |key, value|
         
     | 
| 
      
 83 
     | 
    
         
            +
                        kwargs[key] == value
         
     | 
| 
      
 84 
     | 
    
         
            +
                      end
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
              class TypeConflictError < StandardError
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
              # :startdoc:
         
     | 
| 
      
 92 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,71 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Type
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Serialized < DelegateClass(ActiveModel::Type::Value) # :nodoc:
         
     | 
| 
      
 6 
     | 
    
         
            +
                  undef to_yaml if method_defined?(:to_yaml)
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  include ActiveModel::Type::Helpers::Mutable
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  attr_reader :subtype, :coder
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(subtype, coder)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @subtype = subtype
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @coder = coder
         
     | 
| 
      
 15 
     | 
    
         
            +
                    super(subtype)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def deserialize(value)
         
     | 
| 
      
 19 
     | 
    
         
            +
                    if default_value?(value)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      value
         
     | 
| 
      
 21 
     | 
    
         
            +
                    else
         
     | 
| 
      
 22 
     | 
    
         
            +
                      coder.load(super)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                  def serialize(value)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    return if value.nil?
         
     | 
| 
      
 28 
     | 
    
         
            +
                    unless default_value?(value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      super coder.dump(value)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  def inspect
         
     | 
| 
      
 34 
     | 
    
         
            +
                    Kernel.instance_method(:inspect).bind(self).call
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def changed_in_place?(raw_old_value, value)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    return false if value.nil?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    raw_new_value = encoded(value)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    raw_old_value.nil? != raw_new_value.nil? ||
         
     | 
| 
      
 41 
     | 
    
         
            +
                      subtype.changed_in_place?(raw_old_value, raw_new_value)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  def accessor
         
     | 
| 
      
 45 
     | 
    
         
            +
                    ActiveEntity::Store::IndifferentHashAccessor
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  def assert_valid_value(value)
         
     | 
| 
      
 49 
     | 
    
         
            +
                    if coder.respond_to?(:assert_valid_value)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      coder.assert_valid_value(value, action: "serialize")
         
     | 
| 
      
 51 
     | 
    
         
            +
                    end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  def force_equality?(value)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    coder.respond_to?(:object_class) && value.is_a?(coder.object_class)
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  private
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    def default_value?(value)
         
     | 
| 
      
 61 
     | 
    
         
            +
                      value == coder.load(nil)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    def encoded(value)
         
     | 
| 
      
 65 
     | 
    
         
            +
                      unless default_value?(value)
         
     | 
| 
      
 66 
     | 
    
         
            +
                        coder.dump(value)
         
     | 
| 
      
 67 
     | 
    
         
            +
                      end
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Type
         
     | 
| 
      
 5 
     | 
    
         
            +
                class Time < ActiveModel::Type::Time
         
     | 
| 
      
 6 
     | 
    
         
            +
                  include Internal::Timezone
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  class Value < DelegateClass(::Time) # :nodoc:
         
     | 
| 
      
 9 
     | 
    
         
            +
                  end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  def serialize(value)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    case value = super
         
     | 
| 
      
 13 
     | 
    
         
            +
                    when ::Time
         
     | 
| 
      
 14 
     | 
    
         
            +
                      Value.new(value)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    else
         
     | 
| 
      
 16 
     | 
    
         
            +
                      value
         
     | 
| 
      
 17 
     | 
    
         
            +
                    end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,62 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "concurrent/map"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Type
         
     | 
| 
      
 7 
     | 
    
         
            +
                class TypeMap # :nodoc:
         
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @mapping = {}
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @cache = Concurrent::Map.new do |h, key|
         
     | 
| 
      
 11 
     | 
    
         
            +
                      h.fetch_or_store(key, Concurrent::Map.new)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def lookup(lookup_key, *args)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    fetch(lookup_key, *args) { Type.default_value }
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def fetch(lookup_key, *args, &block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @cache[lookup_key].fetch_or_store(args) do
         
     | 
| 
      
 21 
     | 
    
         
            +
                      perform_fetch(lookup_key, *args, &block)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def register_type(key, value = nil, &block)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    raise ::ArgumentError unless value || block
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @cache.clear
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                    if block
         
     | 
| 
      
 30 
     | 
    
         
            +
                      @mapping[key] = block
         
     | 
| 
      
 31 
     | 
    
         
            +
                    else
         
     | 
| 
      
 32 
     | 
    
         
            +
                      @mapping[key] = proc { value }
         
     | 
| 
      
 33 
     | 
    
         
            +
                    end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def alias_type(key, target_key)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    register_type(key) do |sql_type, *args|
         
     | 
| 
      
 38 
     | 
    
         
            +
                      metadata = sql_type[/\(.*\)/, 0]
         
     | 
| 
      
 39 
     | 
    
         
            +
                      lookup("#{target_key}#{metadata}", *args)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def clear
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @mapping.clear
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  private
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                    def perform_fetch(lookup_key, *args)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      matching_pair = @mapping.reverse_each.detect do |key, _|
         
     | 
| 
      
 51 
     | 
    
         
            +
                        key === lookup_key
         
     | 
| 
      
 52 
     | 
    
         
            +
                      end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                      if matching_pair
         
     | 
| 
      
 55 
     | 
    
         
            +
                        matching_pair.last.call(lookup_key, *args)
         
     | 
| 
      
 56 
     | 
    
         
            +
                      else
         
     | 
| 
      
 57 
     | 
    
         
            +
                        yield lookup_key, *args
         
     | 
| 
      
 58 
     | 
    
         
            +
                      end
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,305 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module ActiveEntity
         
     | 
| 
      
 4 
     | 
    
         
            +
              # = Active Entity Autosave Association
         
     | 
| 
      
 5 
     | 
    
         
            +
              #
         
     | 
| 
      
 6 
     | 
    
         
            +
              # AutosaveAssociation is a module that takes care of automatically saving
         
     | 
| 
      
 7 
     | 
    
         
            +
              # associated records when their parent is saved. In addition to saving, it
         
     | 
| 
      
 8 
     | 
    
         
            +
              # also destroys any associated records that were marked for destruction.
         
     | 
| 
      
 9 
     | 
    
         
            +
              # (See #mark_for_destruction and #marked_for_destruction?).
         
     | 
| 
      
 10 
     | 
    
         
            +
              #
         
     | 
| 
      
 11 
     | 
    
         
            +
              # Saving of the parent, its associations, and the destruction of marked
         
     | 
| 
      
 12 
     | 
    
         
            +
              # associations, all happen inside a transaction. This should never leave the
         
     | 
| 
      
 13 
     | 
    
         
            +
              # database in an inconsistent state.
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              # If validations for any of the associations fail, their error messages will
         
     | 
| 
      
 16 
     | 
    
         
            +
              # be applied to the parent.
         
     | 
| 
      
 17 
     | 
    
         
            +
              #
         
     | 
| 
      
 18 
     | 
    
         
            +
              # Note that it also means that associations marked for destruction won't
         
     | 
| 
      
 19 
     | 
    
         
            +
              # be destroyed directly. They will however still be marked for destruction.
         
     | 
| 
      
 20 
     | 
    
         
            +
              #
         
     | 
| 
      
 21 
     | 
    
         
            +
              # Note that <tt>autosave: false</tt> is not same as not declaring <tt>:autosave</tt>.
         
     | 
| 
      
 22 
     | 
    
         
            +
              # When the <tt>:autosave</tt> option is not present then new association records are
         
     | 
| 
      
 23 
     | 
    
         
            +
              # saved but the updated association records are not saved.
         
     | 
| 
      
 24 
     | 
    
         
            +
              #
         
     | 
| 
      
 25 
     | 
    
         
            +
              # == Validation
         
     | 
| 
      
 26 
     | 
    
         
            +
              #
         
     | 
| 
      
 27 
     | 
    
         
            +
              # Child records are validated unless <tt>:validate</tt> is +false+.
         
     | 
| 
      
 28 
     | 
    
         
            +
              #
         
     | 
| 
      
 29 
     | 
    
         
            +
              # == Callbacks
         
     | 
| 
      
 30 
     | 
    
         
            +
              #
         
     | 
| 
      
 31 
     | 
    
         
            +
              # Association with autosave option defines several callbacks on your
         
     | 
| 
      
 32 
     | 
    
         
            +
              # model (before_save, after_create, after_update). Please note that
         
     | 
| 
      
 33 
     | 
    
         
            +
              # callbacks are executed in the order they were defined in
         
     | 
| 
      
 34 
     | 
    
         
            +
              # model. You should avoid modifying the association content, before
         
     | 
| 
      
 35 
     | 
    
         
            +
              # autosave callbacks are executed. Placing your callbacks after
         
     | 
| 
      
 36 
     | 
    
         
            +
              # associations is usually a good practice.
         
     | 
| 
      
 37 
     | 
    
         
            +
              #
         
     | 
| 
      
 38 
     | 
    
         
            +
              # === One-to-one Example
         
     | 
| 
      
 39 
     | 
    
         
            +
              #
         
     | 
| 
      
 40 
     | 
    
         
            +
              #   class Post < ActiveEntity::Base
         
     | 
| 
      
 41 
     | 
    
         
            +
              #     has_one :author, autosave: true
         
     | 
| 
      
 42 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 43 
     | 
    
         
            +
              #
         
     | 
| 
      
 44 
     | 
    
         
            +
              # Saving changes to the parent and its associated model can now be performed
         
     | 
| 
      
 45 
     | 
    
         
            +
              # automatically _and_ atomically:
         
     | 
| 
      
 46 
     | 
    
         
            +
              #
         
     | 
| 
      
 47 
     | 
    
         
            +
              #   post = Post.find(1)
         
     | 
| 
      
 48 
     | 
    
         
            +
              #   post.title       # => "The current global position of migrating ducks"
         
     | 
| 
      
 49 
     | 
    
         
            +
              #   post.author.name # => "alloy"
         
     | 
| 
      
 50 
     | 
    
         
            +
              #
         
     | 
| 
      
 51 
     | 
    
         
            +
              #   post.title = "On the migration of ducks"
         
     | 
| 
      
 52 
     | 
    
         
            +
              #   post.author.name = "Eloy Duran"
         
     | 
| 
      
 53 
     | 
    
         
            +
              #
         
     | 
| 
      
 54 
     | 
    
         
            +
              #   post.save
         
     | 
| 
      
 55 
     | 
    
         
            +
              #   post.reload
         
     | 
| 
      
 56 
     | 
    
         
            +
              #   post.title       # => "On the migration of ducks"
         
     | 
| 
      
 57 
     | 
    
         
            +
              #   post.author.name # => "Eloy Duran"
         
     | 
| 
      
 58 
     | 
    
         
            +
              #
         
     | 
| 
      
 59 
     | 
    
         
            +
              # Destroying an associated model, as part of the parent's save action, is as
         
     | 
| 
      
 60 
     | 
    
         
            +
              # simple as marking it for destruction:
         
     | 
| 
      
 61 
     | 
    
         
            +
              #
         
     | 
| 
      
 62 
     | 
    
         
            +
              #   post.author.mark_for_destruction
         
     | 
| 
      
 63 
     | 
    
         
            +
              #   post.author.marked_for_destruction? # => true
         
     | 
| 
      
 64 
     | 
    
         
            +
              #
         
     | 
| 
      
 65 
     | 
    
         
            +
              # Note that the model is _not_ yet removed from the database:
         
     | 
| 
      
 66 
     | 
    
         
            +
              #
         
     | 
| 
      
 67 
     | 
    
         
            +
              #   id = post.author.id
         
     | 
| 
      
 68 
     | 
    
         
            +
              #   Author.find_by(id: id).nil? # => false
         
     | 
| 
      
 69 
     | 
    
         
            +
              #
         
     | 
| 
      
 70 
     | 
    
         
            +
              #   post.save
         
     | 
| 
      
 71 
     | 
    
         
            +
              #   post.reload.author # => nil
         
     | 
| 
      
 72 
     | 
    
         
            +
              #
         
     | 
| 
      
 73 
     | 
    
         
            +
              # Now it _is_ removed from the database:
         
     | 
| 
      
 74 
     | 
    
         
            +
              #
         
     | 
| 
      
 75 
     | 
    
         
            +
              #   Author.find_by(id: id).nil? # => true
         
     | 
| 
      
 76 
     | 
    
         
            +
              #
         
     | 
| 
      
 77 
     | 
    
         
            +
              # === One-to-many Example
         
     | 
| 
      
 78 
     | 
    
         
            +
              #
         
     | 
| 
      
 79 
     | 
    
         
            +
              # When <tt>:autosave</tt> is not declared new children are saved when their parent is saved:
         
     | 
| 
      
 80 
     | 
    
         
            +
              #
         
     | 
| 
      
 81 
     | 
    
         
            +
              #   class Post < ActiveEntity::Base
         
     | 
| 
      
 82 
     | 
    
         
            +
              #     has_many :comments # :autosave option is not declared
         
     | 
| 
      
 83 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 84 
     | 
    
         
            +
              #
         
     | 
| 
      
 85 
     | 
    
         
            +
              #   post = Post.new(title: 'ruby rocks')
         
     | 
| 
      
 86 
     | 
    
         
            +
              #   post.comments.build(body: 'hello world')
         
     | 
| 
      
 87 
     | 
    
         
            +
              #   post.save # => saves both post and comment
         
     | 
| 
      
 88 
     | 
    
         
            +
              #
         
     | 
| 
      
 89 
     | 
    
         
            +
              #   post = Post.create(title: 'ruby rocks')
         
     | 
| 
      
 90 
     | 
    
         
            +
              #   post.comments.build(body: 'hello world')
         
     | 
| 
      
 91 
     | 
    
         
            +
              #   post.save # => saves both post and comment
         
     | 
| 
      
 92 
     | 
    
         
            +
              #
         
     | 
| 
      
 93 
     | 
    
         
            +
              #   post = Post.create(title: 'ruby rocks')
         
     | 
| 
      
 94 
     | 
    
         
            +
              #   post.comments.create(body: 'hello world')
         
     | 
| 
      
 95 
     | 
    
         
            +
              #   post.save # => saves both post and comment
         
     | 
| 
      
 96 
     | 
    
         
            +
              #
         
     | 
| 
      
 97 
     | 
    
         
            +
              # When <tt>:autosave</tt> is true all children are saved, no matter whether they
         
     | 
| 
      
 98 
     | 
    
         
            +
              # are new records or not:
         
     | 
| 
      
 99 
     | 
    
         
            +
              #
         
     | 
| 
      
 100 
     | 
    
         
            +
              #   class Post < ActiveEntity::Base
         
     | 
| 
      
 101 
     | 
    
         
            +
              #     has_many :comments, autosave: true
         
     | 
| 
      
 102 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 103 
     | 
    
         
            +
              #
         
     | 
| 
      
 104 
     | 
    
         
            +
              #   post = Post.create(title: 'ruby rocks')
         
     | 
| 
      
 105 
     | 
    
         
            +
              #   post.comments.create(body: 'hello world')
         
     | 
| 
      
 106 
     | 
    
         
            +
              #   post.comments[0].body = 'hi everyone'
         
     | 
| 
      
 107 
     | 
    
         
            +
              #   post.comments.build(body: "good morning.")
         
     | 
| 
      
 108 
     | 
    
         
            +
              #   post.title += "!"
         
     | 
| 
      
 109 
     | 
    
         
            +
              #   post.save # => saves both post and comments.
         
     | 
| 
      
 110 
     | 
    
         
            +
              #
         
     | 
| 
      
 111 
     | 
    
         
            +
              # Destroying one of the associated models as part of the parent's save action
         
     | 
| 
      
 112 
     | 
    
         
            +
              # is as simple as marking it for destruction:
         
     | 
| 
      
 113 
     | 
    
         
            +
              #
         
     | 
| 
      
 114 
     | 
    
         
            +
              #   post.comments # => [#<Comment id: 1, ...>, #<Comment id: 2, ...]>
         
     | 
| 
      
 115 
     | 
    
         
            +
              #   post.comments[1].mark_for_destruction
         
     | 
| 
      
 116 
     | 
    
         
            +
              #   post.comments[1].marked_for_destruction? # => true
         
     | 
| 
      
 117 
     | 
    
         
            +
              #   post.comments.length # => 2
         
     | 
| 
      
 118 
     | 
    
         
            +
              #
         
     | 
| 
      
 119 
     | 
    
         
            +
              # Note that the model is _not_ yet removed from the database:
         
     | 
| 
      
 120 
     | 
    
         
            +
              #
         
     | 
| 
      
 121 
     | 
    
         
            +
              #   id = post.comments.last.id
         
     | 
| 
      
 122 
     | 
    
         
            +
              #   Comment.find_by(id: id).nil? # => false
         
     | 
| 
      
 123 
     | 
    
         
            +
              #
         
     | 
| 
      
 124 
     | 
    
         
            +
              #   post.save
         
     | 
| 
      
 125 
     | 
    
         
            +
              #   post.reload.comments.length # => 1
         
     | 
| 
      
 126 
     | 
    
         
            +
              #
         
     | 
| 
      
 127 
     | 
    
         
            +
              # Now it _is_ removed from the database:
         
     | 
| 
      
 128 
     | 
    
         
            +
              #
         
     | 
| 
      
 129 
     | 
    
         
            +
              #   Comment.find_by(id: id).nil? # => true
         
     | 
| 
      
 130 
     | 
    
         
            +
              module ValidateEmbeddedAssociation
         
     | 
| 
      
 131 
     | 
    
         
            +
                extend ActiveSupport::Concern
         
     | 
| 
      
 132 
     | 
    
         
            +
             
     | 
| 
      
 133 
     | 
    
         
            +
                module AssociationBuilderExtension #:nodoc:
         
     | 
| 
      
 134 
     | 
    
         
            +
                  def self.build(model, reflection)
         
     | 
| 
      
 135 
     | 
    
         
            +
                    model.send(:add_embedded_associations_validation_callbacks, reflection)
         
     | 
| 
      
 136 
     | 
    
         
            +
                  end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                  def self.valid_options
         
     | 
| 
      
 139 
     | 
    
         
            +
                    []
         
     | 
| 
      
 140 
     | 
    
         
            +
                  end
         
     | 
| 
      
 141 
     | 
    
         
            +
                end
         
     | 
| 
      
 142 
     | 
    
         
            +
             
     | 
| 
      
 143 
     | 
    
         
            +
                included do
         
     | 
| 
      
 144 
     | 
    
         
            +
                  Associations::Embedded::Builder::Association.extensions << AssociationBuilderExtension
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                  unless respond_to?(:index_nested_attribute_errors)
         
     | 
| 
      
 147 
     | 
    
         
            +
                    mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false
         
     | 
| 
      
 148 
     | 
    
         
            +
                  end
         
     | 
| 
      
 149 
     | 
    
         
            +
                end
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                module ClassMethods # :nodoc:
         
     | 
| 
      
 152 
     | 
    
         
            +
                  private
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                    def define_non_cyclic_method(name, &block)
         
     | 
| 
      
 155 
     | 
    
         
            +
                      return if instance_methods(false).include?(name)
         
     | 
| 
      
 156 
     | 
    
         
            +
                      define_method(name) do |*args|
         
     | 
| 
      
 157 
     | 
    
         
            +
                        result = true; @_already_called ||= {}
         
     | 
| 
      
 158 
     | 
    
         
            +
                        # Loop prevention for validation of associations
         
     | 
| 
      
 159 
     | 
    
         
            +
                        unless @_already_called[name]
         
     | 
| 
      
 160 
     | 
    
         
            +
                          begin
         
     | 
| 
      
 161 
     | 
    
         
            +
                            @_already_called[name] = true
         
     | 
| 
      
 162 
     | 
    
         
            +
                            result = instance_eval(&block)
         
     | 
| 
      
 163 
     | 
    
         
            +
                          ensure
         
     | 
| 
      
 164 
     | 
    
         
            +
                            @_already_called[name] = false
         
     | 
| 
      
 165 
     | 
    
         
            +
                          end
         
     | 
| 
      
 166 
     | 
    
         
            +
                        end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
      
 168 
     | 
    
         
            +
                        result
         
     | 
| 
      
 169 
     | 
    
         
            +
                      end
         
     | 
| 
      
 170 
     | 
    
         
            +
                    end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                    # Adds validation and save callbacks for the association as specified by
         
     | 
| 
      
 173 
     | 
    
         
            +
                    # the +reflection+.
         
     | 
| 
      
 174 
     | 
    
         
            +
                    #
         
     | 
| 
      
 175 
     | 
    
         
            +
                    # For performance reasons, we don't check whether to validate at runtime.
         
     | 
| 
      
 176 
     | 
    
         
            +
                    # However the validation and callback methods are lazy and those methods
         
     | 
| 
      
 177 
     | 
    
         
            +
                    # get created when they are invoked for the very first time. However,
         
     | 
| 
      
 178 
     | 
    
         
            +
                    # this can change, for instance, when using nested attributes, which is
         
     | 
| 
      
 179 
     | 
    
         
            +
                    # called _after_ the association has been defined. Since we don't want
         
     | 
| 
      
 180 
     | 
    
         
            +
                    # the callbacks to get defined multiple times, there are guards that
         
     | 
| 
      
 181 
     | 
    
         
            +
                    # check if the save or validation methods have already been defined
         
     | 
| 
      
 182 
     | 
    
         
            +
                    # before actually defining them.
         
     | 
| 
      
 183 
     | 
    
         
            +
                    def add_embedded_associations_validation_callbacks(reflection)
         
     | 
| 
      
 184 
     | 
    
         
            +
                      define_embedded_associations_validation_callbacks(reflection)
         
     | 
| 
      
 185 
     | 
    
         
            +
                    end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                    def define_embedded_associations_validation_callbacks(reflection)
         
     | 
| 
      
 188 
     | 
    
         
            +
                      validation_method = :"validate_associated_records_for_#{reflection.name}"
         
     | 
| 
      
 189 
     | 
    
         
            +
                      if reflection.validate? && !method_defined?(validation_method)
         
     | 
| 
      
 190 
     | 
    
         
            +
                        if reflection.collection?
         
     | 
| 
      
 191 
     | 
    
         
            +
                          method = :validate_collection_association
         
     | 
| 
      
 192 
     | 
    
         
            +
                        else
         
     | 
| 
      
 193 
     | 
    
         
            +
                          method = :validate_single_association
         
     | 
| 
      
 194 
     | 
    
         
            +
                        end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
                        define_non_cyclic_method(validation_method) { send(method, reflection) }
         
     | 
| 
      
 197 
     | 
    
         
            +
                        validate validation_method
         
     | 
| 
      
 198 
     | 
    
         
            +
                        after_validation :_ensure_no_duplicate_errors
         
     | 
| 
      
 199 
     | 
    
         
            +
                      end
         
     | 
| 
      
 200 
     | 
    
         
            +
                    end
         
     | 
| 
      
 201 
     | 
    
         
            +
                end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                # Marks this record to be destroyed as part of the parent's save transaction.
         
     | 
| 
      
 204 
     | 
    
         
            +
                # This does _not_ actually destroy the record instantly, rather child record will be destroyed
         
     | 
| 
      
 205 
     | 
    
         
            +
                # when <tt>parent.save</tt> is called.
         
     | 
| 
      
 206 
     | 
    
         
            +
                #
         
     | 
| 
      
 207 
     | 
    
         
            +
                # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
         
     | 
| 
      
 208 
     | 
    
         
            +
                def mark_for_destruction
         
     | 
| 
      
 209 
     | 
    
         
            +
                  @marked_for_destruction = true
         
     | 
| 
      
 210 
     | 
    
         
            +
                end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                # Returns whether or not this record will be destroyed as part of the parent's save transaction.
         
     | 
| 
      
 213 
     | 
    
         
            +
                #
         
     | 
| 
      
 214 
     | 
    
         
            +
                # Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
         
     | 
| 
      
 215 
     | 
    
         
            +
                def marked_for_destruction?
         
     | 
| 
      
 216 
     | 
    
         
            +
                  @marked_for_destruction
         
     | 
| 
      
 217 
     | 
    
         
            +
                end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                # Records the association that is being destroyed and destroying this
         
     | 
| 
      
 220 
     | 
    
         
            +
                # record in the process.
         
     | 
| 
      
 221 
     | 
    
         
            +
                def destroyed_by_association=(reflection)
         
     | 
| 
      
 222 
     | 
    
         
            +
                  @destroyed_by_association = reflection
         
     | 
| 
      
 223 
     | 
    
         
            +
                end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                # Returns the association for the parent being destroyed.
         
     | 
| 
      
 226 
     | 
    
         
            +
                #
         
     | 
| 
      
 227 
     | 
    
         
            +
                # Used to avoid updating the counter cache unnecessarily.
         
     | 
| 
      
 228 
     | 
    
         
            +
                def destroyed_by_association
         
     | 
| 
      
 229 
     | 
    
         
            +
                  @destroyed_by_association
         
     | 
| 
      
 230 
     | 
    
         
            +
                end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
                private
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                  # Returns the record for an association collection that should be validated
         
     | 
| 
      
 235 
     | 
    
         
            +
                  # or saved. If +autosave+ is +false+ only new records will be returned,
         
     | 
| 
      
 236 
     | 
    
         
            +
                  # unless the parent is/was a new record itself.
         
     | 
| 
      
 237 
     | 
    
         
            +
                  def associated_records_to_validate(association)
         
     | 
| 
      
 238 
     | 
    
         
            +
                    association&.target
         
     | 
| 
      
 239 
     | 
    
         
            +
                  end
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                  # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
         
     | 
| 
      
 242 
     | 
    
         
            +
                  # turned on for the association.
         
     | 
| 
      
 243 
     | 
    
         
            +
                  def validate_single_association(reflection)
         
     | 
| 
      
 244 
     | 
    
         
            +
                    association = association_instance_get(reflection.name)
         
     | 
| 
      
 245 
     | 
    
         
            +
                    record      = association&.reader
         
     | 
| 
      
 246 
     | 
    
         
            +
                    association_valid?(reflection, record) if record
         
     | 
| 
      
 247 
     | 
    
         
            +
                  end
         
     | 
| 
      
 248 
     | 
    
         
            +
             
     | 
| 
      
 249 
     | 
    
         
            +
                  # Validate the associated records if <tt>:validate</tt> or
         
     | 
| 
      
 250 
     | 
    
         
            +
                  # <tt>:autosave</tt> is turned on for the association specified by
         
     | 
| 
      
 251 
     | 
    
         
            +
                  # +reflection+.
         
     | 
| 
      
 252 
     | 
    
         
            +
                  def validate_collection_association(reflection)
         
     | 
| 
      
 253 
     | 
    
         
            +
                    if association = association_instance_get(reflection.name)
         
     | 
| 
      
 254 
     | 
    
         
            +
                      if records = associated_records_to_validate(association)
         
     | 
| 
      
 255 
     | 
    
         
            +
                        records.each_with_index { |record, index| association_valid?(reflection, record, index) }
         
     | 
| 
      
 256 
     | 
    
         
            +
                      end
         
     | 
| 
      
 257 
     | 
    
         
            +
                    end
         
     | 
| 
      
 258 
     | 
    
         
            +
                  end
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
                  # Returns whether or not the association is valid and applies any errors to
         
     | 
| 
      
 261 
     | 
    
         
            +
                  # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
         
     | 
| 
      
 262 
     | 
    
         
            +
                  # enabled records if they're marked_for_destruction? or destroyed.
         
     | 
| 
      
 263 
     | 
    
         
            +
                  def association_valid?(reflection, record, index = nil)
         
     | 
| 
      
 264 
     | 
    
         
            +
                    return true if record.marked_for_destruction?
         
     | 
| 
      
 265 
     | 
    
         
            +
             
     | 
| 
      
 266 
     | 
    
         
            +
                    context = validation_context
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                    unless valid = record.valid?(context)
         
     | 
| 
      
 269 
     | 
    
         
            +
                      indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveEntity::Base.index_nested_attribute_errors)
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
      
 271 
     | 
    
         
            +
                      record.errors.each do |attribute, message|
         
     | 
| 
      
 272 
     | 
    
         
            +
                        attribute = normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
         
     | 
| 
      
 273 
     | 
    
         
            +
                        errors[attribute] << message
         
     | 
| 
      
 274 
     | 
    
         
            +
                        errors[attribute].uniq!
         
     | 
| 
      
 275 
     | 
    
         
            +
                      end
         
     | 
| 
      
 276 
     | 
    
         
            +
             
     | 
| 
      
 277 
     | 
    
         
            +
                      record.errors.details.each_key do |attribute|
         
     | 
| 
      
 278 
     | 
    
         
            +
                        reflection_attribute =
         
     | 
| 
      
 279 
     | 
    
         
            +
                          normalize_reflection_attribute(indexed_attribute, reflection, index, attribute).to_sym
         
     | 
| 
      
 280 
     | 
    
         
            +
             
     | 
| 
      
 281 
     | 
    
         
            +
                        record.errors.details[attribute].each do |error|
         
     | 
| 
      
 282 
     | 
    
         
            +
                          errors.details[reflection_attribute] << error
         
     | 
| 
      
 283 
     | 
    
         
            +
                          errors.details[reflection_attribute].uniq!
         
     | 
| 
      
 284 
     | 
    
         
            +
                        end
         
     | 
| 
      
 285 
     | 
    
         
            +
                      end
         
     | 
| 
      
 286 
     | 
    
         
            +
                    end
         
     | 
| 
      
 287 
     | 
    
         
            +
             
     | 
| 
      
 288 
     | 
    
         
            +
                    valid
         
     | 
| 
      
 289 
     | 
    
         
            +
                  end
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
      
 291 
     | 
    
         
            +
                  def normalize_reflection_attribute(indexed_attribute, reflection, index, attribute)
         
     | 
| 
      
 292 
     | 
    
         
            +
                    if indexed_attribute
         
     | 
| 
      
 293 
     | 
    
         
            +
                      "#{reflection.name}[#{index}].#{attribute}"
         
     | 
| 
      
 294 
     | 
    
         
            +
                    else
         
     | 
| 
      
 295 
     | 
    
         
            +
                      "#{reflection.name}.#{attribute}"
         
     | 
| 
      
 296 
     | 
    
         
            +
                    end
         
     | 
| 
      
 297 
     | 
    
         
            +
                  end
         
     | 
| 
      
 298 
     | 
    
         
            +
             
     | 
| 
      
 299 
     | 
    
         
            +
                  def _ensure_no_duplicate_errors
         
     | 
| 
      
 300 
     | 
    
         
            +
                    errors.messages.each_key do |attribute|
         
     | 
| 
      
 301 
     | 
    
         
            +
                      errors[attribute].uniq!
         
     | 
| 
      
 302 
     | 
    
         
            +
                    end
         
     | 
| 
      
 303 
     | 
    
         
            +
                  end
         
     | 
| 
      
 304 
     | 
    
         
            +
              end
         
     | 
| 
      
 305 
     | 
    
         
            +
            end
         
     |