activemodel 7.1.6 → 7.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 +4 -4
- data/CHANGELOG.md +14 -306
- data/README.rdoc +9 -9
- data/lib/active_model/attribute.rb +1 -1
- data/lib/active_model/attribute_assignment.rb +3 -1
- data/lib/active_model/attribute_methods.rb +47 -76
- data/lib/active_model/attribute_registration.rb +62 -22
- data/lib/active_model/attributes.rb +16 -3
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/secure_password.rb +1 -1
- data/lib/active_model/type/helpers/time_value.rb +7 -1
- data/lib/active_model/type/registry.rb +2 -3
- data/lib/active_model/validations/callbacks.rb +1 -1
- data/lib/active_model/validations/comparison.rb +1 -1
- data/lib/active_model/validations/validates.rb +1 -1
- metadata +12 -9
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 70a0c28c415bc15bb939ec838af49318530a90d8f42546153450f4cc108c18c8
         | 
| 4 | 
            +
              data.tar.gz: 57a6406ae772d62fb5b90e3df60de2ecf3f64e3a8c810cea7f24694ad70d16ef
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 54191c069916b9400da52e84d6ba6d874570fc708a89069dd702d59db8fd37f143bc3aa0021d286d6ef9b970cbcfcf828e3111cb9b8c5f7c0502f84a88929a39
         | 
| 7 | 
            +
              data.tar.gz: 16565d50c780f4a2ca419f3e8fff5a2856db38340ae93dc1ad0412bd0dcdc6fbd44b6559b00150ed083891fc392aac204e273f6511819aa7f12b076172f9178c
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,316 +1,24 @@ | |
| 1 | 
            -
            ## Rails 7. | 
| 1 | 
            +
            ## Rails 7.2.0.beta1 (May 29, 2024) ##
         | 
| 2 2 |  | 
| 3 | 
            -
            *    | 
| 3 | 
            +
            *   Fix a bug where type casting of string to `Time` and `DateTime` doesn't
         | 
| 4 | 
            +
                calculate minus minute value in TZ offset correctly.
         | 
| 4 5 |  | 
| 6 | 
            +
                *Akira Matsuda*
         | 
| 5 7 |  | 
| 6 | 
            -
             | 
| 8 | 
            +
            *   Port the `type_for_attribute` method to Active Model. Classes that include
         | 
| 9 | 
            +
                `ActiveModel::Attributes` will now provide this method. This method behaves
         | 
| 10 | 
            +
                the same for Active Model as it does for Active Record.
         | 
| 7 11 |  | 
| 8 | 
            -
             | 
| 12 | 
            +
                  ```ruby
         | 
| 13 | 
            +
                  class MyModel
         | 
| 14 | 
            +
                    include ActiveModel::Attributes
         | 
| 9 15 |  | 
| 10 | 
            -
             | 
| 11 | 
            -
            ## Rails 7.1.5.1 (December 10, 2024) ##
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            *   No changes.
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
            ## Rails 7.1.5 (October 30, 2024) ##
         | 
| 17 | 
            -
             | 
| 18 | 
            -
            *   Fix regression in `alias_attribute` to work with user defined methods.
         | 
| 19 | 
            -
             | 
| 20 | 
            -
                `alias_attribute` would wrongly assume the attribute accessor was generated by Active Model.
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                ```ruby
         | 
| 23 | 
            -
                class Person
         | 
| 24 | 
            -
                  include ActiveModel::AttributeMethods
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                  define_attribute_methods :name
         | 
| 27 | 
            -
                  attr_accessor :name
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                  alias_attribute :full_name, :name
         | 
| 30 | 
            -
                end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                person.full_name # => NoMethodError: undefined method `attribute' for an instance of Person
         | 
| 33 | 
            -
                ```
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                *Jean Boussier*
         | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
            ## Rails 7.1.4.2 (October 23, 2024) ##
         | 
| 39 | 
            -
             | 
| 40 | 
            -
            *   No changes.
         | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
            ## Rails 7.1.4.1 (October 15, 2024) ##
         | 
| 44 | 
            -
             | 
| 45 | 
            -
            *   No changes.
         | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
            ## Rails 7.1.4 (August 22, 2024) ##
         | 
| 49 | 
            -
             | 
| 50 | 
            -
            *   No changes.
         | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
            ## Rails 7.1.3.4 (June 04, 2024) ##
         | 
| 54 | 
            -
             | 
| 55 | 
            -
            *   No changes.
         | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
            ## Rails 7.1.3.3 (May 16, 2024) ##
         | 
| 59 | 
            -
             | 
| 60 | 
            -
            *   No changes.
         | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
            ## Rails 7.1.3.2 (February 21, 2024) ##
         | 
| 64 | 
            -
             | 
| 65 | 
            -
            *   No changes.
         | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
            ## Rails 7.1.3.1 (February 21, 2024) ##
         | 
| 69 | 
            -
             | 
| 70 | 
            -
            *   No changes.
         | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
            ## Rails 7.1.3 (January 16, 2024) ##
         | 
| 74 | 
            -
             | 
| 75 | 
            -
            *   No changes.
         | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
            ## Rails 7.1.2 (November 10, 2023) ##
         | 
| 79 | 
            -
             | 
| 80 | 
            -
            *   Make `==(other)` method of AttributeSet safe.
         | 
| 81 | 
            -
             | 
| 82 | 
            -
                *Dmitry Pogrebnoy*
         | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
            ## Rails 7.1.1 (October 11, 2023) ##
         | 
| 86 | 
            -
             | 
| 87 | 
            -
            *   No changes.
         | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
            ## Rails 7.1.0 (October 05, 2023) ##
         | 
| 91 | 
            -
             | 
| 92 | 
            -
            *   No changes.
         | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
            ## Rails 7.1.0.rc2 (October 01, 2023) ##
         | 
| 96 | 
            -
             | 
| 97 | 
            -
            *   No changes.
         | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
            ## Rails 7.1.0.rc1 (September 27, 2023) ##
         | 
| 101 | 
            -
             | 
| 102 | 
            -
            *   Remove change in the typography of user facing error messages.
         | 
| 103 | 
            -
                For example, “can’t be blank” is again “can't be blank”.
         | 
| 104 | 
            -
             | 
| 105 | 
            -
                *Rafael Mendonça França*
         | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
            ## Rails 7.1.0.beta1 (September 13, 2023) ##
         | 
| 109 | 
            -
             | 
| 110 | 
            -
            *   Support composite identifiers in `to_key`
         | 
| 111 | 
            -
             | 
| 112 | 
            -
                `to_key` avoids wrapping `#id` value into an `Array` if `#id` already an array
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                *Nikita Vasilevsky*
         | 
