activemodel 7.0.8.7 → 7.2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -263
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +27 -2
- data/lib/active_model/attribute_assignment.rb +4 -2
- data/lib/active_model/attribute_methods.rb +145 -85
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set.rb +10 -1
- data/lib/active_model/attributes.rb +78 -48
- data/lib/active_model/callbacks.rb +6 -6
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +4 -3
- data/lib/active_model/errors.rb +37 -6
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +34 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +62 -24
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +7 -1
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/numeric.rb +6 -1
- data/lib/active_model/type/helpers/time_value.rb +50 -13
- data/lib/active_model/type/helpers/timezone.rb +5 -1
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/registry.rb +2 -3
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +17 -1
- data/lib/active_model/type.rb +1 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +5 -5
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +16 -8
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +10 -8
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +4 -4
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +44 -9
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +12 -7
| @@ -11,17 +11,17 @@ module ActiveModel | |
| 11 11 | 
             
              #
         | 
| 12 12 | 
             
              #   user = User.first
         | 
| 13 13 | 
             
              #   user.pets.select(:id).first.user_id
         | 
| 14 | 
            -
              #   # => ActiveModel::MissingAttributeError: missing attribute | 
| 14 | 
            +
              #   # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
         | 
| 15 15 | 
             
              class MissingAttributeError < NoMethodError
         | 
| 16 16 | 
             
              end
         | 
| 17 17 |  | 
| 18 | 
            -
              #  | 
| 18 | 
            +
              # = Active \Model \Attribute \Methods
         | 
| 19 19 | 
             
              #
         | 
| 20 20 | 
             
              # Provides a way to add prefixes and suffixes to your methods as
         | 
| 21 | 
            -
              # well as handling the creation of  | 
| 21 | 
            +
              # well as handling the creation of ActiveRecord::Base - like
         | 
| 22 22 | 
             
              # class methods such as +table_name+.
         | 
| 23 23 | 
             
              #
         | 
| 24 | 
            -
              # The requirements to implement  | 
| 24 | 
            +
              # The requirements to implement +ActiveModel::AttributeMethods+ are to:
         | 
| 25 25 | 
             
              #
         | 
| 26 26 | 
             
              # * <tt>include ActiveModel::AttributeMethods</tt> in your class.
         | 
| 27 27 | 
             
              # * Call each of its methods you want to add, such as +attribute_method_suffix+
         | 
| @@ -66,11 +66,10 @@ module ActiveModel | |
| 66 66 |  | 
| 67 67 | 
             
                NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
         | 
| 68 68 | 
             
                CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
         | 
| 69 | 
            -
                FORWARD_PARAMETERS = "*args"
         | 
| 70 69 |  | 
| 71 70 | 
             
                included do
         | 
| 72 71 | 
             
                  class_attribute :attribute_aliases, instance_writer: false, default: {}
         | 
| 73 | 
            -
                  class_attribute : | 
| 72 | 
            +
                  class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
         | 
| 74 73 | 
             
                end
         | 
| 75 74 |  | 
| 76 75 | 
             
                module ClassMethods
         | 
| @@ -105,7 +104,7 @@ module ActiveModel | |
| 105 104 | 
             
                  #   person.clear_name
         | 
| 106 105 | 
             
                  #   person.name          # => nil
         | 
| 107 106 | 
             
                  def attribute_method_prefix(*prefixes, parameters: nil)
         | 
| 108 | 
            -
                    self. | 
| 107 | 
            +
                    self.attribute_method_patterns += prefixes.map! { |prefix| AttributeMethodPattern.new(prefix: prefix, parameters: parameters) }
         | 
| 109 108 | 
             
                    undefine_attribute_methods
         | 
| 110 109 | 
             
                  end
         | 
| 111 110 |  | 
| @@ -139,7 +138,7 @@ module ActiveModel | |
| 139 138 | 
             
                  #   person.name          # => "Bob"
         | 
| 140 139 | 
             
                  #   person.name_short?   # => true
         | 
| 141 140 | 
             
                  def attribute_method_suffix(*suffixes, parameters: nil)
         | 
| 142 | 
            -
                    self. | 
