activemodel 5.1.7 → 5.2.0.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 +5 -5
- data/CHANGELOG.md +32 -93
- data/README.rdoc +1 -1
- data/lib/active_model.rb +6 -1
- data/lib/active_model/attribute.rb +243 -0
- data/lib/active_model/attribute/user_provided_default.rb +30 -0
- data/lib/active_model/attribute_assignment.rb +8 -5
- data/lib/active_model/attribute_methods.rb +12 -10
- data/lib/active_model/attribute_mutation_tracker.rb +116 -0
- data/lib/active_model/attribute_set.rb +113 -0
- data/lib/active_model/attribute_set/builder.rb +124 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_model/attributes.rb +108 -0
- data/lib/active_model/callbacks.rb +7 -2
- data/lib/active_model/conversion.rb +2 -0
- data/lib/active_model/dirty.rb +124 -57
- data/lib/active_model/errors.rb +32 -21
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +2 -0
- data/lib/active_model/model.rb +2 -0
- data/lib/active_model/naming.rb +5 -3
- data/lib/active_model/railtie.rb +2 -0
- data/lib/active_model/secure_password.rb +5 -3
- data/lib/active_model/serialization.rb +2 -0
- data/lib/active_model/serializers/json.rb +3 -2
- data/lib/active_model/translation.rb +2 -0
- data/lib/active_model/type.rb +6 -0
- data/lib/active_model/type/big_integer.rb +2 -0
- data/lib/active_model/type/binary.rb +2 -0
- data/lib/active_model/type/boolean.rb +2 -0
- data/lib/active_model/type/date.rb +2 -0
- data/lib/active_model/type/date_time.rb +6 -0
- data/lib/active_model/type/decimal.rb +2 -0
- data/lib/active_model/type/float.rb +2 -0
- data/lib/active_model/type/helpers.rb +2 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +6 -0
- data/lib/active_model/type/helpers/mutable.rb +2 -0
- data/lib/active_model/type/helpers/numeric.rb +2 -0
- data/lib/active_model/type/helpers/time_value.rb +2 -1
- data/lib/active_model/type/immutable_string.rb +2 -0
- data/lib/active_model/type/integer.rb +3 -1
- data/lib/active_model/type/registry.rb +2 -0
- data/lib/active_model/type/string.rb +2 -0
- data/lib/active_model/type/time.rb +8 -4
- data/lib/active_model/type/value.rb +3 -1
- data/lib/active_model/validations.rb +7 -3
- data/lib/active_model/validations/absence.rb +2 -0
- data/lib/active_model/validations/acceptance.rb +2 -0
- data/lib/active_model/validations/callbacks.rb +11 -13
- data/lib/active_model/validations/clusivity.rb +2 -0
- data/lib/active_model/validations/confirmation.rb +3 -1
- data/lib/active_model/validations/exclusion.rb +2 -0
- data/lib/active_model/validations/format.rb +1 -0
- data/lib/active_model/validations/helper_methods.rb +2 -0
- data/lib/active_model/validations/inclusion.rb +2 -0
- data/lib/active_model/validations/length.rb +10 -2
- data/lib/active_model/validations/numericality.rb +3 -1
- data/lib/active_model/validations/presence.rb +1 -0
- data/lib/active_model/validations/validates.rb +4 -3
- data/lib/active_model/validations/with.rb +2 -0
- data/lib/active_model/validator.rb +6 -4
- data/lib/active_model/version.rb +2 -0
- metadata +17 -9
| @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require "active_support/core_ext/hash/keys"
         | 
| 2 4 |  | 
| 3 5 | 
             
            module ActiveModel
         | 
| @@ -19,15 +21,15 @@ module ActiveModel | |
| 19 21 | 
             
                #   cat = Cat.new
         | 
| 20 22 | 
             
                #   cat.assign_attributes(name: "Gorby", status: "yawning")
         | 
| 21 23 | 
             
                #   cat.name # => 'Gorby'
         | 
| 22 | 
            -
                #   cat.status => 'yawning'
         | 
| 24 | 
            +
                #   cat.status # => 'yawning'
         | 
| 23 25 | 
             
                #   cat.assign_attributes(status: "sleeping")
         | 
| 24 26 | 
             
                #   cat.name # => 'Gorby'
         | 