| 115 | 
            -
             | 
| 116 | 
            -
            *   Add `ActiveModel::Conversion.param_delimiter` to configure delimiter being used in `to_param`
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                *Nikita Vasilevsky*
         | 
| 119 | 
            -
             | 
| 120 | 
            -
            *   `undefine_attribute_methods` undefines alias attribute methods along with attribute methods.
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                *Nikita Vasilevsky*
         | 
| 123 | 
            -
             | 
| 124 | 
            -
            *   Error.full_message now strips ":base" from the message.
         | 
| 125 | 
            -
             | 
| 126 | 
            -
                *zzak*
         | 
| 127 | 
            -
             | 
| 128 | 
            -
            *   Add a load hook for `ActiveModel::Model` (named `active_model`) to match the load hook for
         | 
| 129 | 
            -
                `ActiveRecord::Base` and allow for overriding aspects of the `ActiveModel::Model` class.
         | 
| 130 | 
            -
             | 
| 131 | 
            -
                *Lewis Buckley*
         | 
| 132 | 
            -
             | 
| 133 | 
            -
            *   Improve password length validation in ActiveModel::SecurePassword to consider byte size for BCrypt
         | 
| 134 | 
            -
                compatibility.
         | 
| 135 | 
            -
             | 
| 136 | 
            -
                The previous password length validation only considered the character count, which may not
         | 
| 137 | 
            -
                accurately reflect the 72-byte size limit imposed by BCrypt. This change updates the validation
         | 
| 138 | 
            -
                to consider both character count and byte size while keeping the character length validation in place.
         | 
| 139 | 
            -
             | 
| 140 | 
            -
                ```ruby
         | 
| 141 | 
            -
                user = User.new(password: "a" * 73)  # 73 characters
         | 
| 142 | 
            -
                user.valid? # => false
         | 
| 143 | 
            -
                user.errors[:password] # => ["is too long"]
         | 
| 144 | 
            -
             | 
| 145 | 
            -
             | 
| 146 | 
            -
                user = User.new(password: "あ" * 25)  # 25 characters, 75 bytes
         | 
| 147 | 
            -
                user.valid? # => false
         | 
| 148 | 
            -
                user.errors[:password] # => ["is too long"]
         | 
| 149 | 
            -
                ```
         | 
| 150 | 
            -
             | 
| 151 | 
            -
                *ChatGPT*, *Guillermo Iguaran*
         | 
| 152 | 
            -
             | 
| 153 | 
            -
            *   `has_secure_password` now generates an `#{attribute}_salt` method that returns the salt
         | 
| 154 | 
            -
                used to compute the password digest. The salt will change whenever the password is changed,
         | 
| 155 | 
            -
                so it can be used to create single-use password reset tokens with `generates_token_for`:
         | 
| 156 | 
            -
             | 
| 157 | 
            -
                ```ruby
         | 
| 158 | 
            -
                class User < ActiveRecord::Base
         | 
| 159 | 
            -
                  has_secure_password
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                  generates_token_for :password_reset, expires_in: 15.minutes do
         | 
| 162 | 
            -
                    password_salt&.last(10)
         | 
| 163 | 
            -
                  end
         | 
| 164 | 
            -
                end
         | 
| 165 | 
            -
                ```
         | 
| 166 | 
            -
             | 
| 167 | 
            -
                *Lázaro Nixon*
         | 
| 168 | 
            -
             | 
| 169 | 
            -
            *   Improve typography of user facing error messages. In English contractions,
         | 
| 170 | 
            -
                the Unicode APOSTROPHE (`U+0027`) is now RIGHT SINGLE QUOTATION MARK
         | 
| 171 | 
            -
                (`U+2019`). For example, "can't be blank" is now "can’t be blank".
         | 
| 172 | 
            -
             | 
| 173 | 
            -
                *Jon Dufresne*
         | 
| 174 | 
            -
             | 
| 175 | 
            -
            *   Add class to `ActiveModel::MissingAttributeError` error message.
         | 
| 176 | 
            -
             | 
| 177 | 
            -
                Show which class is missing the attribute in the error message:
         | 
| 178 | 
            -
             | 
| 179 | 
            -
                ```ruby
         | 
| 180 | 
            -
                user = User.first
         | 
| 181 | 
            -
                user.pets.select(:id).first.user_id
         | 
| 182 | 
            -
                # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
         | 
| 183 | 
            -
                ```
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                *Petrik de Heus*
         | 
| 186 | 
            -
             | 
| 187 | 
            -
            *   Raise `NoMethodError` in `ActiveModel::Type::Value#as_json` to avoid unpredictable
         | 
| 188 | 
            -
                results.
         | 
| 189 | 
            -
             | 
| 190 | 
            -
                *Vasiliy Ermolovich*
         | 
| 191 | 
            -
             | 
| 192 | 
            -
            *   Custom attribute types that inherit from Active Model built-in types and do
         | 
| 193 | 
            -
                not override the `serialize` method will now benefit from an optimization
         | 
| 194 | 
            -
                when serializing attribute values for the database.
         | 
| 195 | 
            -
             | 
| 196 | 
            -
                For example, with a custom type like the following:
         | 
| 197 | 
            -
             | 
| 198 | 
            -
                ```ruby
         | 
| 199 | 
            -
                class DowncasedString < ActiveModel::Type::String
         | 
| 200 | 
            -
                  def cast(value)
         | 
| 201 | 
            -
                    super&.downcase
         | 
| 16 | 
            +
                    attribute :my_attribute, :integer
         | 
| 202 17 | 
             
                  end
         | 
| 203 | 
            -
                end
         | 
| 204 | 
            -
             | 
| 205 | 
            -
                ActiveRecord::Type.register(:downcased_string, DowncasedString)
         | 
| 206 | 
            -
             | 
| 207 | 
            -
                class User < ActiveRecord::Base
         | 
| 208 | 
            -
                  attribute :email, :downcased_string
         | 
| 209 | 
            -
                end
         | 
| 210 | 
            -
             | 
| 211 | 
            -
                user = User.new(email: "FooBar@example.com")
         | 
| 212 | 
            -
                ```
         | 
