When source and target classes have a different set of attributes adapts
attributes such that the extra attributes from target are added.
Fixes#41195
Co-authored-by: SampsonCrowley <sampsonsprojects@gmail.com>
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
and :to options are not being type cast. For example, for an enum
attribute, attribute_changed? should handle a String, Symbol or Integer
for the :from and :to options.
I was running into a case where I didn't want to just disabled the
validations and add my own. In fact, I would very much like to keep the
default validation but just de-activate it on some scenario:
e.g. Inviting a user without having to set a password for them yet so
they can add it themselves later when they receive an email invitation
to finish setting up their account.
My understanding of the validations flag originally intended was to
just disabled them and if you needed something more custom, you could
run your own validations instead.
This would be an acceptable solution, but it would add more code to my
controller. Instead validations can receive a `Hash` wich is then use to
apply validations rules to `validate`.
This is just a suggestion, I am not sure if there is a need, and I am
aware this PR is probably far from perfect. Any feedback welcome.
EDIT: implemented changes as per feedback.
Though we needed to use constantize for ruby < 2.0, ruby >= 2.0
const_get can handle namespace.
> https://docs.ruby-lang.org/en/2.0.0/NEWS.html
> Module#const_get accepts a qualified constant string, e.g. Object.const_get(“Foo::Bar::Baz”)
Some validators, such as validators that inherit from `EachValidator`,
mutate the options they receive. This can cause problems when passing
multiple validators and options to `validates_with`. This can also be a
problem if a validator deletes standard options such as `:if` and `:on`,
because the validation callback would then not receive them.
This commit modifies `validates_with` to `dup` options before passing
them to validators, thus preventing these issues.
Fixes#44460.
Closes#44476.
Co-authored-by: Dieter Späth <dieter.spaeth@lanes-planes.com>
It would be nice to be able to pattern match against ActiveModel (and
transitively ActiveRecord). If you want to check multiple attributes
with conditions, it's nice to be able use the pattern matching syntax.
For example:
```ruby
case Current.user
in { superuser: true }
"Thanks for logging in. You are a superuser."
in { admin: true, name: }
"Thanks for logging in, admin #{name}!"
in { name: }
"Welcome, #{name}!"
end
```
All validations are called on `#save` by default.
The Absence, Acceptance, Presence validators don't do anything special,
so there is no reason to mention they are called on `#save` by default.
Instead we can mention all validations are called in `#save` by default
in ActiveRecord::Validations.
Co-authored-by: Gannon McGibbon <gannon@hey.com>
In https://github.com/rails/rails/pull/43036 an optimisation was applied
to ActiveModel#Serialization to speed up the generation of a
serialized_hash by avoiding loading the subjects attributes by using an
attribute_names method. A fallback method,
ActiveModel::Serialization#attribute_names` was added as #attribute_names
isn't part of the public API of ActiveModel.
Unfortunately, this fallback method happens to override the ActiveRecord
method (as ActiveModel::Serialization is a later mixin than
ActiveRecord::AttributeMethods), so this change didn't actually provide
an optimisation - the full attribute data was loaded as per [1]
This change also, in our case, produced some severe performance issues
as it introduced an N+1 query in a situation where we had one gem,
Globalize [2], which adds in dynamic attributes that are loaded by a query;
and another gem, Transitions [3], that checks attribute names at
initialization. The combination of these meant that for every model that
was initialized an extra query would run - no matter what includes or
eager_load steps were in place. This rapidly hindered our applications'
performance and meant we had to rollback the Rails 7 upgrade.
Following rafaelfranca's suggestion [4] this adds a
`attribute_names_for_serialization` method to Serialization modules in
ActiveRecord and ActiveModel. This allows the ActiveRecord one to
override the ActiveModel fallback and thus be optimised.
Some basic benchmarks of this follow - they use code from
https://github.com/ollietreend/rails-demo and have some pretty large
arrays set as serialized attributes [5] to demonstrate impacts.
Loading attribute names:
Rails 7.0.2.3
```
> Benchmark.ms { Widget.all.map(&:attribute_names) }
Widget Load (131.1ms) SELECT "widgets".* FROM "widgets"
=> 20108.852999983355
```
This patch
```
> Benchmark.ms { Widget.all.map(&:attribute_names) }
Widget Load (144.0ms) SELECT "widgets".* FROM "widgets"
=> 237.96699999365956
```
Using serializable_hash:
Rails 7.0.2.3
```
> widgets = Widget.all.to_a; Benchmark.ms { widgets.map { |w| w.serializable_hash(only: []) } }
Widget Load (133.3ms) SELECT "widgets".* FROM "widgets"
=> 22071.45000001765
```
This patch
```
> widgets = Widget.all.to_a; Benchmark.ms { widgets.map { |w| w.serializable_hash(only: []) } }
Widget Load (83.5ms) SELECT "widgets".* FROM "widgets"
=> 67.9039999959059
```
[1]: eeb2cfb686/activemodel/lib/active_model/serialization.rb (L151-L154)
[2]: https://github.com/globalize/globalize
[3]: https://github.com/troessner/transitions
[4]: https://github.com/rails/rails/pull/44770#pullrequestreview-922209612
[5]: 525f88887b/db/seeds.rb
As a step toward sharing more code between Active Model and Active
Record, this commit factors an `ActiveModel::AttributeRegistration`
module out of `ActiveModel::Attributes`. This module is marked as
`nodoc` and is for internal use only.
Additionally, this commit adds thorough test coverage of attribute
registration and inheritance. (`activemodel/test/cases/attributes_test.rb`
does already test some of this behavior, but it is focused on high-level
functionality.)
This enhances `has_secure_password` to define a `password_challenge`
accessor and the appropriate validation. When `password_challenge` is
set, the validation checks that it matches the currently *persisted*
`password_digest` (i.e. `password_digest_was`).
This allows a password challenge to be implemented with the same ease as
a password confirmation, re-using the same error handling logic in the
view, as well as the controller. For example, in the controller,
instead of:
```ruby
password_params = params.require(:password).permit(
:password_challenge,
:password,
:password_confirmation,
)
password_challenge = password_params.delete(:password_challenge)
@password_challenge_failed = !current_user.authenticate(password_challenge)
if !@password_challenge_failed && current_user.update(password_params)
# ...
end
```
One could write:
```ruby
password_params = params.require(:password).permit(
:password_challenge,
:password,
:password_confirmation,
).with_defaults(password_challenge: "")
if current_user.update(password_params)
# ...
end
```
And, in the view, instead of checking `@password_challenge_failed`, one
could render a password challenge error in the same manner as other form
field errors, including utilizing `config.action_view.field_error_proc`.
RDoc will automatically format and link API references as long as they
are not already marked up as inline code.
This commit removes markup from various API references so that those
references will link to the relevant API docs.
"Overwrite" means "destructively replace", and is more suitable when,
for example, talking about writing data to a location.
"Override" means "supersede", and is more suitable when, for example,
talking about redifining methods in a subclass.
This commit adds documentation to the constants and methods that are
part of Active Model's Attributes API. So far this API has been hidden
with the :nodoc: flag since its inception in Active Record and
subsequent move to Active Model (#30920 and #30985); as the API matures
and gets ready for public usage, visible documentation for its endpoints
becomes necessary.
The classes and modules being documented and publicized by this commit
are the main `Attributes` module, the `Type` namespace, and all the
standard attribute type classes included in the current API, which users
will be able to extend and replicate to suit their customization needs.
Some private modules are also receiving documetation, although they will
continue using the :nodoc: flag. Those are `Attribute` and
`AttributeSet`. Although they remain private I found useful to add some
comments to describe their responsibilities.
Ruby 3.1 introduced an optimization to string interpolation for some
core classes in b08dacfea3.
But since we override `to_s` in some of those core classes to add behavior
like `to_s(:db)`, all Rails applications will not be able to take advantage
of that improvement.
Since we can use the `to_formatted_s` alias for the Rails specific behavior
it is best for us to deprecate the `to_s` core extension and allow Rails
applications to get the proformace improvement.
This commit starts removing all the `to_s(:db)` calls inside the framework
so we can deprecate the core extension in the next commit.
Prior to this change, attempting to merge an `ActiveModel::Error` instance
with itself would result in an endless loop where `ActiveModel::NestedError`s
would continue to be imported on the instance until interrupted. Though the
merging of identical objects is less likely to happen in practice, this method
should still be able to handle such a case gracefully. This change ensures
that instances attempting to merge on themselves return early rather than
hanging indefinitely.
Addresses https://github.com/rails/rails/issues/43737
The bulk of the optimization is to generate code rather than use
`define_method` with a closure.
```
Warming up --------------------------------------
original 207.468k i/100ms
code-generator 340.849k i/100ms
Calculating -------------------------------------
original 2.127M (± 1.1%) i/s - 10.788M in 5.073860s
code-generator 3.426M (± 0.9%) i/s - 17.383M in 5.073965s
Comparison:
code-generator: 3426241.0 i/s
original: 2126539.2 i/s - 1.61x (± 0.00) slower
```
```ruby
require 'benchmark/ips'
require 'active_support/all'
class Original < ActiveSupport::CurrentAttributes
attribute :foo
end
class CodeGen < ActiveSupport::CurrentAttributes
class << self
def attribute(*names)
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
names.each do |name|
owner.define_cached_method(name, namespace: :current_attributes) do |batch|
batch <<
"def #{name}" <<
"attributes[:#{name}]" <<
"end"
end
owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch|
batch <<
"def #{name}=(value)" <<
"attributes[:#{name}] = value" <<
"end"
end
end
end
ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
names.each do |name|
owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
batch <<
"def #{name}" <<
"instance.#{name}" <<
"end"
end
owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
batch <<
"def #{name}=(value)" <<
"instance.#{name} = value" <<
"end"
end
end
end
end
end
attribute :foo
end
Benchmark.ips do |x|
x.report('original') { Original.foo }
x.report('code-generator') { CodeGen.foo }
x.compare!
end
```
Along the same lines as ccb3cb573b, this commit removes unnecessary
references to mental health.
As in that commit, I think many of these are more descriptive than what
we had before.
The commit changes only tests and documentation.
Currently `ActiveModel::Model` is defined as the minimum API to talk
with Action Pack and Action View.
Its name suggests it can be included to create Active Record type
models, but for creating models it's probably too minimal. For example
it's very common to include ActiveModel::Attributes as well.
By moving `ActiveModel::Model`'s implementation to a new
`ActiveModel::API` we keep a definition of the minimum API to talk with
Action Pack and Action View.
For `ActiveModel::Model` we only need to include `ActiveModel::API`.
This will allow adding more funcationality to `ActiveModel::Model` while
keeping backwards compatibility.
Co-authored-by: Nathaniel Watts <1141717+thewatts@users.noreply.github.com>
for ActiveRecord objects, it will force the entire attributes hash to
be constructed, which may include expensive deserialization.
ActiveModel already has a simple attribute_names method defined which
returns attributes.keys, exactly like the code we're replacing.
ActiveRecord overrides that method, and returns the names of the
attributes _without_ having to deserialize any values.
this change can save a lot of CPU if you have a serialized column
on a model that you often load from the DB because it's not worth
the effort to customize the SELECT on every relation, but
also rarely expose in any JSON serializations (i.e. have a default
except option for it).
* Without the change the new test fails like this:
Failure:
ActiveModel::TypeTest#test_registering_a_new_type [test/cases/type_test.rb:21]:
Expected: #<struct args={}>
Actual: #<struct args=nil>
* (*args, **kwargs)-delegation is not correct on Ruby 2.7 unless the
target always accepts keyword arguments (not the case for `Struct.new(:args).new`).
See https://eregon.me/blog/2021/02/13/correct-delegation-in-ruby-2-27-3.html
* Without the change the new test fails like this:
Failure:
ActiveModel::Type::RegistryTest#test_a_class_can_be_registered_for_a_symbol [test/cases/type/registry_test.rb:16]:
Expected: [{}, {}]
Actual: [nil, nil]
* (*args, **kwargs)-delegation is not correct on Ruby 2.7 unless the
target always accepts keyword arguments (not the case for `Array.new`).
See https://eregon.me/blog/2021/02/13/correct-delegation-in-ruby-2-27-3.html
Ruby master ships with Psych 4.0.0 which makes `YAML.load`
defaults to safe mode (https://github.com/ruby/psych/pull/487).
However since these YAML files are trustworthy sources
we can parse them with `unsafe_load`.
`codespell` works with a small custom dictionary and seems to find perhaps more spelling mistakes than `misspell` which really only fixes commonly misspelled English words.
Not all spell checkers can check all file types and most spell checkers can't find all the errors.
https://github.com/codespell-project/codespellhttps://pypi.org/project/codespell/
Somehow it isn't caused on CI, but it consistently causes on locally.
```
% bin/test -w
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:85: warning: method redefined; discarding old validates_each
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:85: warning: previous definition of validates_each was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:89: warning: already initialized constant ActiveModel::Validations::ClassMethods::VALID_OPTIONS_FOR_VALIDATE
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:89: warning: previous definition of VALID_OPTIONS_FOR_VALIDATE was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:152: warning: method redefined; discarding old validate
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:152: warning: previous definition of validate was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:192: warning: method redefined; discarding old validators
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:192: warning: previous definition of validators was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:234: warning: method redefined; discarding old clear_validators!
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:234: warning: previous definition of clear_validators! was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:254: warning: method redefined; discarding old validators_on
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:254: warning: previous definition of validators_on was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:270: warning: method redefined; discarding old attribute_method?
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:270: warning: previous definition of attribute_method? was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:275: warning: method redefined; discarding old inherited
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:275: warning: previous definition of inherited was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:283: warning: method redefined; discarding old initialize_dup
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:283: warning: previous definition of initialize_dup was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:301: warning: method redefined; discarding old errors
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:301: warning: previous definition of errors was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:373: warning: method redefined; discarding old invalid?
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:373: warning: previous definition of invalid? was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:382: warning: method redefined; discarding old validate!
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:382: warning: previous definition of validate! was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:405: warning: method redefined; discarding old run_validations!
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:405: warning: previous definition of run_validations! was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:410: warning: method redefined; discarding old raise_validation_error
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:410: warning: previous definition of raise_validation_error was here
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:426: warning: method redefined; discarding old model
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:428: warning: method redefined; discarding old initialize
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:428: warning: previous definition of initialize was here
Traceback (most recent call last):
23: from bin/test:5:in `<main>'
22: from bin/test:5:in `require_relative'
21: from /Users/kamipo/src/github.com/rails/rails/tools/test.rb:18:in `<top (required)>'
20: from /Users/kamipo/src/github.com/rails/rails/railties/lib/rails/test_unit/runner.rb:40:in `run'
19: from /Users/kamipo/src/github.com/rails/rails/railties/lib/rails/test_unit/runner.rb:52:in `load_tests'
18: from /Users/kamipo/src/github.com/rails/rails/railties/lib/rails/test_unit/runner.rb:52:in `each'
17: from /Users/kamipo/src/github.com/rails/rails/railties/lib/rails/test_unit/runner.rb:52:in `block in load_tests'
16: from /Users/kamipo/src/github.com/rails/rails/railties/lib/rails/test_unit/runner.rb:52:in `require'
15: from /Users/kamipo/src/github.com/rails/rails/activemodel/test/cases/attributes_dirty_test.rb:5:in `<top (required)>'
14: from /Users/kamipo/src/github.com/rails/rails/activemodel/test/cases/attributes_dirty_test.rb:6:in `<class:AttributesDirtyTest>'
13: from /Users/kamipo/src/github.com/rails/rails/activemodel/test/cases/attributes_dirty_test.rb:7:in `<class:DirtyModel>'
12: from /Users/kamipo/src/github.com/rails/rails/activemodel/test/cases/attributes_dirty_test.rb:7:in `require'
11: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/model.rb:3:in `<top (required)>'
10: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/model.rb:59:in `<module:ActiveModel>'
9: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/model.rb:62:in `<module:Model>'
8: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/model.rb:62:in `require'
7: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:436:in `<top (required)>'
6: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:436:in `each'
5: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:436:in `block in <top (required)>'
4: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations.rb:436:in `require'
3: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations/numericality.rb:5:in `<top (required)>'
2: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations/numericality.rb:6:in `<module:ActiveModel>'
1: from /Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations/numericality.rb:7:in `<module:Validations>'
/Users/kamipo/src/github.com/rails/rails/activemodel/lib/active_model/validations/numericality.rb:8:in `<class:NumericalityValidator>': uninitialized constant ActiveModel::Validations::NumericalityValidator::Comparability (NameError)
```
This saves some array allocations from avoiding `*args`, as well
as makes the Method object `arity` and `parameters` correct.
e.g. before this patch, ArgumentError would be confusing:
```ruby
>> model.name_was(1)
ArgumentError: wrong number of arguments (given 2, expected 1)
```
ActiveRecord::Type::Registry doesn't need to inherit from
ActiveModel::Type::Registry, and it makes both classes more simple.
Co-authored-by: Adrianna Chang <adrianna.chang@shopify.com>
Since Ruby 2.7 `self.some_private_method` works fine.
So now that Ruby 2.7 is the minimal supported version,
`define_proxy_call` can always prepend `self.`
I used `type.cast(value)` to emulate unchecked serialized value in
`unboundable?`, since `RangeError` was raised only for the integer type,
so the emulation works enough for the integer type.
But since #41516, Enum types are also not always serializable, so
`type.cast(value)` may also be called for the enum types.
I've delegated `type.cast(value)` to the subtype if an unknown label is
passed to work the emulation even on Enum types in 3b6461b. But it is
strange to delegate to the subtype for the emulation only if an unknown
label is passed.
Instead of using `type.cast(value)` for the emulation, extend
`serializable?` to get unchecked serialized value if the value is not
serializable.
Since a `BindParam` object always has an attribute object as the value
in the Active Record usage, so `_insert_record`/`_update_record` could
be passed attribute set instead of wrapping casted value by a query
attribute.
RDoc Markup does not support backticks the way Markdown does to mark up
inline code. Additionally, `<tt>` must be used to mark up inline code
that includes spaces or certain punctuation characters (e.g. quotes).
These methods have changed in Ruby 2.5 to be more akin to grep:
https://bugs.ruby-lang.org/issues/11286
Using classes seems to be faster (and a bit more expressive) than iterating over
the collection items:
```
Warming up --------------------------------------
#all? with class 504.000 i/100ms
#all? with proc 189.000 i/100ms
Calculating -------------------------------------
#all? with class 4.960k (± 1.6%) i/s - 25.200k in 5.082049s
#all? with proc 1.874k (± 2.8%) i/s - 9.450k in 5.047866s
Comparison:
#all? with class: 4959.9 i/s
#all? with proc: 1873.8 i/s - 2.65x (± 0.00) slower
```
Benchmark script:
```ruby
require "minitest/autorun"
require "benchmark/ips"
class BugTest < Minitest::Test
def test_enumerators_with_classes
arr = (1..10000).to_a << nil
assert_equal arr.all?(Integer), arr.all? { |v| v.is_a?(Integer) }
Benchmark.ips do |x|
x.report("#all? with class") do
arr.all?(Integer)
end
x.report("#all? with proc") do
arr.all? { |v| v.is_a?(Integer) }
end
x.compare!
end
end
end
```
We allow for compare validations in NumericalityValidator, but these
only work on numbers. There are various comparisons people may want
to validate, from dates to strings, to custom comparisons.
```
validates_comparison_of :end_date, greater_than: :start_date
```
Refactor NumericalityValidator to share module Comparison with ComparabilityValidator
* Move creating the option_value into a reusable module
* Separate COMPARE_CHECKS which support compare functions and accept values
* Move odd/even checks to NUMBER_CHECKS as they can only be run on numbers
If unknow type is given for attribute (`attribute :foo, :unknown`),
unknown type error isn't raised on the definition time but runtime.
It should be raised on the definition time.
f72f743 introduces truncate(scale) in the Numericality validator.
This behaviour conflicts with AR decimal type conversion,
which uses round(scale) instead.
Changes the Numericality validator in order to use
round(scale) for consistency.
The ability has lost due to reverted #39321 in #41049.
We should allow updating with dirty locking value to work the documented
usage, but if casted value has no difference (i.e. regarded as no dirty),
identify the object by the original (uninitialized default) value.
In some cases, the framework was mutating the :if option of callbacks.
Since #38323, those options are frozen, so the framework could raise
exception when trying to mutate those options if they were being resued
with method like `with_options`.
It is a regression for 4cc438a1df75e4c230f19cafe9258dbab969cd27.
`NumericalityValidator` basically takes the value before typecasting,
but `allow_nil` should work for the typecasted value for the
compatibility.
Fixes#40750.
This reverts commit d93a5d385e5bc2392a1f47dc2885e353898b62e1.
Revert "Revert "Remove unused internal methods in ActiveModel::Attributes""
This reverts commit 2d7967204e7f7d5ba846b0a6ed51088c7a7db365.
Reason: read_attribute was added in 6.1 as a performance optimization
and it is not needed anymore and write_attribute only existed to make
possible to call something that is not `attribute=` with send. We don't
need those methods internally and since they were never part of the
public API we can remove them.
There are validation cases in which the human_attribute_name depends on
other fields of the base class.
For instance, an Address model that depends on the selected country to
localize the attribute name to be shown in error messages. E.g. the
:address1 and :address2 attributes can be displayed as very different
strings depending on whether the address is in the US or in Japan.
The model global `read_attribute_for_validation` is not fit for some
validators (specifically for numericality validator).
To allow per-validator customization for attribute value, it extracts
`read_attribute_for_validation` in `EachValidator`.
This makes `datetime.serialize` about 10% faster.
```ruby
type = ActiveRecord::Type.lookup(:datetime)
time = Time.now.utc
Benchmark.ips do |x|
x.report("type.serialize(time)") do
type.serialize(time)
type.serialize(time)
type.serialize(time)
type.serialize(time)
end
end
```
Before:
```
Warming up --------------------------------------
type.serialize(time) 12.899k i/100ms
Calculating -------------------------------------
type.serialize(time) 131.293k (± 1.6%) i/s - 657.849k in 5.011870s
```
After:
```
Warming up --------------------------------------
type.serialize(time) 14.603k i/100ms
Calculating -------------------------------------
type.serialize(time) 145.941k (± 1.1%) i/s - 730.150k in 5.003639s
```