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).