* Save cache size by omit the prefix if unnecessary
* rename to straightforward naming.
* check the prefix directly instead of inspect
* Remove unused helper method
* add to changelog
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
Follow-up to #50677.
Prior to this commit, all `ActiveSupport::CurrentAttributes` subclasses
stored their default values in the same `Hash`, causing default values
to leak between classes. This commit ensures each subclass maintains a
separate `Hash`.
This commit also simplifies the resolution of default values, replacing
the `merge_defaults!` method with `resolve_defaults`.
For example:
```ruby
delegate :negative?, to: :value, as: Numeric
```
Before:
```
def negative?(&block)
_ = @value
_.negative?(&block)
rescue NoMethodError => e
if _.nil? && e.name == :negative?
raise DelegationError, "ActiveSupport::Duration#negative? delegated to @value.negative?, but @value is nil: #{self.inspect}"
else
raise
end
end
```
After:
```ruby
def negative?(&block)
_ = @value
_.negative?(&block)
rescue NoMethodError => e
if _.nil? && e.name == :negative?
raise DelegationError.nil_target(:negative?, :"@value")
else
raise
end
end
```
Before almost every delegator would generate a large unique string that gets interned for
the error message that is rarely if ever used.
Rather than to "hardcode" a unique string, we now only pass pre-existing symbols to
a method helper that will build the error message.
This alone saves about 160B per delegator, and the method bytecode is also marginally
smaller (but it's harder to assess how much this actually saves)
Replacing on the fly a `method_missing` by a generated method
sound like a nice trick, but it's not as good as it sound for
optimization, as the method will be generated by the first
request to use it, preventing the ISeq from being is shared memory.
Instead we can eagerly define a delegator when instance methods
are defined, and keep a regular `method_missing + send` for the
very rare cases not covered.
Co-Authored-By: Jean Boussier <jean.boussier@gmail.com>
Extend the `.attribute` class method to accept a `:default` option for
its list of attributes:
```ruby
class Current < ActiveSupport::CurrentAttributes
attribute :counter, default: 0
end
```
Internally, `ActiveSupport::CurrentAttributes` will maintain a
`.defaults` class attribute to determine default values during instance
initialization.
Now that we require Ruby 3.1, we can assume `Process._fork` is
defined on MRI, hence we can trust that our decorator will
reliably detect forks so we no longer need to check the if
the pid changed in critical spots.
Using Symbol#name allows to hit two birds with one stone.
First it will return a pre-existing string, so will save
one allocation per key.
Second, that string will be already interned, so it will
save the internal `Hash` implementation the work of looking
up the interned strings table to deduplicate the key.
```
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin21]
Warming up --------------------------------------
to_s 17.768k i/100ms
cond 23.703k i/100ms
Calculating -------------------------------------
to_s 169.830k (±10.4%) i/s - 852.864k in 5.088377s
cond 236.803k (± 7.9%) i/s - 1.185M in 5.040945s
Comparison:
to_s: 169830.3 i/s
cond: 236803.4 i/s - 1.39x faster
```
```ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'benchmark-ips', require: false
end
HASH = {
first_name: nil,
last_name: nil,
country: nil,
profession: nil,
language: nil,
hobby: nil,
pet: nil,
longer_name: nil,
occupation: nil,
mailing_address: nil,
}.freeze
require 'benchmark/ips'
Benchmark.ips do |x|
x.report("to_s") { HASH.transform_keys(&:to_s) }
x.report("cond") { HASH.transform_keys { |k| Symbol === k ? k.name : k.to_s } }
x.compare!(order: :baseline)
end
```
The introduction of the block argument means that `Object#with` can now
accept a `Symbol#to_proc` as the block argument:
```ruby
client.with(timeout: 5_000) do |c|
c.get("/commits")
end
```
Now that we dropped support for Ruby 2.7, we no longer
need to check if variables are defined before accessing them
to avoid the undefined variable warning.
Now that we no longer support Ruby 2.7, many `ruby2_keyword` calls
can be eliminated.
The ones that are left could be eliminated but would end up substantially
slower or more compliacated so I left them for now.
When `parallelize` is used, and the ActiveSupport::Testing::ParallelizeExecutor
is being set, we can't call `parallelize_me!` ourselves since the
parallel executor might not be set to run tests in parallel.
In fact, maybe we should stop calling `parallelize_me!` altogether here
and rely on `parallelize` to do the right thing, but this might break
test classes that are using `ActiveSupport::Testing::Isolation` outside
without calling `parallelize`.
This changes the default number of workers when parallelizing tests to
be based on logical core count instead of physical core count. This may
provide a performance boost for parallelized tests.
This also reverts #50545 in favor of using the default worker count.
- tweak opening paragraph to specifically mention that the Redis backing
a Cache should not be the Redis backing Active Job
- add code blocks to Redis::Distributed since it uses the class syntax
- expand on ways to point Cache at a Redis in initialization
- add an rdoc link for #fetch and a code block for `-amount`
ActiveSupport::Deprecation used to `include Singleton` which is why the require is here, but it was removed in 9812641891f1c9ba4c3f8ffb8549ec26fc2668c4 (June 5 2023). Leaving the require seems to have been an oversight.
However, module ActiveJob::Serializers::ObjectSerializer does require Singleton and was relying on AS:Deprecation to require it. This PR just moves the require to the place it's actually used.
- Link to Deprecation::Behavior in configuring guide
The current list of options for `config.active_support.deprecation`
was missing the newly added `:report` option. Instead of adding the
missing option and continuing to keep 4 different lists of the same
options in sync, I opted to replace the list with a link to the
options in the Behavior API docs. This had the additional advantage of
giving more information about all of the options which was not
mentioned in the Configuring guide.
- Use symbols for Behavior options
It felt to me like naming the options did not make it explicit that
those were the symbols to pass to `#behavior=`, but by adding the `:`
that becomes more clear.
- Add some API links
There were a few references to `behavior=`, but we may as well link to
the actual method.
Fix: https://github.com/rails/rails/pull/50136
Using delegate saves on the `method_missing + public_send` combo.
I chose to delegate to all the methods I saw called in Rails own test
suite, but there is likely a handful more candidates for explicit delegation.
But also this rely on a new (private) parameter in `Module#delegate` to
provide the expected delegated type and use that to define delegators
with the extact required signature. This saves on array and hash allocations
caused by splatting.
In some ways it's a continuation of https://github.com/rails/rails/pull/46875
Note that I didn't make the new `as:` parameter public, as I fear it's
a bit too brittle to be used. For the same reason I'm considering reverting
the optimized path behavior on `to: :class` and requiring to explictly pass
`as: self` for that optimized path.
Co-Authored-By: Alexandre Terrasa <alexandre.terrasa@shopify.com>
This is a continuation of https://github.com/rails/rails/pull/46875
The behavior of looking up the class method when `to: :class` is passed
is a bit error prone because it silently degrades.
By passing the expected owner of the delegated method, we can be more
strict, and also generate a delegator in a module rather than having
to do it at inclusion time.
I made this argument private API because we want it in Rails, but
I'm worried it might be a bit too sharp for public API. I can
be convinced otherwise though.