| 213 18 |  | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 19 | 
            +
                  MyModel.type_for_attribute(:my_attribute) # => #<ActiveModel::Type::Integer ...>
         | 
| 20 | 
            +
                  ```
         | 
| 216 21 |  | 
| 217 22 | 
             
                *Jonathan Hefner*
         | 
| 218 23 |  | 
| 219 | 
            -
             | 
| 220 | 
            -
                `password_challenge` accessor and validation.
         | 
| 221 | 
            -
             | 
| 222 | 
            -
                A password challenge is a safeguard to verify that the current user is
         | 
| 223 | 
            -
                actually the password owner.  It can be used when changing sensitive model
         | 
| 224 | 
            -
                fields, such as the password itself.  It is different than a password
         | 
| 225 | 
            -
                confirmation, which is used to prevent password typos.
         | 
| 226 | 
            -
             | 
| 227 | 
            -
                When `password_challenge` is set, the validation checks that the value's
         | 
| 228 | 
            -
                digest matches the *currently persisted* `password_digest` (i.e.
         | 
| 229 | 
            -
                `password_digest_was`).
         | 
| 230 | 
            -
             | 
| 231 | 
            -
                This allows a password challenge to be done as part of a typical `update`
         | 
| 232 | 
            -
                call, just like a password confirmation.  It also allows a password
         | 
| 233 | 
            -
                challenge error to be handled in the same way as other validation errors.
         | 
| 234 | 
            -
             | 
| 235 | 
            -
                For example, in the controller, instead of:
         | 
| 236 | 
            -
             | 
| 237 | 
            -
                ```ruby
         | 
| 238 | 
            -
                password_params = params.require(:password).permit(
         | 
| 239 | 
            -
                  :password_challenge,
         | 
| 240 | 
            -
                  :password,
         | 
| 241 | 
            -
                  :password_confirmation,
         | 
| 242 | 
            -
                )
         | 
| 243 | 
            -
             | 
| 244 | 
            -
                password_challenge = password_params.delete(:password_challenge)
         | 
| 245 | 
            -
                @password_challenge_failed = !current_user.authenticate(password_challenge)
         | 
| 246 | 
            -
             | 
| 247 | 
            -
                if !@password_challenge_failed && current_user.update(password_params)
         | 
| 248 | 
            -
                  # ...
         | 
| 249 | 
            -
                end
         | 
| 250 | 
            -
                ```
         | 
| 251 | 
            -
             | 
| 252 | 
            -
                You can now write:
         | 
| 253 | 
            -
             | 
| 254 | 
            -
                ```ruby
         | 
| 255 | 
            -
                password_params = params.require(:password).permit(
         | 
| 256 | 
            -
                  :password_challenge,
         | 
| 257 | 
            -
                  :password,
         | 
| 258 | 
            -
                  :password_confirmation,
         | 
| 259 | 
            -
                ).with_defaults(password_challenge: "")
         | 
| 260 | 
            -
             | 
| 261 | 
            -
                if current_user.update(password_params)
         | 
| 262 | 
            -
                  # ...
         | 
| 263 | 
            -
                end
         | 
| 264 | 
            -
                ```
         | 
| 265 | 
            -
             | 
| 266 | 
            -
                And, in the view, instead of checking `@password_challenge_failed`, you can
         | 
| 267 | 
            -
                render an error for the `password_challenge` field just as you would for
         | 
| 268 | 
            -
                other form fields, including utilizing `config.action_view.field_error_proc`.
         | 
| 269 | 
            -
             | 
| 270 | 
            -
                *Jonathan Hefner*
         | 
| 271 | 
            -
             | 
| 272 | 
            -
            *   Support infinite ranges for `LengthValidator`s `:in`/`:within` options
         | 
| 273 | 
            -
             | 
| 274 | 
            -
                ```ruby
         | 
| 275 | 
            -
                validates_length_of :first_name, in: ..30
         | 
| 276 | 
            -
                ```
         | 
| 277 | 
            -
             | 
| 278 | 
            -
                *fatkodima*
         | 
| 279 | 
            -
             | 
| 280 | 
            -
            *   Add support for beginless ranges to inclusivity/exclusivity validators:
         | 
| 281 | 
            -
             | 
| 282 | 
            -
                ```ruby
         | 
| 283 | 
            -
                validates_inclusion_of :birth_date, in: -> { (..Date.today) }
         | 
| 284 | 
            -
                ```
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                ```ruby
         | 
| 287 | 
            -
                validates_exclusion_of :birth_date, in: -> { (..Date.today) }
         | 
| 288 | 
            -
                ```
         | 
| 289 | 
            -
             | 
| 290 | 
            -
                *Bo Jeanes*
         | 
| 291 | 
            -
             | 
| 292 | 
            -
            *   Make validators accept lambdas without record argument
         | 
| 293 | 
            -
             | 
| 294 | 
            -
                ```ruby
         | 
| 295 | 
            -
                # Before
         | 
| 296 | 
            -
                validates_comparison_of :birth_date, less_than_or_equal_to: ->(_record) { Date.today }
         | 
| 297 | 
            -
             | 
| 298 | 
            -
                # After
         | 
| 299 | 
            -
                validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
         | 