| 141 | 
            +
                    self.attribute_method_patterns += suffixes.map! { |suffix| AttributeMethodPattern.new(suffix: suffix, parameters: parameters) }
         | 
| 143 142 | 
             
                    undefine_attribute_methods
         | 
| 144 143 | 
             
                  end
         | 
| 145 144 |  | 
| @@ -174,7 +173,7 @@ module ActiveModel | |
| 174 173 | 
             
                  #   person.reset_name_to_default!
         | 
| 175 174 | 
             
                  #   person.name                         # => 'Default Name'
         | 
| 176 175 | 
             
                  def attribute_method_affix(*affixes)
         | 
| 177 | 
            -
                    self. | 
| 176 | 
            +
                    self.attribute_method_patterns += affixes.map! { |affix| AttributeMethodPattern.new(**affix) }
         | 
| 178 177 | 
             
                    undefine_attribute_methods
         | 
| 179 178 | 
             
                  end
         | 
| 180 179 |  | 
| @@ -202,38 +201,41 @@ module ActiveModel | |
| 202 201 | 
             
                  #   person.name_short?     # => true
         | 
| 203 202 | 
             
                  #   person.nickname_short? # => true
         | 
| 204 203 | 
             
                  def alias_attribute(new_name, old_name)
         | 
| 205 | 
            -
                     | 
| 206 | 
            -
                     | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
                        mangled_name = target_name
         | 
| 213 | 
            -
                        unless NAME_COMPILABLE_REGEXP.match?(target_name)
         | 
| 214 | 
            -
                          mangled_name = "__temp__#{target_name.unpack1("h*")}"
         | 
| 215 | 
            -
                        end
         | 
| 204 | 
            +
                    old_name = old_name.to_s
         | 
| 205 | 
            +
                    new_name = new_name.to_s
         | 
| 206 | 
            +
                    self.attribute_aliases = attribute_aliases.merge(new_name => old_name)
         | 
| 207 | 
            +
                    aliases_by_attribute_name[old_name] << new_name
         | 
| 208 | 
            +
                    eagerly_generate_alias_attribute_methods(new_name, old_name)
         | 
| 209 | 
            +
                  end
         | 
| 216 210 |  | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
             | 
| 226 | 
            -
             | 
| 227 | 
            -
             | 
| 228 | 
            -
                          batch <<
         | 
| 229 | 
            -
                            "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
         | 
| 230 | 
            -
                            body <<
         | 
| 231 | 
            -
                            "end"
         | 
| 232 | 
            -
                        end
         | 
| 211 | 
            +
                  def eagerly_generate_alias_attribute_methods(new_name, old_name) # :nodoc:
         | 
| 212 | 
            +
                    ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
         | 
| 213 | 
            +
                      generate_alias_attribute_methods(code_generator, new_name, old_name)
         | 
| 214 | 
            +
                    end
         | 
| 215 | 
            +
                  end
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                  def generate_alias_attribute_methods(code_generator, new_name, old_name)
         | 
| 218 | 
            +
                    ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner|
         | 
| 219 | 
            +
                      attribute_method_patterns.each do |pattern|
         | 
| 220 | 
            +
                        alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
         | 
| 233 221 | 
             
                      end
         | 
| 222 | 
            +
                      attribute_method_patterns_cache.clear
         | 
| 234 223 | 
             
                    end
         | 
| 235 224 | 
             
                  end
         | 
| 236 225 |  | 
| 226 | 
            +
                  def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
         | 
| 227 | 
            +
                    method_name = pattern.method_name(new_name).to_s
         | 
| 228 | 
            +
                    target_name = pattern.method_name(old_name).to_s
         | 
| 229 | 
            +
                    parameters = pattern.parameters
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                    mangled_name = build_mangled_name(target_name)
         | 
| 232 | 
            +
             | 
| 233 | 
            +
                    call_args = []
         | 
| 234 | 
            +
                    call_args << parameters if parameters
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                    define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute, as: method_name)
         | 
| 237 | 
            +
                  end
         | 
| 238 | 
            +
             | 
| 237 239 | 
             
                  # Is +new_name+ an alias?
         | 
| 238 240 | 
             
                  def attribute_alias?(new_name)
         | 
| 239 241 | 
             
                    attribute_aliases.key? new_name.to_s
         | 