| 25 | 
            -
                #   cat.status => 'sleeping'
         | 
| 27 | 
            +
                #   cat.status # => 'sleeping'
         | 
| 26 28 | 
             
                def assign_attributes(new_attributes)
         | 
| 27 29 | 
             
                  if !new_attributes.respond_to?(:stringify_keys)
         | 
| 28 30 | 
             
                    raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
         | 
| 29 31 | 
             
                  end
         | 
| 30 | 
            -
                  return if new_attributes. | 
| 32 | 
            +
                  return if new_attributes.empty?
         | 
| 31 33 |  | 
| 32 34 | 
             
                  attributes = new_attributes.stringify_keys
         | 
| 33 35 | 
             
                  _assign_attributes(sanitize_for_mass_assignment(attributes))
         | 
| @@ -42,8 +44,9 @@ module ActiveModel | |
| 42 44 | 
             
                  end
         | 
| 43 45 |  | 
| 44 46 | 
             
                  def _assign_attribute(k, v)
         | 
| 45 | 
            -
                     | 
| 46 | 
            -
             | 
| 47 | 
            +
                    setter = :"#{k}="
         | 
| 48 | 
            +
                    if respond_to?(setter)
         | 
| 49 | 
            +
                      public_send(setter, v)
         | 
| 47 50 | 
             
                    else
         | 
| 48 51 | 
             
                      raise UnknownAttributeError.new(self, k)
         | 
| 49 52 | 
             
                    end
         | 
| @@ -1,5 +1,6 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            require "concurrent/map"
         | 
| 2 | 
            -
            require "mutex_m"
         | 
| 3 4 |  | 
| 4 5 | 
             
            module ActiveModel
         | 
| 5 6 | 
             
              # Raised when an attribute is not defined.
         | 
| @@ -68,9 +69,8 @@ module ActiveModel | |
| 68 69 | 
             
                CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
         | 
| 69 70 |  | 
| 70 71 | 
             
                included do
         | 
| 71 | 
            -
                  class_attribute :attribute_aliases, : | 
| 72 | 
            -
                   | 
| 73 | 
            -
                  self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
         | 
| 72 | 
            +
                  class_attribute :attribute_aliases, instance_writer: false, default: {}
         | 
| 73 | 
            +
                  class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]
         | 
| 74 74 | 
             
                end
         | 
| 75 75 |  | 
| 76 76 | 
             
                module ClassMethods
         | 
| @@ -328,13 +328,11 @@ module ActiveModel | |
| 328 328 | 
             
                    attribute_method_matchers_cache.clear
         | 
| 329 329 | 
             
                  end
         | 
| 330 330 |  | 
| 331 | 
            -
                  def generated_attribute_methods #:nodoc:
         | 
| 332 | 
            -
                    @generated_attribute_methods ||= Module.new {
         | 
| 333 | 
            -
                      extend Mutex_m
         | 
| 334 | 
            -
                    }.tap { |mod| include mod }
         | 
| 335 | 
            -
                  end
         | 
| 336 | 
            -
             | 
| 337 331 | 
             
                  private
         | 
| 332 | 
            +
                    def generated_attribute_methods
         | 
| 333 | 
            +
                      @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
         | 
| 334 | 
            +
                    end
         | 
| 335 | 
            +
             | 
| 338 336 | 
             
                    def instance_method_already_implemented?(method_name)
         | 
| 339 337 | 
             
                      generated_attribute_methods.method_defined?(method_name)
         | 
| 340 338 | 
             
                    end
         | 
| @@ -472,5 +470,9 @@ module ActiveModel | |
| 472 470 | 
             
                  def missing_attribute(attr_name, stack)
         | 
| 473 471 | 
             
                    raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
         | 
| 474 472 | 
             
                  end
         | 
| 473 | 
            +
             | 
| 474 | 
            +
                  def _read_attribute(attr)
         | 
| 475 | 
            +
                    __send__(attr)
         | 
| 476 | 
            +
                  end
         | 
| 475 477 | 
             
              end
         | 
| 476 478 | 
             
            end
         | 
| @@ -0,0 +1,116 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_support/core_ext/hash/indifferent_access"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ActiveModel
         | 
| 6 | 
            +
              class AttributeMutationTracker # :nodoc:
         | 