| 300 | 
            -
                ```
         | 
| 301 | 
            -
             | 
| 302 | 
            -
                *fatkodima*
         | 
| 303 | 
            -
             | 
| 304 | 
            -
            *   Fix casting long strings to `Date`, `Time` or `DateTime`
         | 
| 305 | 
            -
             | 
| 306 | 
            -
                *fatkodima*
         | 
| 307 | 
            -
             | 
| 308 | 
            -
            *   Use different cache namespace for proxy calls
         | 
| 309 | 
            -
             | 
| 310 | 
            -
                Models can currently have different attribute bodies for the same method
         | 
| 311 | 
            -
                names, leading to conflicts. Adding a new namespace `:active_model_proxy`
         | 
| 312 | 
            -
                fixes the issue.
         | 
| 313 | 
            -
             | 
| 314 | 
            -
                *Chris Salzberg*
         | 
| 315 | 
            -
             | 
| 316 | 
            -
            Please check [7-0-stable](https://github.com/rails/rails/blob/7-0-stable/activemodel/CHANGELOG.md) for previous changes.
         | 
| 24 | 
            +
            Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activemodel/CHANGELOG.md) for previous changes.
         | 
    
        data/README.rdoc
    CHANGED
    
    | @@ -56,7 +56,7 @@ behavior out of the box: | |
| 56 56 | 
             
                person.clear_name
         | 
| 57 57 | 
             
                person.clear_age
         | 
| 58 58 |  | 
| 59 | 
            -
              {Learn more}[ | 
| 59 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/AttributeMethods.html]
         | 
| 60 60 |  | 
| 61 61 | 
             
            * Callbacks for certain operations
         | 
| 62 62 |  | 
| @@ -74,7 +74,7 @@ behavior out of the box: | |
| 74 74 | 
             
              This generates +before_create+, +around_create+ and +after_create+
         | 
| 75 75 | 
             
              class methods that wrap your create method.
         | 
| 76 76 |  | 
| 77 | 
            -
              {Learn more}[ | 
| 77 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Callbacks.html]
         | 
| 78 78 |  | 
| 79 79 | 
             
            * Tracking value changes
         | 
| 80 80 |  | 
| @@ -110,7 +110,7 @@ behavior out of the box: | |
| 110 110 | 
             
                person.save
         | 
| 111 111 | 
             
                person.previous_changes # => {'name' => ['bob, 'robert']}
         | 
| 112 112 |  | 
| 113 | 
            -
              {Learn more}[ | 
| 113 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Dirty.html]
         | 
| 114 114 |  | 
| 115 115 | 
             
            * Adding +errors+ interface to objects
         | 
| 116 116 |  | 
| @@ -141,7 +141,7 @@ behavior out of the box: | |
| 141 141 | 
             
                person.errors.full_messages
         | 
| 142 142 | 
             
                # => ["Name cannot be nil"]
         | 
| 143 143 |  | 
| 144 | 
            -
              {Learn more}[ | 
| 144 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Errors.html]
         | 
| 145 145 |  | 
| 146 146 | 
             
            * Model name introspection
         | 
| 147 147 |  | 
| @@ -152,7 +152,7 @@ behavior out of the box: | |
| 152 152 | 
             
                NamedPerson.model_name.name   # => "NamedPerson"
         | 
| 153 153 | 
             
                NamedPerson.model_name.human  # => "Named person"
         | 
| 154 154 |  | 
| 155 | 
            -
              {Learn more}[ | 
| 155 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Naming.html]
         | 
| 156 156 |  | 
| 157 157 | 
             
            * Making objects serializable
         | 
| 158 158 |  | 
| @@ -179,7 +179,7 @@ behavior out of the box: | |
| 179 179 | 
             
                s = SerialPerson.new
         | 
| 180 180 | 
             
                s.to_json             # => "{\"name\":null}"
         | 
| 181 181 |  | 
| 182 | 
            -
              {Learn more}[ | 
| 182 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Serialization.html]
         | 
| 183 183 |  | 
| 184 184 | 
             
            * Internationalization (i18n) support
         | 
| 185 185 |  | 
| @@ -190,7 +190,7 @@ behavior out of the box: | |
| 190 190 | 
             
                Person.human_attribute_name('my_attribute')
         | 
| 191 191 | 
             
                # => "My attribute"
         | 
| 192 192 |  | 
| 193 | 
            -
              {Learn more}[ | 
| 193 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Translation.html]
         | 
| 194 194 |  | 
| 195 195 | 
             
            * Validation support
         | 
| 196 196 |  | 
| @@ -208,7 +208,7 @@ behavior out of the box: | |
| 208 208 | 
             
                person.first_name = 'zoolander'
         | 
| 209 209 | 
             
                person.valid?  # => false
         | 
| 210 210 |  | 
| 211 | 
            -
              {Learn more}[ | 
| 211 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validations.html]
         | 
| 212 212 |  | 
| 213 213 | 
             
            * Custom validators
         | 
| 214 214 |  | 
| @@ -230,7 +230,7 @@ behavior out of the box: | |
| 230 230 | 
             
                p.name = "Bob"
         | 
| 231 231 | 
             
                p.valid?                  # =>  true
         | 
| 232 232 |  | 
| 233 | 
            -
              {Learn more}[ | 
| 233 | 
            +
              {Learn more}[https://api.rubyonrails.org/classes/ActiveModel/Validator.html]
         | 
| 234 234 |  | 
| 235 235 |  | 
| 236 236 | 
             
            == Download and installation
         | 
| @@ -66,7 +66,6 @@ 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: {}
         | 
| @@ -215,12 +214,9 @@ module ActiveModel | |
| 215 214 | 
             
                    end
         | 
| 216 215 | 
             
                  end
         | 
| 217 216 |  | 
| 218 | 
            -
                  def generate_alias_attribute_methods(code_generator, new_name, old_name) | 
| 219 | 
            -
                     | 
| 220 | 
            -
                       | 
| 221 | 
            -
                        alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
         | 
| 222 | 
            -
                      end
         | 
| 223 | 
            -
                      attribute_method_patterns_cache.clear
         | 
| 217 | 
            +
                  def generate_alias_attribute_methods(code_generator, new_name, old_name)
         | 
| 218 | 
            +
                    attribute_method_patterns.each do |pattern|
         | 
| 219 | 
            +
                      alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
         | 
| 224 220 | 
             
                    end
         | 
| 225 221 | 
             
                  end
         | 
| 226 222 |  | 
| @@ -228,28 +224,13 @@ module ActiveModel | |
| 228 224 | 
             
                    method_name = pattern.method_name(new_name).to_s
         | 
| 229 225 | 
             
                    target_name = pattern.method_name(old_name).to_s
         | 
| 230 226 | 
             
                    parameters = pattern.parameters
         | 
| 231 | 
            -
                    mangled_name = target_name
         | 
| 232 227 |  | 
| 233 | 
            -
                     | 
| 234 | 
            -
                      mangled_name = "__temp__#{target_name.unpack1("h*")}"
         | 
| 235 | 
            -
                    end
         | 
| 236 | 
            -
             | 
| 237 | 
            -
                    code_generator.define_cached_method(mangled_name, as: method_name, namespace: :alias_attribute) do |batch|
         | 
| 238 | 
            -
                      body = if CALL_COMPILABLE_REGEXP.match?(target_name)
         | 
| 239 | 
            -
                        "self.#{target_name}(#{parameters || ''})"
         | 
| 240 | 
            -
                      else
         | 
| 241 | 
            -
                        call_args = [":'#{target_name}'"]
         | 
| 242 | 
            -
                        call_args << parameters if parameters
         | 
| 243 | 
            -
                        "send(#{call_args.join(", ")})"
         | 
| 244 | 
            -
                      end
         | 
| 228 | 
            +
                    mangled_name = build_mangled_name(target_name)
         | 
| 245 229 |  | 
| 246 | 
            -
             | 
| 230 | 
            +
                    call_args = []
         | 
| 231 | 
            +
                    call_args << parameters if parameters
         | 
| 247 232 |  | 
| 248 | 
            -
             | 
| 249 | 
            -
                        "#{modifier}def #{mangled_name}(#{parameters || ''})" <<
         | 
| 250 | 
            -
                        body <<
         | 
| 251 | 
            -
                        "end"
         | 
| 252 | 
            -
                    end
         | 
| 233 | 
            +
                    define_call(code_generator, method_name, target_name, mangled_name, parameters, call_args, namespace: :alias_attribute)
         | 
| 253 234 | 
             
                  end
         | 
| 254 235 |  | 
| 255 236 | 
             
                  # Is +new_name+ an alias?
         | 
| @@ -324,41 +305,22 @@ module ActiveModel | |
| 324 305 | 
             
                  #   person.name = 'Bob'
         | 
| 325 306 | 
             
                  #   person.name        # => "Bob"
         | 
| 326 307 | 
             
                  #   person.name_short? # => true
         | 
| 327 | 
            -
                  def define_attribute_method(attr_name, _owner: generated_attribute_methods | 
| 308 | 
            +
                  def define_attribute_method(attr_name, _owner: generated_attribute_methods)
         | 
| 328 309 | 
             
                    ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
         | 
| 329 310 | 
             
                      attribute_method_patterns.each do |pattern|
         | 
| 330 | 
            -
                         | 
| 331 | 
            -
                      end
         | 
| 332 | 
            -
                      attribute_method_patterns_cache.clear
         | 
| 333 | 
            -
                    end
         | 
| 334 | 
            -
                  end
         | 
| 311 | 
            +
                        method_name = pattern.method_name(attr_name)
         | 
| 335 312 |  | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 338 | 
            -
                    public_method_name = pattern.method_name(as)
         | 
| 339 | 
            -
             | 
| 340 | 
            -
                    # If defining a regular attribute method, we don't override methods that are explictly
         | 
| 341 | 
            -
                    # defined in parrent classes.
         | 
| 342 | 
            -
                    if instance_method_already_implemented?(public_method_name)
         | 
| 343 | 
            -
                      # However, for `alias_attribute`, we always define the method.
         | 
| 344 | 
            -
                      # We check for override second because `instance_method_already_implemented?`
         | 
| 345 | 
            -
                      # also check for dangerous methods.
         | 
| 346 | 
            -
                      return unless override
         | 
| 347 | 
            -
                    end
         | 
| 313 | 
            +
                        unless instance_method_already_implemented?(method_name)
         | 
| 314 | 
            +
                          generate_method = "define_method_#{pattern.proxy_target}"
         | 
| 348 315 |  | 
| 349 | 
            -
             | 
| 350 | 
            -
             | 
| 351 | 
            -
             | 
| 352 | 
            -
             | 
| 353 | 
            -
             | 
| 354 | 
            -
                         | 
| 355 | 
            -
             | 
| 356 | 
            -
             | 
| 357 | 
            -
                        pattern.parameters,
         | 
| 358 | 
            -
                        attr_name.to_s,
         | 
| 359 | 
            -
                        namespace: :active_model_proxy,
         | 
| 360 | 
            -
                        as: public_method_name,
         | 
| 361 | 
            -
                      )
         | 
| 316 | 
            +
                          if respond_to?(generate_method, true)
         | 
| 317 | 
            +
                            send(generate_method, attr_name.to_s, owner: owner)
         | 
| 318 | 
            +
                          else
         | 
| 319 | 
            +
                            define_proxy_call(owner, method_name, pattern.proxy_target, pattern.parameters, attr_name.to_s, namespace: :active_model_proxy)
         | 
| 320 | 
            +
                          end
         | 
| 321 | 
            +
                        end
         | 
| 322 | 
            +
                      end
         | 
| 323 | 
            +
                      attribute_method_patterns_cache.clear
         | 
| 362 324 | 
             
                    end
         | 
| 363 325 | 
             
                  end
         | 
| 364 326 |  | 
| @@ -403,6 +365,8 @@ module ActiveModel | |
| 403 365 | 
             
                      super
         | 
| 404 366 | 
             
                      base.class_eval do
         | 
| 405 367 | 
             
                        @attribute_method_patterns_cache = nil
         | 
| 368 | 
            +
                        @aliases_by_attribute_name = nil
         | 
| 369 | 
            +
                        @generated_attribute_methods = nil
         | 
| 406 370 | 
             
                      end
         | 
| 407 371 | 
             
                    end
         | 
| 408 372 |  | 
| @@ -440,28 +404,37 @@ module ActiveModel | |
| 440 404 | 
             
                    # Define a method `name` in `mod` that dispatches to `send`
         | 
| 441 405 | 
             
                    # using the given `extra` args. This falls back on `send`
         | 
| 442 406 | 
             
                    # if the called name cannot be compiled.
         | 
| 443 | 
            -
                    def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace | 
| 407 | 
            +
                    def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:)
         | 
| 408 | 
            +
                      mangled_name = build_mangled_name(name)
         | 
| 409 | 
            +
             | 
| 410 | 
            +
                      call_args.map!(&:inspect)
         | 
| 411 | 
            +
                      call_args << parameters if parameters
         | 
| 412 | 
            +
                      namespace = :"#{namespace}_#{proxy_target}_#{call_args.join("_")}}"
         | 
| 413 | 
            +
             | 
| 414 | 
            +
                      define_call(code_generator, name, proxy_target, mangled_name, parameters, call_args, namespace: namespace)
         | 
| 415 | 
            +
                    end
         | 
| 416 | 
            +
             | 
| 417 | 
            +
                    def build_mangled_name(name)
         | 
| 444 418 | 
             
                      mangled_name = name
         | 
| 419 | 
            +
             | 
| 445 420 | 
             
                      unless NAME_COMPILABLE_REGEXP.match?(name)
         | 
| 446 421 | 
             
                        mangled_name = "__temp__#{name.unpack1("h*")}"
         | 
| 447 422 | 
             
                      end
         | 
| 448 423 |  | 
| 449 | 
            -
                       | 
| 450 | 
            -
             | 
| 451 | 
            -
                      namespace = :"#{namespace}_#{proxy_target}"
         | 
| 424 | 
            +
                      mangled_name
         | 
| 425 | 
            +
                    end
         | 
| 452 426 |  | 
| 453 | 
            -
             | 
| 454 | 
            -
             | 
| 455 | 
            -
             | 
| 427 | 
            +
                    def define_call(code_generator, name, target_name, mangled_name, parameters, call_args, namespace:)
         | 
| 428 | 
            +
                      code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
         | 
| 429 | 
            +
                        body = if CALL_COMPILABLE_REGEXP.match?(target_name)
         | 
| 430 | 
            +
                          "self.#{target_name}(#{call_args.join(", ")})"
         | 
| 456 431 | 
             
                        else
         | 
| 457 | 
            -
                          call_args.unshift(":'#{ | 
| 432 | 
            +
                          call_args.unshift(":'#{target_name}'")
         | 
| 458 433 | 
             
                          "send(#{call_args.join(", ")})"
         | 
| 459 434 | 
             
                        end
         | 
| 460 435 |  | 
| 461 | 
            -
                        modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
         | 
| 462 | 
            -
             | 
| 463 436 | 
             
                        batch <<
         | 
| 464 | 
            -
                          " | 
| 437 | 
            +
                          "def #{mangled_name}(#{parameters || ''})" <<
         | 
| 465 438 | 
             
                          body <<
         | 
| 466 439 | 
             
                          "end"
         | 
| 467 440 | 
             
                      end
         | 
| @@ -475,7 +448,7 @@ module ActiveModel | |
| 475 448 | 
             
                      def initialize(prefix: "", suffix: "", parameters: nil)
         | 
| 476 449 | 
             
                        @prefix = prefix
         | 
| 477 450 | 
             
                        @suffix = suffix
         | 
| 478 | 
            -
                        @parameters = parameters.nil? ?  | 
| 451 | 
            +
                        @parameters = parameters.nil? ? "..." : parameters
         | 
| 479 452 | 
             
                        @regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
         | 
| 480 453 | 
             
                        @proxy_target = "#{@prefix}attribute#{@suffix}"
         | 
| 481 454 | 
             
                        @method_name = "#{prefix}%s#{suffix}"
         | 
| @@ -503,24 +476,22 @@ module ActiveModel | |
| 503 476 | 
             
                # It's also possible to instantiate related objects, so a <tt>Client</tt>
         | 
| 504 477 | 
             
                # class belonging to the +clients+ table with a +master_id+ foreign key
         | 
| 505 478 | 
             
                # can instantiate master through <tt>Client#master</tt>.
         | 
| 506 | 
            -
                def method_missing(method,  | 
| 479 | 
            +
                def method_missing(method, ...)
         | 
| 507 480 | 
             
                  if respond_to_without_attributes?(method, true)
         | 
| 508 481 | 
             
                    super
         | 
| 509 482 | 
             
                  else
         | 
| 510 | 
            -
                    match = matched_attribute_method(method. | 
| 511 | 
            -
                    match ? attribute_missing(match,  | 
| 483 | 
            +
                    match = matched_attribute_method(method.name)
         | 
| 484 | 
            +
                    match ? attribute_missing(match, ...) : super
         | 
| 512 485 | 
             
                  end
         | 
| 513 486 | 
             
                end
         | 
| 514 | 
            -
                ruby2_keywords(:method_missing)
         | 
| 515 487 |  | 
| 516 488 | 
             
                # +attribute_missing+ is like +method_missing+, but for attributes. When
         | 
| 517 489 | 
             
                # +method_missing+ is called we check to see if there is a matching
         | 
| 518 490 | 
             
                # attribute method. If so, we tell +attribute_missing+ to dispatch the
         | 
| 519 491 | 
             
                # attribute. This method can be overloaded to customize the behavior.
         | 
| 520 | 
            -
                def attribute_missing(match,  | 
| 521 | 
            -
                  __send__(match.proxy_target, match.attr_name,  | 
| 492 | 
            +
                def attribute_missing(match, ...)
         | 
| 493 | 
            +
                  __send__(match.proxy_target, match.attr_name, ...)
         | 
| 522 494 | 
             
                end
         | 
| 523 | 
            -
                ruby2_keywords(:attribute_missing)
         | 
| 524 495 |  | 
| 525 496 | 
             
                # A +Person+ instance with a +name+ attribute can ask
         | 
| 526 497 | 
             
                # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
         | 
| @@ -10,17 +10,28 @@ module ActiveModel | |
| 10 10 |  | 
| 11 11 | 
             
                module ClassMethods # :nodoc:
         | 
| 12 12 | 
             
                  def attribute(name, type = nil, default: (no_default = true), **options)
         | 
| 13 | 
            +
                    name = resolve_attribute_name(name)
         | 
| 13 14 | 
             
                    type = resolve_type_name(type, **options) if type.is_a?(Symbol)
         | 
| 15 | 
            +
                    type = hook_attribute_type(name, type) if type
         | 
| 14 16 |  | 
| 15 | 
            -
                     | 
| 16 | 
            -
                     | 
| 17 | 
            -
             | 
| 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)
         | 
| 18 27 |  | 
| 19 28 | 
             
                    reset_default_attributes
         | 
| 20 29 | 
             
                  end
         | 
| 21 30 |  | 
| 22 31 | 
             
                  def _default_attributes # :nodoc:
         | 
| 23 | 
            -
                    @default_attributes ||=  | 
| 32 | 
            +
                    @default_attributes ||= AttributeSet.new({}).tap do |attribute_set|
         | 
| 33 | 
            +
                      apply_pending_attribute_modifications(attribute_set)
         | 
| 34 | 
            +
                    end
         | 
| 24 35 | 
             
                  end
         | 
| 25 36 |  | 
| 26 37 | 
             
                  def attribute_types # :nodoc:
         | 
| @@ -29,40 +40,62 @@ module ActiveModel | |
| 29 40 | 
             
                    end
         | 
| 30 41 | 
             
                  end
         | 
| 31 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 | 
            +
             | 
| 32 53 | 
             
                  private
         | 
| 33 | 
            -
                     | 
| 34 | 
            -
                       | 
| 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
         | 
| 35 60 |  | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
                         | 
| 39 | 
            -
                        attribute
         | 
| 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)
         | 
| 40 64 | 
             
                      end
         | 
| 41 65 | 
             
                    end
         | 
| 42 66 |  | 
| 43 | 
            -
                     | 
| 44 | 
            -
                       | 
| 45 | 
            -
             | 
| 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
         | 
| 46 75 | 
             
                    end
         | 
| 47 76 |  | 
| 48 | 
            -
                    def  | 
| 49 | 
            -
                       | 
| 77 | 
            +
                    def pending_attribute_modifications
         | 
| 78 | 
            +
                      @pending_attribute_modifications ||= []
         | 
| 79 | 
            +
                    end
         | 
| 50 80 |  | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 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)
         | 
| 53 84 | 
             
                      end
         | 
| 54 85 |  | 
| 55 | 
            -
                       | 
| 86 | 
            +
                      pending_attribute_modifications.each do |modification|
         | 
| 87 | 
            +
                        modification.apply_to(attribute_set)
         | 
| 88 | 
            +
                      end
         | 
| 56 89 | 
             
                    end
         | 
| 57 90 |  | 
| 58 | 
            -
                    def  | 
| 59 | 
            -
                       | 
| 91 | 
            +
                    def reset_default_attributes
         | 
| 92 | 
            +
                      reset_default_attributes!
         | 
| 93 | 
            +
                      subclasses.each { |subclass| subclass.send(:reset_default_attributes) }
         | 
| 60 94 | 
             
                    end
         | 
| 61 95 |  | 
| 62 | 
            -
                    def reset_default_attributes
         | 
| 96 | 
            +
                    def reset_default_attributes!
         | 
| 63 97 | 
             
                      @default_attributes = nil
         | 
| 64 98 | 
             
                      @attribute_types = nil
         | 
| 65 | 
            -
                      subclasses.each { |subclass| subclass.send(__method__) }
         | 
| 66 99 | 
             
                    end
         | 
| 67 100 |  | 
| 68 101 | 
             
                    def resolve_attribute_name(name)
         | 
| @@ -72,6 +105,13 @@ module ActiveModel | |
| 72 105 | 
             
                    def resolve_type_name(name, **options)
         | 
| 73 106 | 
             
                      Type.lookup(name, **options)
         | 
| 74 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
         | 
| 75 115 | 
             
                end
         | 
| 76 116 | 
             
              end
         | 
| 77 117 | 
             
            end
         | 
| @@ -75,12 +75,25 @@ module ActiveModel | |
| 75 75 | 
             
                    attribute_types.keys
         | 
| 76 76 | 
             
                  end
         | 
| 77 77 |  | 
| 78 | 
            +
                  ##
         | 
| 79 | 
            +
                  # :method: type_for_attribute
         | 
| 80 | 
            +
                  # :call-seq: type_for_attribute(attribute_name, &block)
         | 
| 81 | 
            +
                  #
         | 
| 82 | 
            +
                  # Returns the type of the specified attribute after applying any
         | 
| 83 | 
            +
                  # modifiers. This method is the only valid source of information for
         | 
| 84 | 
            +
                  # anything related to the types of a model's attributes. The return value
         | 
| 85 | 
            +
                  # of this method will implement the interface described by
         | 
| 86 | 
            +
                  # ActiveModel::Type::Value (though the object itself may not subclass it).
         | 
| 87 | 
            +
                  #--
         | 
| 88 | 
            +
                  # Implemented by ActiveModel::AttributeRegistration::ClassMethods#type_for_attribute.
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  ##
         | 
| 78 91 | 
             
                  private
         | 
| 79 | 
            -
                    def define_method_attribute=( | 
| 92 | 
            +
                    def define_method_attribute=(name, owner:)
         | 
| 80 93 | 
             
                      ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
         | 
| 81 | 
            -
                        owner,  | 
| 94 | 
            +
                        owner, name, writer: true,
         | 
| 82 95 | 
             
                      ) do |temp_method_name, attr_name_expr|
         | 
| 83 | 
            -
                        owner.define_cached_method( | 
| 96 | 
            +
                        owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_model) do |batch|
         | 
| 84 97 | 
             
                          batch <<
         | 
| 85 98 | 
             
                            "def #{temp_method_name}(value)" <<
         | 
| 86 99 | 
             
                            "  _write_attribute(#{attr_name_expr}, value)" <<
         | 
| @@ -60,7 +60,7 @@ module ActiveModel | |
| 60 60 | 
             
              # Would only create the +after_create+ and +before_create+ callback methods in
         | 
| 61 61 | 
             
              # your class.
         | 
| 62 62 | 
             
              #
         | 
| 63 | 
            -
              # NOTE:  | 
| 63 | 
            +
              # NOTE: Defining the same callback multiple times will overwrite previous callback definitions.
         | 
| 64 64 | 
             
              #
         | 
| 65 65 | 
             
              module Callbacks
         | 
| 66 66 | 
             
                def self.extended(base) # :nodoc:
         | 
| @@ -94,7 +94,13 @@ module ActiveModel | |
| 94 94 | 
             
                          end
         | 
| 95 95 |  | 
| 96 96 | 
             
                          if $8
         | 
| 97 | 
            -
                            offset =  | 
| 97 | 
            +
                            offset = \
         | 
| 98 | 
            +
                              if $8 == "Z"
         | 
| 99 | 
            +
                                0
         | 
| 100 | 
            +
                              else
         | 
| 101 | 
            +
                                offset_h, offset_m = $8.to_i, $9.to_i
         | 
| 102 | 
            +
                                offset_h.to_i * 3600 + (offset_h.negative? ? -1 : 1) * offset_m * 60
         | 
| 103 | 
            +
                              end
         | 
| 98 104 | 
             
                          end
         | 
| 99 105 |  | 
| 100 106 | 
             
                          new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
         | 
| @@ -20,16 +20,15 @@ module ActiveModel | |
| 20 20 | 
             
                    registrations[type_name] = block
         | 
| 21 21 | 
             
                  end
         | 
| 22 22 |  | 
| 23 | 
            -
                  def lookup(symbol,  | 
| 23 | 
            +
                  def lookup(symbol, ...)
         | 
| 24 24 | 
             
                    registration = registrations[symbol]
         | 
| 25 25 |  | 
| 26 26 | 
             
                    if registration
         | 
| 27 | 
            -
                      registration.call(symbol,  | 
| 27 | 
            +
                      registration.call(symbol, ...)
         | 
| 28 28 | 
             
                    else
         | 
| 29 29 | 
             
                      raise ArgumentError, "Unknown type #{symbol.inspect}"
         | 
| 30 30 | 
             
                    end
         | 
| 31 31 | 
             
                  end
         | 
| 32 | 
            -
                  ruby2_keywords(:lookup)
         | 
| 33 32 |  | 
| 34 33 | 
             
                  private
         | 
| 35 34 | 
             
                    attr_reader :registrations
         | 
| @@ -10,7 +10,7 @@ module ActiveModel | |
| 10 10 | 
             
                  include ResolveValue
         | 
| 11 11 |  | 
| 12 12 | 
             
                  def check_validity!
         | 
| 13 | 
            -
                    unless  | 
| 13 | 
            +
                    unless options.keys.intersect?(COMPARE_CHECKS.keys)
         | 
| 14 14 | 
             
                      raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
         | 
| 15 15 | 
             
                      ":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
         | 
| 16 16 | 
             
                    end
         | 
| @@ -10,7 +10,7 @@ module ActiveModel | |
| 10 10 | 
             
                  # validators can be overridden inside specific classes by creating
         | 
| 11 11 | 
             
                  # custom validator classes in their place such as PresenceValidator.
         | 
| 12 12 | 
             
                  #
         | 
| 13 | 
            -
                  # Examples of using the default  | 
| 13 | 
            +
                  # Examples of using the default Rails validators:
         | 
| 14 14 | 
             
                  #
         | 
| 15 15 | 
             
                  #   validates :username, absence: true
         | 
| 16 16 | 
             
                  #   validates :terms, acceptance: true
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: activemodel
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 7. | 
| 4 | 
            +
              version: 7.2.0.beta1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - David Heinemeier Hansson
         | 
| 8 | 
            +
            autorequire:
         | 
| 8 9 | 
             
            bindir: bin
         | 
| 9 10 | 
             
            cert_chain: []
         | 
| 10 | 
            -
            date:  | 
| 11 | 
            +
            date: 2024-05-29 00:00:00.000000000 Z
         | 
| 11 12 | 
             
            dependencies:
         | 
| 12 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 13 14 | 
             
              name: activesupport
         | 
| @@ -15,14 +16,14 @@ dependencies: | |
| 15 16 | 
             
                requirements:
         | 
| 16 17 | 
             
                - - '='
         | 
| 17 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 18 | 
            -
                    version: 7. | 
| 19 | 
            +
                    version: 7.2.0.beta1
         | 
| 19 20 | 
             
              type: :runtime
         | 
| 20 21 | 
             
              prerelease: false
         | 
| 21 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 22 23 | 
             
                requirements:
         | 
| 23 24 | 
             
                - - '='
         | 
| 24 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 25 | 
            -
                    version: 7. | 
| 26 | 
            +
                    version: 7.2.0.beta1
         | 
| 26 27 | 
             
            description: A toolkit for building modeling frameworks like Active Record. Rich support
         | 
| 27 28 | 
             
              for attributes, callbacks, validations, serialization, internationalization, and
         | 
| 28 29 | 
             
              testing.
         | 
| @@ -111,11 +112,12 @@ licenses: | |
| 111 112 | 
             
            - MIT
         | 
| 112 113 | 
             
            metadata:
         | 
| 113 114 | 
             
              bug_tracker_uri: https://github.com/rails/rails/issues
         | 
| 114 | 
            -
              changelog_uri: https://github.com/rails/rails/blob/v7. | 
| 115 | 
            -
              documentation_uri: https://api.rubyonrails.org/v7. | 
| 115 | 
            +
              changelog_uri: https://github.com/rails/rails/blob/v7.2.0.beta1/activemodel/CHANGELOG.md
         | 
| 116 | 
            +
              documentation_uri: https://api.rubyonrails.org/v7.2.0.beta1/
         | 
| 116 117 | 
             
              mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
         | 
| 117 | 
            -
              source_code_uri: https://github.com/rails/rails/tree/v7. | 
| 118 | 
            +
              source_code_uri: https://github.com/rails/rails/tree/v7.2.0.beta1/activemodel
         | 
| 118 119 | 
             
              rubygems_mfa_required: 'true'
         | 
| 120 | 
            +
            post_install_message:
         | 
| 119 121 | 
             
            rdoc_options: []
         | 
| 120 122 | 
             
            require_paths:
         | 
| 121 123 | 
             
            - lib
         | 
| @@ -123,14 +125,15 @@ required_ruby_version: !ruby/object:Gem::Requirement | |
| 123 125 | 
             
              requirements:
         | 
| 124 126 | 
             
              - - ">="
         | 
| 125 127 | 
             
                - !ruby/object:Gem::Version
         | 
| 126 | 
            -
                  version:  | 
| 128 | 
            +
                  version: 3.1.0
         | 
| 127 129 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 128 130 | 
             
              requirements:
         | 
| 129 131 | 
             
              - - ">="
         | 
| 130 132 | 
             
                - !ruby/object:Gem::Version
         | 
| 131 133 | 
             
                  version: '0'
         | 
| 132 134 | 
             
            requirements: []
         | 
| 133 | 
            -
            rubygems_version: 3. | 
| 135 | 
            +
            rubygems_version: 3.5.10
         | 
| 136 | 
            +
            signing_key:
         | 
| 134 137 | 
             
            specification_version: 4
         | 
| 135 138 | 
             
            summary: A toolkit for building modeling frameworks (part of Rails).
         | 
| 136 139 | 
             
            test_files: []
         |