| @@ -245,7 +247,7 @@ module ActiveModel | |
| 245 247 | 
             
                  end
         | 
| 246 248 |  | 
| 247 249 | 
             
                  # Declares the attributes that should be prefixed and suffixed by
         | 
| 248 | 
            -
                  #  | 
| 250 | 
            +
                  # +ActiveModel::AttributeMethods+.
         | 
| 249 251 | 
             
                  #
         | 
| 250 252 | 
             
                  # To use, pass attribute names (as strings or symbols). Be sure to declare
         | 
| 251 253 | 
             
                  # +define_attribute_methods+ after you define any prefix, suffix, or affix
         | 
| @@ -269,12 +271,17 @@ module ActiveModel | |
| 269 271 | 
             
                  #   end
         | 
| 270 272 | 
             
                  def define_attribute_methods(*attr_names)
         | 
| 271 273 | 
             
                    ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
         | 
| 272 | 
            -
                      attr_names.flatten.each  | 
| 274 | 
            +
                      attr_names.flatten.each do |attr_name|
         | 
| 275 | 
            +
                        define_attribute_method(attr_name, _owner: owner)
         | 
| 276 | 
            +
                        aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
         | 
| 277 | 
            +
                          generate_alias_attribute_methods owner, aliased_name, attr_name
         | 
| 278 | 
            +
                        end
         | 
| 279 | 
            +
                      end
         | 
| 273 280 | 
             
                    end
         | 
| 274 281 | 
             
                  end
         | 
| 275 282 |  | 
| 276 283 | 
             
                  # Declares an attribute that should be prefixed and suffixed by
         | 
| 277 | 
            -
                  #  | 
| 284 | 
            +
                  # +ActiveModel::AttributeMethods+.
         | 
| 278 285 | 
             
                  #
         | 
| 279 286 | 
             
                  # To use, pass an attribute name (as string or symbol). Be sure to declare
         | 
| 280 287 | 
             
                  # +define_attribute_method+ after you define any prefix, suffix or affix
         | 
| @@ -301,26 +308,46 @@ module ActiveModel | |
| 301 308 | 
             
                  #   person.name = 'Bob'
         | 
| 302 309 | 
             
                  #   person.name        # => "Bob"
         | 
| 303 310 | 
             
                  #   person.name_short? # => true
         | 
| 304 | 
            -
                  def define_attribute_method(attr_name, _owner: generated_attribute_methods)
         | 
| 311 | 
            +
                  def define_attribute_method(attr_name, _owner: generated_attribute_methods, as: attr_name)
         | 
| 305 312 | 
             
                    ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
         | 
| 306 | 
            -
                       | 
| 307 | 
            -
                         | 
| 313 | 
            +
                      attribute_method_patterns.each do |pattern|
         | 
| 314 | 
            +
                        define_attribute_method_pattern(pattern, attr_name, owner: owner, as: as)
         | 
| 315 | 
            +
                      end
         | 
| 316 | 
            +
                      attribute_method_patterns_cache.clear
         | 
| 317 | 
            +
                    end
         | 
| 318 | 
            +
                  end
         | 
| 308 319 |  | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 320 | 
            +
                  def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc:
         | 
| 321 | 
            +
                    canonical_method_name = pattern.method_name(attr_name)
         | 
| 322 | 
            +
                    public_method_name = pattern.method_name(as)
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                    # If defining a regular attribute method, we don't override methods that are explictly
         | 
| 325 | 
            +
                    # defined in parrent classes.
         | 
| 326 | 
            +
                    if instance_method_already_implemented?(public_method_name)
         | 
| 327 | 
            +
                      # However, for `alias_attribute`, we always define the method.
         | 
| 328 | 
            +
                      # We check for override second because `instance_method_already_implemented?`
         | 
| 329 | 
            +
                      # also check for dangerous methods.
         | 
| 330 | 
            +
                      return unless override
         | 
| 331 | 
            +
                    end
         | 
| 311 332 |  | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 333 | 
            +
                    generate_method = "define_method_#{pattern.proxy_target}"
         | 
| 334 | 
            +
             | 
| 335 | 
            +
                    if respond_to?(generate_method, true)
         | 