| 7 | 
            +
                OPTION_NOT_GIVEN = Object.new
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def initialize(attributes)
         | 
| 10 | 
            +
                  @attributes = attributes
         | 
| 11 | 
            +
                  @forced_changes = Set.new
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def changed_values
         | 
| 15 | 
            +
                  attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
         | 
| 16 | 
            +
                    if changed?(attr_name)
         | 
| 17 | 
            +
                      result[attr_name] = attributes[attr_name].original_value
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def changes
         | 
| 23 | 
            +
                  attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
         | 
| 24 | 
            +
                    change = change_to_attribute(attr_name)
         | 
| 25 | 
            +
                    if change
         | 
| 26 | 
            +
                      result[attr_name] = change
         | 
| 27 | 
            +
                    end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def change_to_attribute(attr_name)
         | 
| 32 | 
            +
                  attr_name = attr_name.to_s
         | 
| 33 | 
            +
                  if changed?(attr_name)
         | 
| 34 | 
            +
                    [attributes[attr_name].original_value, attributes.fetch_value(attr_name)]
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def any_changes?
         | 
| 39 | 
            +
                  attr_names.any? { |attr| changed?(attr) }
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
         | 
| 43 | 
            +
                  attr_name = attr_name.to_s
         | 
| 44 | 
            +
                  forced_changes.include?(attr_name) ||
         | 
| 45 | 
            +
                    attributes[attr_name].changed? &&
         | 
| 46 | 
            +
                    (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) &&
         | 
