activemodel 7.2.2.1 → 8.1.2
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 +25 -45
- data/README.rdoc +1 -1
- data/lib/active_model/attribute/user_provided_default.rb +10 -0
- data/lib/active_model/attribute.rb +9 -1
- data/lib/active_model/attribute_assignment.rb +23 -2
- data/lib/active_model/attribute_methods.rb +5 -5
- data/lib/active_model/attribute_mutation_tracker.rb +5 -5
- data/lib/active_model/attribute_set.rb +1 -1
- data/lib/active_model/attributes/normalization.rb +192 -0
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +16 -9
- data/lib/active_model/error.rb +3 -2
- data/lib/active_model/errors.rb +1 -4
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/lint.rb +7 -3
- data/lib/active_model/model.rb +2 -2
- data/lib/active_model/naming.rb +0 -1
- data/lib/active_model/nested_error.rb +1 -3
- data/lib/active_model/railtie.rb +8 -4
- data/lib/active_model/secure_password.rb +61 -4
- data/lib/active_model/serialization.rb +21 -21
- data/lib/active_model/translation.rb +22 -5
- data/lib/active_model/type/big_integer.rb +21 -0
- data/lib/active_model/type/boolean.rb +1 -0
- data/lib/active_model/type/date.rb +1 -0
- data/lib/active_model/type/date_time.rb +8 -0
- data/lib/active_model/type/decimal.rb +1 -0
- data/lib/active_model/type/float.rb +9 -8
- data/lib/active_model/type/helpers/immutable.rb +13 -0
- data/lib/active_model/type/helpers/time_value.rb +20 -44
- data/lib/active_model/type/helpers.rb +1 -0
- data/lib/active_model/type/immutable_string.rb +2 -0
- data/lib/active_model/type/integer.rb +31 -19
- data/lib/active_model/type/string.rb +4 -0
- data/lib/active_model/type/value.rb +4 -4
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +10 -0
- data/lib/active_model/validations/validates.rb +7 -2
- data/lib/active_model/validations.rb +57 -32
- data/lib/active_model.rb +6 -0
- metadata +11 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4f299734cfee7396d03d330f92d0be6f335017395937c52f6e6a88ebc894c1b8
|
|
4
|
+
data.tar.gz: d9ed23ff71d39c9ad9dc0df555413ed5440baef7240d1dc12d6ddc35cdc1c76c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9586704ac01c836de27bfc1fdc50deee46f921af37590022fba6c13af2c703c7d4973cb0d50c16b45687afe1bb36eb7c6ea5fb6c04992b93a42dcc3f8f80637d
|
|
7
|
+
data.tar.gz: 428ad04107455dbb8e62a341192f8f4a093a4d17afca052623d25305ae7e109b21fa0dab8f2aa7581dc9ec1ebfe58bfad1e0008387df1b2cc044f95772895297
|
data/CHANGELOG.md
CHANGED
|
@@ -1,66 +1,46 @@
|
|
|
1
|
-
## Rails
|
|
1
|
+
## Rails 8.1.2 (January 08, 2026) ##
|
|
2
2
|
|
|
3
3
|
* No changes.
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
## Rails
|
|
7
|
-
|
|
8
|
-
* Fix regression in `alias_attribute` to work with user defined methods.
|
|
9
|
-
|
|
10
|
-
`alias_attribute` would wrongly assume the attribute accessor was generated by Active Model.
|
|
11
|
-
|
|
12
|
-
```ruby
|
|
13
|
-
class Person
|
|
14
|
-
include ActiveModel::AttributeMethods
|
|
15
|
-
|
|
16
|
-
define_attribute_methods :name
|
|
17
|
-
attr_accessor :name
|
|
18
|
-
|
|
19
|
-
alias_attribute :full_name, :name
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
person.full_name # => NoMethodError: undefined method `attribute' for an instance of Person
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
*Jean Boussier*
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Rails 7.2.1.2 (October 23, 2024) ##
|
|
6
|
+
## Rails 8.1.1 (October 28, 2025) ##
|
|
29
7
|
|
|
30
8
|
* No changes.
|
|
31
9
|
|
|
32
10
|
|
|
33
|
-
## Rails
|
|
11
|
+
## Rails 8.1.0 (October 22, 2025) ##
|
|
34
12
|
|
|
35
|
-
*
|
|
13
|
+
* Add `reset_token: { expires_in: ... }` option to `has_secure_password`.
|
|
36
14
|
|
|
15
|
+
Allows configuring the expiry duration of password reset tokens (default remains 15 minutes for backwards compatibility).
|
|
37
16
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
17
|
+
```ruby
|
|
18
|
+
has_secure_password reset_token: { expires_in: 1.hour }
|
|
19
|
+
```
|
|
41
20
|
|
|
21
|
+
*Jevin Sew*, *Abeid Ahmed*
|
|
42
22
|
|
|
43
|
-
|
|
23
|
+
* Add `except_on:` option for validation callbacks.
|
|
44
24
|
|
|
45
|
-
*
|
|
46
|
-
calculate minus minute value in TZ offset correctly.
|
|
25
|
+
*Ben Sheldon*
|
|
47
26
|
|
|
48
|
-
|
|
27
|
+
* Backport `ActiveRecord::Normalization` to `ActiveModel::Attributes::Normalization`
|
|
49
28
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
29
|
+
```ruby
|
|
30
|
+
class User
|
|
31
|
+
include ActiveModel::Attributes
|
|
32
|
+
include ActiveModel::Attributes::Normalization
|
|
53
33
|
|
|
54
|
-
|
|
55
|
-
class MyModel
|
|
56
|
-
include ActiveModel::Attributes
|
|
34
|
+
attribute :email, :string
|
|
57
35
|
|
|
58
|
-
|
|
59
|
-
|
|
36
|
+
normalizes :email, with: -> email { email.strip.downcase }
|
|
37
|
+
end
|
|
60
38
|
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
user = User.new
|
|
40
|
+
user.email = " CRUISE-CONTROL@EXAMPLE.COM\n"
|
|
41
|
+
user.email # => "cruise-control@example.com"
|
|
42
|
+
```
|
|
63
43
|
|
|
64
|
-
*
|
|
44
|
+
*Sean Doyle*
|
|
65
45
|
|
|
66
|
-
Please check [
|
|
46
|
+
Please check [8-0-stable](https://github.com/rails/rails/blob/8-0-stable/activemodel/CHANGELOG.md) for previous changes.
|
data/README.rdoc
CHANGED
|
@@ -261,6 +261,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
|
|
|
261
261
|
|
|
262
262
|
* https://github.com/rails/rails/issues
|
|
263
263
|
|
|
264
|
-
Feature requests should be discussed on the
|
|
264
|
+
Feature requests should be discussed on the rubyonrails-core forum here:
|
|
265
265
|
|
|
266
266
|
* https://discuss.rubyonrails.org/c/rubyonrails-core
|
|
@@ -26,6 +26,16 @@ module ActiveModel
|
|
|
26
26
|
self.class.new(name, user_provided_value, type, original_attribute)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def dup_or_share # :nodoc:
|
|
30
|
+
# Can't elide dup when the default is a Proc
|
|
31
|
+
# See Attribute#dup_or_share
|
|
32
|
+
if @user_provided_value.is_a?(Proc)
|
|
33
|
+
dup
|
|
34
|
+
else
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
29
39
|
def marshal_dump
|
|
30
40
|
result = [
|
|
31
41
|
name,
|
|
@@ -38,7 +38,7 @@ module ActiveModel
|
|
|
38
38
|
@value = value unless value.nil?
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def value
|
|
41
|
+
def value(&)
|
|
42
42
|
# `defined?` is cheaper than `||=` when we get back falsy values
|
|
43
43
|
@value = type_cast(value_before_type_cast) unless defined?(@value)
|
|
44
44
|
@value
|
|
@@ -96,6 +96,14 @@ module ActiveModel
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
+
def dup_or_share # :nodoc:
|
|
100
|
+
if @type.mutable?
|
|
101
|
+
dup
|
|
102
|
+
else
|
|
103
|
+
self # If the underlying type is immutable we can get away with not duping
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
99
107
|
def type_cast(*)
|
|
100
108
|
raise NotImplementedError
|
|
101
109
|
end
|
|
@@ -36,9 +36,30 @@ module ActiveModel
|
|
|
36
36
|
|
|
37
37
|
alias attributes= assign_attributes
|
|
38
38
|
|
|
39
|
+
# Like `BasicObject#method_missing`, `#attribute_writer_missing` is invoked
|
|
40
|
+
# when `#assign_attributes` is passed an unknown attribute name.
|
|
41
|
+
#
|
|
42
|
+
# By default, `#attribute_writer_missing` raises an UnknownAttributeError.
|
|
43
|
+
#
|
|
44
|
+
# class Rectangle
|
|
45
|
+
# include ActiveModel::AttributeAssignment
|
|
46
|
+
#
|
|
47
|
+
# attr_accessor :length, :width
|
|
48
|
+
#
|
|
49
|
+
# def attribute_writer_missing(name, value)
|
|
50
|
+
# Rails.logger.warn "Tried to assign to unknown attribute #{name}"
|
|
51
|
+
# end
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# rectangle = Rectangle.new
|
|
55
|
+
# rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'"
|
|
56
|
+
def attribute_writer_missing(name, value)
|
|
57
|
+
raise UnknownAttributeError.new(self, name)
|
|
58
|
+
end
|
|
59
|
+
|
|
39
60
|
private
|
|
40
61
|
def _assign_attributes(attributes)
|
|
41
|
-
attributes.
|
|
62
|
+
attributes.each_pair do |k, v|
|
|
42
63
|
_assign_attribute(k, v)
|
|
43
64
|
end
|
|
44
65
|
end
|
|
@@ -50,7 +71,7 @@ module ActiveModel
|
|
|
50
71
|
if respond_to?(setter)
|
|
51
72
|
raise
|
|
52
73
|
else
|
|
53
|
-
|
|
74
|
+
attribute_writer_missing(k.to_s, v)
|
|
54
75
|
end
|
|
55
76
|
end
|
|
56
77
|
end
|
|
@@ -214,7 +214,7 @@ module ActiveModel
|
|
|
214
214
|
end
|
|
215
215
|
end
|
|
216
216
|
|
|
217
|
-
def generate_alias_attribute_methods(code_generator, new_name, old_name)
|
|
217
|
+
def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
|
|
218
218
|
ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner|
|
|
219
219
|
attribute_method_patterns.each do |pattern|
|
|
220
220
|
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
|
@@ -321,8 +321,8 @@ module ActiveModel
|
|
|
321
321
|
canonical_method_name = pattern.method_name(attr_name)
|
|
322
322
|
public_method_name = pattern.method_name(as)
|
|
323
323
|
|
|
324
|
-
# If defining a regular attribute method, we don't override methods that are
|
|
325
|
-
# defined in
|
|
324
|
+
# If defining a regular attribute method, we don't override methods that are explicitly
|
|
325
|
+
# defined in parent classes.
|
|
326
326
|
if instance_method_already_implemented?(public_method_name)
|
|
327
327
|
# However, for `alias_attribute`, we always define the method.
|
|
328
328
|
# We check for override second because `instance_method_already_implemented?`
|
|
@@ -373,7 +373,7 @@ module ActiveModel
|
|
|
373
373
|
# person.name_short? # => NoMethodError
|
|
374
374
|
# person.first_name # => NoMethodError
|
|
375
375
|
def undefine_attribute_methods
|
|
376
|
-
generated_attribute_methods
|
|
376
|
+
@generated_attribute_methods&.module_eval do
|
|
377
377
|
undef_method(*instance_methods)
|
|
378
378
|
end
|
|
379
379
|
attribute_method_patterns_cache.clear
|
|
@@ -402,7 +402,7 @@ module ActiveModel
|
|
|
402
402
|
end
|
|
403
403
|
|
|
404
404
|
def instance_method_already_implemented?(method_name)
|
|
405
|
-
generated_attribute_methods
|
|
405
|
+
@generated_attribute_methods&.method_defined?(method_name)
|
|
406
406
|
end
|
|
407
407
|
|
|
408
408
|
# The methods +method_missing+ and +respond_to?+ of this module are
|
|
@@ -5,7 +5,7 @@ require "active_support/core_ext/object/duplicable"
|
|
|
5
5
|
|
|
6
6
|
module ActiveModel
|
|
7
7
|
class AttributeMutationTracker # :nodoc:
|
|
8
|
-
OPTION_NOT_GIVEN = Object.new
|
|
8
|
+
OPTION_NOT_GIVEN = Object.new.freeze
|
|
9
9
|
|
|
10
10
|
def initialize(attributes)
|
|
11
11
|
@attributes = attributes
|
|
@@ -16,17 +16,17 @@ module ActiveModel
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def changed_values
|
|
19
|
-
attr_names.each_with_object(
|
|
19
|
+
attr_names.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) do |attr_name, result|
|
|
20
20
|
if changed?(attr_name)
|
|
21
|
-
result
|
|
21
|
+
result.store(attr_name, original_value(attr_name), convert_value: false)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def changes
|
|
27
|
-
attr_names.each_with_object(
|
|
27
|
+
attr_names.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) do |attr_name, result|
|
|
28
28
|
if change = change_to_attribute(attr_name)
|
|
29
|
-
result.
|
|
29
|
+
result.store(attr_name, change, convert_value: false)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
module Attributes
|
|
5
|
+
module Normalization
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
include ActiveModel::Dirty
|
|
10
|
+
include ActiveModel::Validations::Callbacks
|
|
11
|
+
|
|
12
|
+
class_attribute :normalized_attributes, default: Set.new
|
|
13
|
+
|
|
14
|
+
before_validation :normalize_changed_in_place_attributes
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Normalizes a specified attribute using its declared normalizations.
|
|
18
|
+
#
|
|
19
|
+
# ==== Examples
|
|
20
|
+
#
|
|
21
|
+
# class User
|
|
22
|
+
# include ActiveModel::Attributes
|
|
23
|
+
# include ActiveModel::Attributes::Normalization
|
|
24
|
+
#
|
|
25
|
+
# attribute :email, :string
|
|
26
|
+
#
|
|
27
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# legacy_user = User.load_from_legacy_data(...)
|
|
31
|
+
# legacy_user.email # => " CRUISE-CONTROL@EXAMPLE.COM\n"
|
|
32
|
+
# legacy_user.normalize_attribute(:email)
|
|
33
|
+
# legacy_user.email # => "cruise-control@example.com"
|
|
34
|
+
#
|
|
35
|
+
# ==== Behavior with Active Record
|
|
36
|
+
#
|
|
37
|
+
# To prevent confusion, normalization will not be applied
|
|
38
|
+
# when the attribute is fetched from the database. This means that if a
|
|
39
|
+
# record was persisted before the normalization was declared, the record's
|
|
40
|
+
# attribute will not be normalized until either it is assigned a new
|
|
41
|
+
# value, or it is explicitly migrated via Normalization#normalize_attribute.
|
|
42
|
+
#
|
|
43
|
+
# Be aware that if your app was created before Rails 7.1, and your app
|
|
44
|
+
# marshals instances of the targeted model (for example, when caching),
|
|
45
|
+
# then you should set ActiveRecord.marshalling_format_version to +7.1+ or
|
|
46
|
+
# higher via either <tt>config.load_defaults 7.1</tt> or
|
|
47
|
+
# <tt>config.active_record.marshalling_format_version = 7.1</tt>.
|
|
48
|
+
# Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+
|
|
49
|
+
# and raise +TypeError+.
|
|
50
|
+
#
|
|
51
|
+
# class User < ActiveRecord::Base
|
|
52
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
|
53
|
+
# normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
|
|
57
|
+
# user.email # => "cruise-control@example.com"
|
|
58
|
+
#
|
|
59
|
+
# user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
|
|
60
|
+
# user.email # => "cruise-control@example.com"
|
|
61
|
+
# user.email_before_type_cast # => "cruise-control@example.com"
|
|
62
|
+
#
|
|
63
|
+
# User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
|
|
64
|
+
# User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
|
|
65
|
+
#
|
|
66
|
+
# User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
|
|
67
|
+
# User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
|
|
68
|
+
#
|
|
69
|
+
# User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
|
|
70
|
+
def normalize_attribute(name)
|
|
71
|
+
# Treat the value as a new, unnormalized value.
|
|
72
|
+
send(:"#{name}=", send(name))
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
module ClassMethods
|
|
76
|
+
# Declares a normalization for one or more attributes. The normalization
|
|
77
|
+
# is applied when the attribute is assigned or validated.
|
|
78
|
+
#
|
|
79
|
+
# Because the normalization may be applied multiple times, it should be
|
|
80
|
+
# _idempotent_. In other words, applying the normalization more than once
|
|
81
|
+
# should have the same result as applying it only once.
|
|
82
|
+
#
|
|
83
|
+
# By default, the normalization will not be applied to +nil+ values. This
|
|
84
|
+
# behavior can be changed with the +:apply_to_nil+ option.
|
|
85
|
+
#
|
|
86
|
+
# ==== Options
|
|
87
|
+
#
|
|
88
|
+
# * +:with+ - Any callable object that accepts the attribute's value as
|
|
89
|
+
# its sole argument, and returns it normalized.
|
|
90
|
+
# * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values.
|
|
91
|
+
# Defaults to +false+.
|
|
92
|
+
#
|
|
93
|
+
# ==== Examples
|
|
94
|
+
#
|
|
95
|
+
# class User
|
|
96
|
+
# include ActiveModel::Attributes
|
|
97
|
+
# include ActiveModel::Attributes::Normalization
|
|
98
|
+
#
|
|
99
|
+
# attribute :email, :string
|
|
100
|
+
# attribute :phone, :string
|
|
101
|
+
#
|
|
102
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
|
103
|
+
# normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
|
|
104
|
+
# end
|
|
105
|
+
#
|
|
106
|
+
# user = User.new
|
|
107
|
+
# user.email = " CRUISE-CONTROL@EXAMPLE.COM\n"
|
|
108
|
+
# user.email # => "cruise-control@example.com"
|
|
109
|
+
#
|
|
110
|
+
# User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
|
|
111
|
+
def normalizes(*names, with:, apply_to_nil: false)
|
|
112
|
+
decorate_attributes(names) do |name, cast_type|
|
|
113
|
+
NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
self.normalized_attributes += names.map(&:to_sym)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Normalizes a given +value+ using normalizations declared for +name+.
|
|
120
|
+
#
|
|
121
|
+
# ==== Examples
|
|
122
|
+
#
|
|
123
|
+
# class User
|
|
124
|
+
# include ActiveModel::Attributes
|
|
125
|
+
# include ActiveModel::Attributes::Normalization
|
|
126
|
+
#
|
|
127
|
+
# attribute :email, :string
|
|
128
|
+
#
|
|
129
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
|
130
|
+
# end
|
|
131
|
+
#
|
|
132
|
+
# User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
|
|
133
|
+
# # => "cruise-control@example.com"
|
|
134
|
+
def normalize_value_for(name, value)
|
|
135
|
+
type_for_attribute(name).cast(value)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
def normalize_changed_in_place_attributes
|
|
141
|
+
self.class.normalized_attributes.each do |name|
|
|
142
|
+
normalize_attribute(name) if attribute_changed_in_place?(name)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
|
|
147
|
+
include ActiveModel::Type::SerializeCastValue
|
|
148
|
+
|
|
149
|
+
attr_reader :cast_type, :normalizer, :normalize_nil
|
|
150
|
+
alias :normalize_nil? :normalize_nil
|
|
151
|
+
|
|
152
|
+
def initialize(cast_type:, normalizer:, normalize_nil:)
|
|
153
|
+
@cast_type = cast_type
|
|
154
|
+
@normalizer = normalizer
|
|
155
|
+
@normalize_nil = normalize_nil
|
|
156
|
+
super(cast_type)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def cast(value)
|
|
160
|
+
normalize(super(value))
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def serialize(value)
|
|
164
|
+
serialize_cast_value(cast(value))
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def serialize_cast_value(value)
|
|
168
|
+
ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def ==(other)
|
|
172
|
+
self.class == other.class &&
|
|
173
|
+
normalize_nil? == other.normalize_nil? &&
|
|
174
|
+
normalizer == other.normalizer &&
|
|
175
|
+
cast_type == other.cast_type
|
|
176
|
+
end
|
|
177
|
+
alias eql? ==
|
|
178
|
+
|
|
179
|
+
def hash
|
|
180
|
+
[self.class, cast_type, normalizer, normalize_nil?].hash
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
define_method(:inspect, Kernel.instance_method(:inspect))
|
|
184
|
+
|
|
185
|
+
private
|
|
186
|
+
def normalize(value)
|
|
187
|
+
normalizer.call(value) unless value.nil? && !normalize_nil?
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module ActiveModel
|
|
4
4
|
# = Active \Model \Conversion
|
|
5
5
|
#
|
|
6
|
-
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
|
|
6
|
+
# Handles default conversions: #to_model, #to_key, #to_param, and #to_partial_path.
|
|
7
7
|
#
|
|
8
8
|
# Let's take for example this non-persisted object.
|
|
9
9
|
#
|
data/lib/active_model/dirty.rb
CHANGED
|
@@ -108,7 +108,7 @@ module ActiveModel
|
|
|
108
108
|
# person.changes # => {"name" => ["Bill", "Bob"]}
|
|
109
109
|
#
|
|
110
110
|
# If an attribute is modified in-place then make use of
|
|
111
|
-
# {*_will_change!}[rdoc-
|
|
111
|
+
# {*_will_change!}[rdoc-ref:#*_will_change!] to mark that the attribute is changing.
|
|
112
112
|
# Otherwise \Active \Model can't track changes to in-place attributes. Note
|
|
113
113
|
# that Active Record can detect in-place modifications automatically. You do
|
|
114
114
|
# not need to call <tt>*_will_change!</tt> on Active Record models.
|
|
@@ -247,16 +247,23 @@ module ActiveModel
|
|
|
247
247
|
|
|
248
248
|
def initialize_dup(other) # :nodoc:
|
|
249
249
|
super
|
|
250
|
+
@mutations_from_database = nil
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def init_attributes(other) # :nodoc:
|
|
254
|
+
attrs = super
|
|
250
255
|
if self.class.respond_to?(:_default_attributes)
|
|
251
|
-
|
|
252
|
-
attr.with_value_from_user(
|
|
256
|
+
self.class._default_attributes.map do |attr|
|
|
257
|
+
attr.with_value_from_user(attrs.fetch_value(attr.name))
|
|
253
258
|
end
|
|
259
|
+
else
|
|
260
|
+
attrs
|
|
254
261
|
end
|
|
255
|
-
@mutations_from_database = nil
|
|
256
262
|
end
|
|
257
263
|
|
|
258
264
|
def as_json(options = {}) # :nodoc:
|
|
259
|
-
|
|
265
|
+
except = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
|
|
266
|
+
options = options.merge except: except
|
|
260
267
|
super(options)
|
|
261
268
|
end
|
|
262
269
|
|
|
@@ -289,22 +296,22 @@ module ActiveModel
|
|
|
289
296
|
mutations_from_database.changed_attribute_names
|
|
290
297
|
end
|
|
291
298
|
|
|
292
|
-
# Dispatch target for {*_changed?}[rdoc-
|
|
299
|
+
# Dispatch target for {*_changed?}[rdoc-ref:#*_changed?] attribute methods.
|
|
293
300
|
def attribute_changed?(attr_name, **options)
|
|
294
301
|
mutations_from_database.changed?(attr_name.to_s, **options)
|
|
295
302
|
end
|
|
296
303
|
|
|
297
|
-
# Dispatch target for {*_was}[rdoc-
|
|
304
|
+
# Dispatch target for {*_was}[rdoc-ref:#*_was] attribute methods.
|
|
298
305
|
def attribute_was(attr_name)
|
|
299
306
|
mutations_from_database.original_value(attr_name.to_s)
|
|
300
307
|
end
|
|
301
308
|
|
|
302
|
-
# Dispatch target for {*_previously_changed?}[rdoc-
|
|
309
|
+
# Dispatch target for {*_previously_changed?}[rdoc-ref:#*_previously_changed?] attribute methods.
|
|
303
310
|
def attribute_previously_changed?(attr_name, **options)
|
|
304
311
|
mutations_before_last_save.changed?(attr_name.to_s, **options)
|
|
305
312
|
end
|
|
306
313
|
|
|
307
|
-
# Dispatch target for {*_previously_was}[rdoc-
|
|
314
|
+
# Dispatch target for {*_previously_was}[rdoc-ref:#*_previously_was] attribute methods.
|
|
308
315
|
def attribute_previously_was(attr_name)
|
|
309
316
|
mutations_before_last_save.original_value(attr_name.to_s)
|
|
310
317
|
end
|
data/lib/active_model/error.rb
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "active_support/core_ext/class/attribute"
|
|
4
3
|
|
|
5
4
|
module ActiveModel
|
|
6
5
|
# = Active \Model \Error
|
|
@@ -83,7 +82,7 @@ module ActiveModel
|
|
|
83
82
|
defaults << :"#{i18n_scope}.errors.messages.#{type}"
|
|
84
83
|
|
|
85
84
|
catch(:exception) do
|
|
86
|
-
translation = I18n.translate(defaults.first, **options
|
|
85
|
+
translation = I18n.translate(defaults.first, **options, default: defaults.drop(1), throw: true)
|
|
87
86
|
return translation unless translation.nil?
|
|
88
87
|
end unless options[:message]
|
|
89
88
|
else
|
|
@@ -205,4 +204,6 @@ module ActiveModel
|
|
|
205
204
|
[@base, @attribute, @raw_type, @options.except(*CALLBACKS_OPTIONS)]
|
|
206
205
|
end
|
|
207
206
|
end
|
|
207
|
+
|
|
208
|
+
ActiveSupport.run_load_hooks(:active_model_error, Error)
|
|
208
209
|
end
|
data/lib/active_model/errors.rb
CHANGED
|
@@ -5,7 +5,6 @@ require "active_support/core_ext/string/inflections"
|
|
|
5
5
|
require "active_support/core_ext/object/deep_dup"
|
|
6
6
|
require "active_model/error"
|
|
7
7
|
require "active_model/nested_error"
|
|
8
|
-
require "forwardable"
|
|
9
8
|
|
|
10
9
|
module ActiveModel
|
|
11
10
|
# = Active \Model \Errors
|
|
@@ -61,8 +60,6 @@ module ActiveModel
|
|
|
61
60
|
class Errors
|
|
62
61
|
include Enumerable
|
|
63
62
|
|
|
64
|
-
extend Forwardable
|
|
65
|
-
|
|
66
63
|
##
|
|
67
64
|
# :method: each
|
|
68
65
|
#
|
|
@@ -100,7 +97,7 @@ module ActiveModel
|
|
|
100
97
|
#
|
|
101
98
|
# Returns number of errors.
|
|
102
99
|
|
|
103
|
-
|
|
100
|
+
delegate :each, :clear, :empty?, :size, :uniq!, to: :@errors
|
|
104
101
|
|
|
105
102
|
# The actual array of +Error+ objects
|
|
106
103
|
# This method is aliased to <tt>objects</tt>.
|
data/lib/active_model/lint.rb
CHANGED
|
@@ -30,7 +30,7 @@ module ActiveModel
|
|
|
30
30
|
# of the model, and is used to a generate unique DOM id for the object.
|
|
31
31
|
def test_to_key
|
|
32
32
|
assert_respond_to model, :to_key
|
|
33
|
-
|
|
33
|
+
def_method(model, :persisted?) { false }
|
|
34
34
|
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -45,8 +45,8 @@ module ActiveModel
|
|
|
45
45
|
# any of the possible implementation strategies on the implementer.
|
|
46
46
|
def test_to_param
|
|
47
47
|
assert_respond_to model, :to_param
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
def_method(model, :to_key) { [1] }
|
|
49
|
+
def_method(model, :persisted?) { false }
|
|
50
50
|
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -105,6 +105,10 @@ module ActiveModel
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
private
|
|
108
|
+
def def_method(receiver, name, &block)
|
|
109
|
+
::Object.instance_method(:define_singleton_method).bind_call(receiver, name, &block)
|
|
110
|
+
end
|
|
111
|
+
|
|
108
112
|
def model
|
|
109
113
|
assert_respond_to @model, :to_model
|
|
110
114
|
@model.to_model
|
data/lib/active_model/model.rb
CHANGED
|
@@ -54,7 +54,7 @@ module ActiveModel
|
|
|
54
54
|
#
|
|
55
55
|
# person = Person.new(id: 1, name: "bob")
|
|
56
56
|
# person.slice(:id, :name)
|
|
57
|
-
# => { "id" => 1, "name" => "bob" }
|
|
57
|
+
# # => { "id" => 1, "name" => "bob" }
|
|
58
58
|
#
|
|
59
59
|
#--
|
|
60
60
|
# Implemented by ActiveModel::Access#slice.
|
|
@@ -68,7 +68,7 @@ module ActiveModel
|
|
|
68
68
|
#
|
|
69
69
|
# person = Person.new(id: 1, name: "bob")
|
|
70
70
|
# person.values_at(:id, :name)
|
|
71
|
-
# => [1, "bob"]
|
|
71
|
+
# # => [1, "bob"]
|
|
72
72
|
#
|
|
73
73
|
#--
|
|
74
74
|
# Implemented by ActiveModel::Access#values_at.
|
data/lib/active_model/naming.rb
CHANGED