| 336 | 
            +
                      send(generate_method, attr_name.to_s, owner: owner, as: as)
         | 
| 337 | 
            +
                    else
         | 
| 338 | 
            +
                      define_proxy_call(
         | 
| 339 | 
            +
                        owner,
         | 
| 340 | 
            +
                        canonical_method_name,
         | 
| 341 | 
            +
                        pattern.proxy_target,
         | 
| 342 | 
            +
                        pattern.parameters,
         | 
| 343 | 
            +
                        attr_name.to_s,
         | 
| 344 | 
            +
                        namespace: :active_model_proxy,
         | 
| 345 | 
            +
                        as: public_method_name
         | 
| 346 | 
            +
                      )
         | 
| 320 347 | 
             
                    end
         | 
| 321 348 | 
             
                  end
         | 
| 322 349 |  | 
| 323 | 
            -
                  # Removes all the previously dynamically defined methods from the class.
         | 
| 350 | 
            +
                  # Removes all the previously dynamically defined methods from the class, including alias attribute methods.
         | 
| 324 351 | 
             
                  #
         | 
| 325 352 | 
             
                  #   class Person
         | 
| 326 353 | 
             
                  #     include ActiveModel::AttributeMethods
         | 
| @@ -328,6 +355,7 @@ module ActiveModel | |
| 328 355 | 
             
                  #     attr_accessor :name
         | 
| 329 356 | 
             
                  #     attribute_method_suffix '_short?'
         | 
| 330 357 | 
             
                  #     define_attribute_method :name
         | 
| 358 | 
            +
                  #     alias_attribute :first_name, :name
         | 
| 331 359 | 
             
                  #
         | 
| 332 360 | 
             
                  #     private
         | 
| 333 361 | 
             
                  #       def attribute_short?(attr)
         | 
| @@ -337,19 +365,38 @@ module ActiveModel | |
| 337 365 | 
             
                  #
         | 
| 338 366 | 
             
                  #   person = Person.new
         | 
| 339 367 | 
             
                  #   person.name = 'Bob'
         | 
| 368 | 
            +
                  #   person.first_name  # => "Bob"
         | 
| 340 369 | 
             
                  #   person.name_short? # => true
         | 
| 341 370 | 
             
                  #
         | 
| 342 371 | 
             
                  #   Person.undefine_attribute_methods
         | 
| 343 372 | 
             
                  #
         | 
| 344 373 | 
             
                  #   person.name_short? # => NoMethodError
         | 
| 374 | 
            +
                  #   person.first_name  # => NoMethodError
         | 
| 345 375 | 
             
                  def undefine_attribute_methods
         | 
| 346 376 | 
             
                    generated_attribute_methods.module_eval do
         | 
| 347 377 | 
             
                      undef_method(*instance_methods)
         | 
| 348 378 | 
             
                    end
         | 
| 349 | 
            -
                     | 
| 379 | 
            +
                    attribute_method_patterns_cache.clear
         | 
| 380 | 
            +
                  end
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                  def aliases_by_attribute_name # :nodoc:
         | 
| 383 | 
            +
                    @aliases_by_attribute_name ||= Hash.new { |h, k| h[k] = [] }
         | 
| 350 384 | 
             
                  end
         | 
| 351 385 |  | 
| 352 386 | 
             
                  private
         | 
| 387 | 
            +
                    def inherited(base) # :nodoc:
         | 
| 388 | 
            +
                      super
         | 
| 389 | 
            +
                      base.class_eval do
         | 
| 390 | 
            +
                        @attribute_method_patterns_cache = nil
         | 
| 391 | 
            +
                        @aliases_by_attribute_name = nil
         | 
| 392 | 
            +
                        @generated_attribute_methods = nil
         | 
| 393 | 
            +
                      end
         | 
| 394 | 
            +
                    end
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                    def resolve_attribute_name(name)
         | 
| 397 | 
            +
                      attribute_aliases.fetch(super, &:itself)
         | 
| 398 | 
            +
                    end
         | 
| 399 | 
            +
             | 
| 353 400 | 
             
                    def generated_attribute_methods
         | 
| 354 401 | 
             
                      @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
         | 
| 355 402 | 
             
                    end
         | 