| 47 | 
            +
                    (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def changed_in_place?(attr_name)
         | 
| 51 | 
            +
                  attributes[attr_name.to_s].changed_in_place?
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def forget_change(attr_name)
         | 
| 55 | 
            +
                  attr_name = attr_name.to_s
         | 
| 56 | 
            +
                  attributes[attr_name] = attributes[attr_name].forgetting_assignment
         | 
| 57 | 
            +
                  forced_changes.delete(attr_name)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def original_value(attr_name)
         | 
| 61 | 
            +
                  attributes[attr_name.to_s].original_value
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def force_change(attr_name)
         | 
| 65 | 
            +
                  forced_changes << attr_name.to_s
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # TODO Change this to private once we've dropped Ruby 2.2 support.
         | 
| 69 | 
            +
                # Workaround for Ruby 2.2 "private attribute?" warning.
         | 
| 70 | 
            +
                protected
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  attr_reader :attributes, :forced_changes
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                private
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def attr_names
         | 
| 77 | 
            +
                    attributes.keys
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
              end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
              class NullMutationTracker # :nodoc:
         | 
| 82 | 
            +
                include Singleton
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def changed_values(*)
         | 
| 85 | 
            +
                  {}
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def changes(*)
         | 
| 89 | 
            +
                  {}
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def change_to_attribute(attr_name)
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                def any_changes?(*)
         | 
| 96 | 
            +
                  false
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def changed?(*)
         | 
| 100 | 
            +
                  false
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def changed_in_place?(*)
         | 
| 104 | 
            +
                  false
         | 
| 105 | 
            +
                end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                def forget_change(*)
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def original_value(*)
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                def force_change(*)
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
              end
         | 
| 116 | 
            +
            end
         | 
| @@ -0,0 +1,113 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_model/attribute_set/builder"
         | 
| 4 | 
            +
            require "active_model/attribute_set/yaml_encoder"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module ActiveModel
         | 
| 7 | 
            +
              class AttributeSet # :nodoc:
         | 
| 8 | 
            +
                delegate :each_value, :fetch, to: :attributes
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(attributes)
         | 
| 11 | 
            +
                  @attributes = attributes
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def [](name)
         | 
| 15 | 
            +
                  attributes[name] || Attribute.null(name)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def []=(name, value)
         | 
| 19 | 
            +
                  attributes[name] = value
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def values_before_type_cast
         | 
| 23 | 
            +
                  attributes.transform_values(&:value_before_type_cast)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def to_hash
         | 
| 27 | 
            +
                  initialized_attributes.transform_values(&:value)
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                alias_method :to_h, :to_hash
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def key?(name)
         | 
| 32 | 
            +
                  attributes.key?(name) && self[name].initialized?
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def keys
         | 
| 36 | 
            +
                  attributes.each_key.select { |name| self[name].initialized? }
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                if defined?(JRUBY_VERSION)
         | 
| 40 | 
            +
                  # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
         | 
| 41 | 
            +
                  # https://github.com/jruby/jruby/pull/2562
         | 
| 42 | 
            +
                  def fetch_value(name, &block)
         | 
| 43 | 
            +
                    self[name].value(&block)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                else
         | 
| 46 | 
            +
                  def fetch_value(name)
         | 
| 47 | 
            +
                    self[name].value { |n| yield n if block_given? }
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def write_from_database(name, value)
         | 
| 52 | 
            +
                  attributes[name] = self[name].with_value_from_database(value)
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def write_from_user(name, value)
         | 
| 56 | 
            +
                  attributes[name] = self[name].with_value_from_user(value)
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                def write_cast_value(name, value)
         | 
| 60 | 
            +
                  attributes[name] = self[name].with_cast_value(value)
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                def freeze
         | 
| 64 | 
            +
                  @attributes.freeze
         | 
| 65 | 
            +
                  super
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def deep_dup
         | 
| 69 | 
            +
                  self.class.allocate.tap do |copy|
         | 
| 70 | 
            +
                    copy.instance_variable_set(:@attributes, attributes.deep_dup)
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def initialize_dup(_)
         | 
| 75 | 
            +
                  @attributes = attributes.dup
         | 
| 76 | 
            +
                  super
         | 
| 77 | 
            +
                end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                def initialize_clone(_)
         | 
| 80 | 
            +
                  @attributes = attributes.clone
         | 
| 81 | 
            +
                  super
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def reset(key)
         | 
| 85 | 
            +
                  if key?(key)
         | 
| 86 | 
            +
                    write_from_database(key, nil)
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def accessed
         | 
| 91 | 
            +
                  attributes.select { |_, attr| attr.has_been_read? }.keys
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                def map(&block)
         | 
| 95 | 
            +
                  new_attributes = attributes.transform_values(&block)
         | 
| 96 | 
            +
                  AttributeSet.new(new_attributes)
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                def ==(other)
         | 
| 100 | 
            +
                  attributes == other.attributes
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                protected
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                  attr_reader :attributes
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                private
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  def initialized_attributes
         | 
| 110 | 
            +
                    attributes.select { |_, attr| attr.initialized? }
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
            end
         | 
| @@ -0,0 +1,124 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_model/attribute"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module ActiveModel
         | 
| 6 | 
            +
              class AttributeSet # :nodoc:
         | 
| 7 | 
            +
                class Builder # :nodoc:
         | 
| 8 | 
            +
                  attr_reader :types, :always_initialized, :default
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(types, always_initialized = nil, &default)
         | 
| 11 | 
            +
                    @types = types
         | 
| 12 | 
            +
                    @always_initialized = always_initialized
         | 
| 13 | 
            +
                    @default = default
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def build_from_database(values = {}, additional_types = {})
         | 
| 17 | 
            +
                    if always_initialized && !values.key?(always_initialized)
         | 
| 18 | 
            +
                      values[always_initialized] = nil
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    attributes = LazyAttributeHash.new(types, values, additional_types, &default)
         | 
| 22 | 
            +
                    AttributeSet.new(attributes)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              class LazyAttributeHash # :nodoc:
         | 
| 28 | 
            +
                delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def initialize(types, values, additional_types, &default)
         | 
| 31 | 
            +
                  @types = types
         | 
| 32 | 
            +
                  @values = values
         | 
| 33 | 
            +
                  @additional_types = additional_types
         | 
| 34 | 
            +
                  @materialized = false
         | 
| 35 | 
            +
                  @delegate_hash = {}
         | 
| 36 | 
            +
                  @default = default || proc {}
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def key?(key)
         | 
| 40 | 
            +
                  delegate_hash.key?(key) || values.key?(key) || types.key?(key)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def [](key)
         | 
| 44 | 
            +
                  delegate_hash[key] || assign_default_value(key)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def []=(key, value)
         | 
| 48 | 
            +
                  if frozen?
         | 
| 49 | 
            +
                    raise RuntimeError, "Can't modify frozen hash"
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
                  delegate_hash[key] = value
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def deep_dup
         | 
| 55 | 
            +
                  dup.tap do |copy|
         | 
| 56 | 
            +
                    copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def initialize_dup(_)
         | 
| 61 | 
            +
                  @delegate_hash = Hash[delegate_hash]
         | 
| 62 | 
            +
                  super
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def select
         | 
| 66 | 
            +
                  keys = types.keys | values.keys | delegate_hash.keys
         | 
| 67 | 
            +
                  keys.each_with_object({}) do |key, hash|
         | 
| 68 | 
            +
                    attribute = self[key]
         | 
| 69 | 
            +
                    if yield(key, attribute)
         | 
| 70 | 
            +
                      hash[key] = attribute
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def ==(other)
         | 
| 76 | 
            +
                  if other.is_a?(LazyAttributeHash)
         | 
| 77 | 
            +
                    materialize == other.materialize
         | 
| 78 | 
            +
                  else
         | 
| 79 | 
            +
                    materialize == other
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def marshal_dump
         | 
| 84 | 
            +
                  materialize
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def marshal_load(delegate_hash)
         | 
| 88 | 
            +
                  @delegate_hash = delegate_hash
         | 
| 89 | 
            +
                  @types = {}
         | 
| 90 | 
            +
                  @values = {}
         | 
| 91 | 
            +
                  @additional_types = {}
         | 
| 92 | 
            +
                  @materialized = true
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                protected
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  attr_reader :types, :values, :additional_types, :delegate_hash, :default
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                  def materialize
         | 
| 100 | 
            +
                    unless @materialized
         | 
| 101 | 
            +
                      values.each_key { |key| self[key] }
         | 
| 102 | 
            +
                      types.each_key { |key| self[key] }
         | 
| 103 | 
            +
                      unless frozen?
         | 
| 104 | 
            +
                        @materialized = true
         | 
| 105 | 
            +
                      end
         | 
| 106 | 
            +
                    end
         | 
| 107 | 
            +
                    delegate_hash
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                private
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  def assign_default_value(name)
         | 
| 113 | 
            +
                    type = additional_types.fetch(name, types[name])
         | 
| 114 | 
            +
                    value_present = true
         | 
| 115 | 
            +
                    value = values.fetch(name) { value_present = false }
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                    if value_present
         | 
| 118 | 
            +
                      delegate_hash[name] = Attribute.from_database(name, value, type)
         | 
| 119 | 
            +
                    elsif types.key?(name)
         | 
| 120 | 
            +
                      delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type)
         | 
