* Add `Enumerable#sole`, from `ActiveRecord::FinderMethods#sole`
* distinguish single-item Enumerable and two-item with nil last
Add a test for same.
* add symmetry, against rubocop's wishes
By pushing the checks for TZInfo::Timezone and ActiveSupport::TimeZone
down into the case statement inside the `[]` method we can radically
simplify the logic inside of the `find_timezone!` method.
There's an edge case where passing an invalid argument to `[]` via the
`find_timezone!` method generates a slightly different message for the
`ArgumentError` exception and this is maintained in the unlikely case
someone was relying on the difference.
When utc_to_local_returns_utc_offset_times is false and the time
instance had fractional seconds the new UTC time instance was out
by a factor of 1,000,000 as the Time.utc constructor takes a usec
value and not a fractional second value.
When deep merging a Hash, first a duplicate is created. The duplicate only
does a shallow copy, so deeper level structures are not duplicated but
remain references.
This can lead to unexpected modifications of the original hash when a deeply
nested hash is merged with another hash, and the newly returned hash gets modified.
```
x = { a: { b: "foo" } }
y = { d: { e: "bar" } }
z = x.deep_merge(y)
# z => { a: { b: "foo" }, d: { e: "bar" } }
z[:a][:b] = "baz"
# z => { a: { b: "baz" }, d: { e: "bar" } }
# x => { a: { b: "baz" } }
```
All the complexity of that method was to work around various
problems caused by Ruby's constant lookup semantic as well
as the classic autoloader shortcommings.
Now that Rails require Ruby 2.7 I don't think we need anything
more than just `Object.const_get`.
```ruby
require 'benchmark/ips'
require 'active_support/all'
module Foo
module Bar
module Baz
end
end
end
def patched_constantize(name)
Object.const_get(name)
end
Benchmark.ips do |x|
x.report('orig') { ActiveSupport::Inflector.constantize("Foo::Bar::Baz") }
x.report('patched') { patched_constantize("Foo::Bar::Baz") }
x.compare!
end
```
```
Warming up --------------------------------------
orig 69.668k i/100ms
patched 391.385k i/100ms
Calculating -------------------------------------
orig 705.027k (± 1.9%) i/s - 3.553M in 5.041486s
patched 3.935M (± 1.1%) i/s - 19.961M in 5.072912s
Comparison:
patched: 3935235.5 i/s
orig: 705027.2 i/s - 5.58x (± 0.00) slower
```
The default behavior of the Psych gem for Ruby classes is to call the
`name` method to generate a tag for encoding. However this causes a
stream of deprecation warnings whenever ActiveSupport::TimeWithZone
instances are encoded into YAML. By utilising the load_tags/dump_tags
configuration we can prevent Psych from calling the `name` method and
thereby prevent the triggering of the deprecation warnings.
Rails now delegates autoloading to Zeitwerk, and therefore does not need to test
autoloading itself. Zeitwerk has test coverage, in Rails we only need to test
the integration.
We are gradually trimming AS::Dependencies, and the AS test suite. With the
removal of DependenciesTestHelpers and client code in af27a25, these fixtures
became orphan.
Three of them are left. They are to be autoloaded with Module#autoload because
they raise errors when the file is evaluated. Their current use cases are
already committed.
Sometime it can be useful to set a cache entry expiry
not relative to current time, but as an absolute timestamps,
e.g.:
- If you want to cache an API token that was provided to
you with a precise expiry time.
- If you want to cache something until a precise cutoff
time, e.g. `expires_at: Time.now.at_end_of_hour`
This leaves the `@created_at` variable in a weird state,
but this is to avoid breaking the binary format.
In c00f2d2 the `name` method was overridden to return 'Time' instead of
the real class name 'ActiveSupport::TimeWithZone'. The reasoning for
this is unclear and it can cause confusion for developers assuming that
name is returning the real class name. Since we don't know why this
was added, we're deprecating the method first to give developers a
chance to provide us with feedback and look to fix any issues that arise.
The rewritten test is not super clean with the manual cleanup etc.. If this is a
one-off it's not a big deal. However, if in subsequent rewrites I spot more
occurrences of this pattern, then I'll refactor.
Reverts a change from
2327ebfdc6
which can overwrite `test_order` that may have been manually set in
config. This can cause a situation where the user is depending on a
particular `test_order` but is unknowingly forced into another.
`case format when nil` is very efficient because it end up calling `NilClass === nil`
which pretty much translates to `nil.is_a?(NilClass)`.
On the other hand `format.nil?` benefit from a dedicated op code, so it's quite faster.
In this case `Integer#to_s` is much more often called without any arguments,
so it's worth optimizing for the most common case.
```ruby
class Integer
alias_method :faster_to_s, :to_s
end
require 'active_support/all'
require 'benchmark/ips'
module FasterNumericWithFormat
def faster_to_s(format = nil, options = nil)
if format.nil?
return super()
end
case format
when Integer, String
super(format)
when :phone
ActiveSupport::NumberHelper.number_to_phone(self, options || {})
when :currency
ActiveSupport::NumberHelper.number_to_currency(self, options || {})
when :percentage
ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
when :delimited
ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
when :rounded
ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
when :human
ActiveSupport::NumberHelper.number_to_human(self, options || {})
when :human_size
ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
when Symbol
super()
else
super(format)
end
end
end
Integer.prepend(FasterNumericWithFormat)
Benchmark.ips do |x|
x.report('orig no-arg') { 42.to_s }
x.report('fast no-arg') { 42.faster_to_s }
x.compare!
end
Benchmark.ips do |x|
x.report('orig :human') { 42.to_s(:human) }
x.report('fast :human') { 42.faster_to_s(:human) }
x.compare!
end
```
Ruby 2.7.2
```
Warming up --------------------------------------
orig no-arg 567.569k i/100ms
fast no-arg 692.636k i/100ms
Calculating -------------------------------------
orig no-arg 5.709M (± 1.3%) i/s - 28.946M in 5.070660s
fast no-arg 6.892M (± 0.7%) i/s - 34.632M in 5.024961s
Comparison:
fast no-arg: 6892287.7 i/s
orig no-arg: 5709450.0 i/s - 1.21x (± 0.00) slower
Warming up --------------------------------------
orig :human 575.000 i/100ms
fast :human 619.000 i/100ms
Calculating -------------------------------------
orig :human 6.176k (± 1.6%) i/s - 31.050k in 5.028656s
fast :human 6.179k (± 1.8%) i/s - 30.950k in 5.010372s
Comparison:
fast :human: 6179.1 i/s
orig :human: 6176.3 i/s - same-ish: difference falls within error
```
LogSubscriber overrides start/finish to avoid instrumenting when its
logger is nil. In order to support buffered notification events, as used
by async queries, we need to apply a similar override to
LogSubscriber#publish_event.
Date._iso8601 will return a hash for some values eg. '12936' but this will not contain the expected :mon and :mday keys.
Check for these keys and raise an ArgumentError if they aren't present as this is consistent with other invalid input behaviour.
OrderedHash is deprecated but there are some requires and references
to OrderedHash, which might be confusing.
As described in
https://github.com/rails/rails/issues/22681#issuecomment-166059717
OrderedHash is internal only so references in the docs should be
removed.