| @@ -367,62 +414,77 @@ module ActiveModel | |
| 367 414 | 
             
                    # used to alleviate the GC, which ultimately also speeds up the app
         | 
| 368 415 | 
             
                    # significantly (in our case our test suite finishes 10% faster with
         | 
| 369 416 | 
             
                    # this cache).
         | 
| 370 | 
            -
                    def  | 
| 371 | 
            -
                      @ | 
| 417 | 
            +
                    def attribute_method_patterns_cache
         | 
| 418 | 
            +
                      @attribute_method_patterns_cache ||= Concurrent::Map.new(initial_capacity: 4)
         | 
| 372 419 | 
             
                    end
         | 
| 373 420 |  | 
| 374 | 
            -
                    def  | 
| 375 | 
            -
                       | 
| 376 | 
            -
                         | 
| 421 | 
            +
                    def attribute_method_patterns_matching(method_name)
         | 
| 422 | 
            +
                      attribute_method_patterns_cache.compute_if_absent(method_name) do
         | 
| 423 | 
            +
                        attribute_method_patterns.filter_map { |pattern| pattern.match(method_name) }
         | 
| 377 424 | 
             
                      end
         | 
| 378 425 | 
             
                    end
         | 
| 379 426 |  | 
| 380 427 | 
             
                    # Define a method `name` in `mod` that dispatches to `send`
         | 
| 381 428 | 
             
                    # using the given `extra` args. This falls back on `send`
         | 
| 382 429 | 
             
                    # if the called name cannot be compiled.
         | 
| 383 | 
            -
                    def define_proxy_call(code_generator, name,  | 
| 430 | 
            +
                    def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:, as: name)
         | 
| 431 | 
            +
                      mangled_name = build_mangled_name(name)
         | 
| 432 | 
            +
             | 
| 433 | 
            +
                      call_args.map!(&:inspect)
         | 
| 434 | 
            +
                      call_args << parameters if parameters
         | 
| 435 | 
            +
             | 
| 436 | 
            +
                      # We have to use a different namespace for every target method, because
         | 
| 437 | 
            +
                      # if someone defines an attribute that look like an attribute method we could clash, e.g.
         | 
| 438 | 
            +
                      #   attribute :title_was
         | 
| 439 | 
            +
                      #   attribute :title
         | 
| 440 | 
            +
                      namespace = :"#{namespace}_#{proxy_target}"
         | 
| 441 | 
            +
             | 
| 442 | 
            +
                      define_call(code_generator, name, proxy_target, mangled_name, parameters, call_args, namespace: namespace, as: as)
         | 
| 443 | 
            +
                    end
         | 
| 444 | 
            +
             | 
| 445 | 
            +
                    def build_mangled_name(name)
         | 
| 384 446 | 
             
                      mangled_name = name
         | 
| 447 | 
            +
             | 
| 385 448 | 
             
                      unless NAME_COMPILABLE_REGEXP.match?(name)
         | 
| 386 | 
            -
                        mangled_name = "__temp__#{name.unpack1("h*")}"
         | 
| 449 | 
            +
                        mangled_name = :"__temp__#{name.unpack1("h*")}"
         | 
| 387 450 | 
             
                      end
         | 
| 388 451 |  | 
| 389 | 
            -
                       | 
| 390 | 
            -
             | 
| 391 | 
            -
                        call_args << parameters if parameters
         | 
| 452 | 
            +
                      mangled_name
         | 
| 453 | 
            +
                    end
         | 
| 392 454 |  | 
| 393 | 
            -
             | 
| 394 | 
            -
             | 
| 455 | 
            +
                    def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:, as:)
         | 
| 456 | 
            +
                      code_generator.define_cached_method(mangled_name, as: as, namespace: namespace) do |batch|
         | 
| 457 | 
            +
                        body = if CALL_COMPILABLE_REGEXP.match?(target_name)
         | 
| 458 | 
            +
                          "self.#{target_name}(#{call_args.join(", ")})"
         | 
| 395 459 | 
             
                        else
         | 