| 121 | 
            +
                    end
         | 
| 122 | 
            +
                  end
         | 
| 123 | 
            +
              end
         | 
| 124 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ActiveModel
         | 
| 4 | 
            +
              class AttributeSet
         | 
| 5 | 
            +
                # Attempts to do more intelligent YAML dumping of an
         | 
| 6 | 
            +
                # ActiveModel::AttributeSet to reduce the size of the resulting string
         | 
| 7 | 
            +
                class YAMLEncoder # :nodoc:
         | 
| 8 | 
            +
                  def initialize(default_types)
         | 
| 9 | 
            +
                    @default_types = default_types
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def encode(attribute_set, coder)
         | 
| 13 | 
            +
                    coder["concise_attributes"] = attribute_set.each_value.map do |attr|
         | 
| 14 | 
            +
                      if attr.type.equal?(default_types[attr.name])
         | 
| 15 | 
            +
                        attr.with_type(nil)
         | 
| 16 | 
            +
                      else
         | 
| 17 | 
            +
                        attr
         | 
| 18 | 
            +
                      end
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def decode(coder)
         | 
| 23 | 
            +
                    if coder["attributes"]
         | 
| 24 | 
            +
                      coder["attributes"]
         | 
| 25 | 
            +
                    else
         | 
| 26 | 
            +
                      attributes_hash = Hash[coder["concise_attributes"].map do |attr|
         | 
| 27 | 
            +
                        if attr.type.nil?
         | 
| 28 | 
            +
                          attr = attr.with_type(default_types[attr.name])
         | 
| 29 | 
            +
                        end
         | 
| 30 | 
            +
                        [attr.name, attr]
         | 
| 31 | 
            +
                      end]
         | 
| 32 | 
            +
                      AttributeSet.new(attributes_hash)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  protected
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    attr_reader :default_types
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         |