| 396 | 
            -
                          call_args.unshift(":'#{ | 
| 460 | 
            +
                          call_args.unshift(":'#{target_name}'")
         | 
| 397 461 | 
             
                          "send(#{call_args.join(", ")})"
         | 
| 398 462 | 
             
                        end
         | 
| 399 463 |  | 
| 400 | 
            -
                        modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
         | 
| 401 | 
            -
             | 
| 402 464 | 
             
                        batch <<
         | 
| 403 | 
            -
                          " | 
| 465 | 
            +
                          "def #{mangled_name}(#{parameters || ''})" <<
         | 
| 404 466 | 
             
                          body <<
         | 
| 405 467 | 
             
                          "end"
         | 
| 406 468 | 
             
                      end
         | 
| 407 469 | 
             
                    end
         | 
| 408 470 |  | 
| 409 | 
            -
                    class  | 
| 410 | 
            -
                      attr_reader :prefix, :suffix, : | 
| 471 | 
            +
                    class AttributeMethodPattern # :nodoc:
         | 
| 472 | 
            +
                      attr_reader :prefix, :suffix, :proxy_target, :parameters
         | 
| 411 473 |  | 
| 412 | 
            -
                       | 
| 474 | 
            +
                      AttributeMethod = Struct.new(:proxy_target, :attr_name)
         | 
| 413 475 |  | 
| 414 476 | 
             
                      def initialize(prefix: "", suffix: "", parameters: nil)
         | 
| 415 477 | 
             
                        @prefix = prefix
         | 
| 416 478 | 
             
                        @suffix = suffix
         | 
| 417 | 
            -
                        @parameters = parameters.nil? ?  | 
| 479 | 
            +
                        @parameters = parameters.nil? ? "..." : parameters
         | 
| 418 480 | 
             
                        @regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
         | 
| 419 | 
            -
                        @ | 
| 481 | 
            +
                        @proxy_target = "#{@prefix}attribute#{@suffix}"
         | 
| 420 482 | 
             
                        @method_name = "#{prefix}%s#{suffix}"
         | 
| 421 483 | 
             
                      end
         | 
| 422 484 |  | 
| 423 485 | 
             
                      def match(method_name)
         | 
| 424 486 | 
             
                        if @regex =~ method_name
         | 
| 425 | 
            -
                           | 
| 487 | 
            +
                          AttributeMethod.new(proxy_target, $1)
         | 
| 426 488 | 
             
                        end
         | 
| 427 489 | 
             
                      end
         | 
| 428 490 |  | 
| @@ -442,24 +504,22 @@ module ActiveModel | |
| 442 504 | 
             
                # It's also possible to instantiate related objects, so a <tt>Client</tt>
         | 
| 443 505 | 
             
                # class belonging to the +clients+ table with a +master_id+ foreign key
         | 
| 444 506 | 
             
                # can instantiate master through <tt>Client#master</tt>.
         | 
| 445 | 
            -
                def method_missing(method,  | 
| 507 | 
            +
                def method_missing(method, ...)
         | 
| 446 508 | 
             
                  if respond_to_without_attributes?(method, true)
         | 
| 447 509 | 
             
                    super
         | 
| 448 510 | 
             
                  else
         | 
| 449 | 
            -
                    match = matched_attribute_method(method. | 
| 450 | 
            -
                    match ? attribute_missing(match,  | 
| 511 | 
            +
                    match = matched_attribute_method(method.name)
         | 
| 512 | 
            +
                    match ? attribute_missing(match, ...) : super
         | 
| 451 513 | 
             
                  end
         | 
| 452 514 | 
             
                end
         | 
| 453 | 
            -
                ruby2_keywords(:method_missing)
         | 
| 454 515 |  | 
| 455 516 | 
             
                # +attribute_missing+ is like +method_missing+, but for attributes. When
         | 
| 456 517 | 
             
                # +method_missing+ is called we check to see if there is a matching
         | 
| 457 518 | 
             
                # attribute method. If so, we tell +attribute_missing+ to dispatch the
         | 
| 458 519 | 
             
                # attribute. This method can be overloaded to customize the behavior.
         | 
| 459 | 
            -
                def attribute_missing(match,  | 
| 460 | 
            -
                  __send__(match. | 
| 520 | 
            +
                def attribute_missing(match, ...)
         | 
| 521 | 
            +
                  __send__(match.proxy_target, match.attr_name, ...)
         | 
| 461 522 | 
             
                end
         | 
| 462 | 
            -
                ruby2_keywords(:attribute_missing)
         | 
| 463 523 |  | 
| 464 524 | 
             
                # A +Person+ instance with a +name+ attribute can ask
         | 
| 465 525 | 
             
                # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
         | 
| @@ -485,12 +545,12 @@ module ActiveModel | |
| 485 545 | 
             
                  # Returns a struct representing the matching attribute method.
         | 
| 486 546 | 
             
                  # The struct's attributes are prefix, base and suffix.
         | 
| 487 547 | 
             
                  def matched_attribute_method(method_name)
         | 
| 488 | 
            -
                    matches = self.class.send(: | 
| 548 | 
            +
                    matches = self.class.send(:attribute_method_patterns_matching, method_name)
         | 
| 489 549 | 
             
                    matches.detect { |match| attribute_method?(match.attr_name) }
         | 
| 490 550 | 
             
                  end
         | 
| 491 551 |  | 
| 492 552 | 
             
                  def missing_attribute(attr_name, stack)
         | 
| 493 | 
            -
                    raise ActiveModel::MissingAttributeError, "missing attribute | 
| 553 | 
            +
                    raise ActiveModel::MissingAttributeError, "missing attribute '#{attr_name}' for #{self.class}", stack
         | 
| 494 554 | 
             
                  end
         | 
| 495 555 |  | 
| 496 556 | 
             
                  def _read_attribute(attr)
         | 
| @@ -0,0 +1,117 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "active_support/core_ext/class/subclasses"
         | 
| 4 | 
            +
            require "active_model/attribute_set"
         | 
| 5 | 
            +
            require "active_model/attribute/user_provided_default"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ActiveModel
         | 
| 8 | 
            +
              module AttributeRegistration # :nodoc:
         | 
| 9 | 
            +
                extend ActiveSupport::Concern
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                module ClassMethods # :nodoc:
         | 
| 12 | 
            +
                  def attribute(name, type = nil, default: (no_default = true), **options)
         | 
| 13 | 
            +
                    name = resolve_attribute_name(name)
         | 
| 14 | 
            +
                    type = resolve_type_name(type, **options) if type.is_a?(Symbol)
         | 
| 15 | 
            +
                    type = hook_attribute_type(name, type) if type
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    pending_attribute_modifications << PendingType.new(name, type) if type || no_default
         | 
| 18 | 
            +
                    pending_attribute_modifications << PendingDefault.new(name, default) unless no_default
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    reset_default_attributes
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def decorate_attributes(names = nil, &decorator) # :nodoc:
         | 
| 24 | 
            +
                    names = names&.map { |name| resolve_attribute_name(name) }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    pending_attribute_modifications << PendingDecorator.new(names, decorator)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    reset_default_attributes
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def _default_attributes # :nodoc:
         | 
| 32 | 
            +
                    @default_attributes ||= AttributeSet.new({}).tap do |attribute_set|
         | 
| 33 | 
            +
                      apply_pending_attribute_modifications(attribute_set)
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def attribute_types # :nodoc:
         | 
| 38 | 
            +
                    @attribute_types ||= _default_attributes.cast_types.tap do |hash|
         | 
| 39 | 
            +
                      hash.default = Type.default_value
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def type_for_attribute(attribute_name, &block)
         | 
| 44 | 
            +
                    attribute_name = resolve_attribute_name(attribute_name)
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    if block
         | 
| 47 | 
            +
                      attribute_types.fetch(attribute_name, &block)
         | 
| 48 | 
            +
                    else
         | 
| 49 | 
            +
                      attribute_types[attribute_name]
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  private
         | 
| 54 | 
            +
                    PendingType = Struct.new(:name, :type) do # :nodoc:
         | 
| 55 | 
            +
                      def apply_to(attribute_set)
         | 
| 56 | 
            +
                        attribute = attribute_set[name]
         | 
| 57 | 
            +
                        attribute_set[name] = attribute.with_type(type || attribute.type)
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    PendingDefault = Struct.new(:name, :default) do # :nodoc:
         | 
| 62 | 
            +
                      def apply_to(attribute_set)
         | 
| 63 | 
            +
                        attribute_set[name] = attribute_set[name].with_user_default(default)
         | 
| 64 | 
            +
                      end
         | 
| 65 | 
            +
                    end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    PendingDecorator = Struct.new(:names, :decorator) do # :nodoc:
         | 
| 68 | 
            +
                      def apply_to(attribute_set)
         | 
| 69 | 
            +
                        (names || attribute_set.keys).each do |name|
         | 
| 70 | 
            +
                          attribute = attribute_set[name]
         | 
| 71 | 
            +
                          type = decorator.call(name, attribute.type)
         | 
| 72 | 
            +
                          attribute_set[name] = attribute.with_type(type) if type
         | 
| 73 | 
            +
                        end
         | 
| 74 | 
            +
                      end
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    def pending_attribute_modifications
         | 
| 78 | 
            +
                      @pending_attribute_modifications ||= []
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    def apply_pending_attribute_modifications(attribute_set)
         | 
| 82 | 
            +
                      if superclass.respond_to?(:apply_pending_attribute_modifications, true)
         | 
| 83 | 
            +
                        superclass.send(:apply_pending_attribute_modifications, attribute_set)
         | 
| 84 | 
            +
                      end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                      pending_attribute_modifications.each do |modification|
         | 
| 87 | 
            +
                        modification.apply_to(attribute_set)
         | 
| 88 | 
            +
                      end
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    def reset_default_attributes
         | 
| 92 | 
            +
                      reset_default_attributes!
         | 
| 93 | 
            +
                      subclasses.each { |subclass| subclass.send(:reset_default_attributes) }
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                    def reset_default_attributes!
         | 
| 97 | 
            +
                      @default_attributes = nil
         | 
| 98 | 
            +
                      @attribute_types = nil
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                    def resolve_attribute_name(name)
         | 
| 102 | 
            +
                      name.to_s
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    def resolve_type_name(name, **options)
         | 
| 106 | 
            +
                      Type.lookup(name, **options)
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                    # Hook for other modules to override. The attribute type is passed
         | 
| 110 | 
            +
                    # through this method immediately after it is resolved, before any type
         | 
| 111 | 
            +
                    # decorations are applied.
         | 
| 112 | 
            +
                    def hook_attribute_type(attribute, type)
         | 
| 113 | 
            +
                      type
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                end
         | 
| 116 | 
            +
              end
         | 
| 117 | 
            +
            end
         | 
| @@ -21,6 +21,10 @@ module ActiveModel | |
| 21 21 | 
             
                  @attributes[name] = value
         | 
| 22 22 | 
             
                end
         | 
| 23 23 |  | 
| 24 | 
            +
                def cast_types
         | 
| 25 | 
            +
                  attributes.transform_values(&:type)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 24 28 | 
             
                def values_before_type_cast
         | 
| 25 29 | 
             
                  attributes.transform_values(&:value_before_type_cast)
         | 
| 26 30 | 
             
                end
         | 
| @@ -37,6 +41,7 @@ module ActiveModel | |
| 37 41 | 
             
                def key?(name)
         | 
| 38 42 | 
             
                  attributes.key?(name) && self[name].initialized?
         | 
| 39 43 | 
             
                end
         | 
| 44 | 
            +
                alias :include? :key?
         | 
| 40 45 |  | 
| 41 46 | 
             
                def keys
         | 
| 42 47 | 
             
                  attributes.each_key.select { |name| self[name].initialized? }
         | 
| @@ -94,8 +99,12 @@ module ActiveModel | |
| 94 99 | 
             
                  AttributeSet.new(new_attributes)
         | 
| 95 100 | 
             
                end
         | 
| 96 101 |  | 
| 102 | 
            +
                def reverse_merge!(target_attributes)
         | 
| 103 | 
            +
                  attributes.reverse_merge!(target_attributes.attributes) && self
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 97 106 | 
             
                def ==(other)
         | 
| 98 | 
            -
                  attributes == other.attributes
         | 
| 107 | 
            +
                  other.is_a?(AttributeSet) && attributes == other.send(:attributes)
         | 
| 99 108 | 
             
                end
         | 
| 100 109 |  | 
| 101 110 | 
             
                protected
         |