Using ActiveSupport::LogSubscriber#color inside a custom log subscriber
causes NoMethodError.
```ruby
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "activesupport"
end
require "active_support"
class TestLogSubscriber < ActiveSupport::LogSubscriber
attach_to :test
def hi(event)
info(color(event.payload[:message], GREEN))
end
private
def log_exception(name, e)
super
raise e
end
end
ActiveSupport::LogSubscriber.logger = ActiveSupport::Logger.new(STDOUT)
ActiveSupport::Notifications.instrument("hi.test", message: "Hello!")
```
```
/rails/activesupport/lib/active_support/log_subscriber.rb:193:in `mode_from': undefined method `compact_blank' for an instance of Hash (NoMethodError)
modes = MODES.values_at(*options.compact_blank.keys)
^^^^^^^^^^^^^^
```
For what it's worth, I have encountered this while using kredis locally, like;
```
$ cd kredis
$ bin/console
irb(main):001> Kredis.string "mystring"
Could not log "meta.kredis" event. NoMethodError: undefined method `compact_blank' for an instance of Hash
```
Fixed incorrect documentation for `ActiveSupport::Logger.logger_outputs_to?`. The method expects the first argument to be a Logger object and subsequent variadic arguments to be either IO objects or strings representing file paths.
Also corrected the sample code in CHANGELOG.md, which previously only passed a single argument, not reflecting the correct usage.
related PR: https://github.com/rails/rails/pull/51125
The original example has race condition issue that the output of the example isn't conistent, see https://github.com/rails/rails/issues/43588.
The change improves the example by removing unnecessary thread and add detailed comments.
In the new example, there will be race condition only if `ActiveSupport::Cache::MemoryStore` took longer than 0.1 second to extend expiry but that's unlikely to happen.
Close#43588
See: https://bugs.ruby-lang.org/issues/20250
The bug exist all the way since Ruby 2.7, if you `clone` a `Proc`
object on which you already accessed `object_id`, when its clone
is GCed Ruby will crash.
By accessing the clone's `object_id` right away, we prevent the
crash.
The `create` method is currently marked as an alias of `new`. However,
because `new` is later overridden, it's no longer an alias.
This requires wrapping the `alias_method` with stopdoc/startdoc, as the
method is still marked as an alias otherwise (adding `:nodoc:` doesn't
help).
The `initialize` method has to be wrapped with stopdoc/startdoc as well,
as the `new` method will still be documented for the initialized.
Adding a `:nodoc:` instead will remove documentation for all following
methods.
Managed to reproduce CI failure at https://buildkite.com/rails/rails-nightly/builds/133#018d7bb8-8a32-4978-8e36-d7cb9b067813/1196-1204 . It would also reproduce against the released versions of Ruby because this is triggered by minitest v5.22.0 change. ebb468c81c
To avoid all of railties CI failures, pin minitest version to 5.21 tentatively.
* Steps to reproduce
```ruby
git clone https://github.com/rails/rails
cd rails
rm Gemfile.lock
bundle install
cd railties
bin/test test/application/test_runner_test.rb -n test_system_tests_are_not_run_with_the_default_test_command
```
* Expected behavior
It should pass.
* Actual behavior
```ruby
$ bin/test test/application/test_runner_test.rb -n test_system_tests_are_not_run_with_the_default_test_command
Run options: -n test_system_tests_are_not_run_with_the_default_test_command --seed 14574
F
Failure:
ApplicationTests::TestRunnerTest#test_system_tests_are_not_run_with_the_default_test_command [test/application/test_runner_test.rb:1191]:
Expected /0\ runs,\ 0\ assertions,\ 0\ failures,\ 0\ errors,\ 0\ skips/ to match "Nothing ran for filter: \nRunning 0 tests in a single process (parallelization threshold is 50)\nRun options: --seed 45713\n\n# Running:\n\n".
bin/test test/application/test_runner_test.rb:1179
Finished in 9.982314s, 0.1002 runs/s, 0.2004 assertions/s.
1 runs, 2 assertions, 1 failures, 0 errors, 0 skips
$
```
* Switch ActiveSupport::TestCase teardown and setup callbacks to run in setup and teardown minitest lifecycle hooks.
Minitest provides `setup` and `teardown` lifecycle hooks to run code in. In general it is best practice to when defining your own test case class, to use `Minitest::TestCase.setup` and `Minitest::TestCase.teardown` instead of `before_setup` and `after_teardown`.
Per Minitest's Documentation on Lifecycle Hooks: https://docs.ruby-lang.org/en/2.1.0/MiniTest/Unit/LifecycleHooks.html
> before_setup()
> Runs before every test, before setup. This hook is meant for libraries to extend minitest. It is not meant to be used by test developers.
> after_teardown()
> Runs after every test, after teardown. This hook is meant for libraries to extend minitest. It is not meant to be used by test developers.
Since the `setup` and `teardown` ActiveSupport::TestCase callbacks are in essence user code, it makes sense to run during their corresponding Minitest Lifecycle hooks.
* Ensure test fixutres are torndown on errors in superclass after_teardown code.
By not adding wrapping the `teardown_fixtures` code, its possible that super raises an error and prevents the existing database transaction from rolling back.
`super` in general should only be calling `Minitest::Testcase.after_teardown` however, if another library were to override `Minitest::Testcase.after_teardown`, like the popular gem [rspec-mocks](https://github.com/rspec/rspec-mocks/blob/main/lib/rspec/mocks/minitest_integration.rb#L23) does, it causes all subsequent tests to retain any changes that were made to the database in the original test that errors.
* Remove unnecessary setup and teardown methods in tests
* update activesupport Changelog
* Fix linter issues in CHANGELOG
* fix tests with improper setup and teardown method definitions
* Fix final CHANGELOG lint
* Revert "Fix final CHANGELOG lint"
This reverts commit f30682eb629780862ccc63e1d3210dfe035e997e.
* Revert "fix tests with improper setup and teardown method definitions"
This reverts commit 1d5b88c8739695a4eed5c46924c9ffc6010353f5.
* Revert "Fix linter issues in CHANGELOG"
This reverts commit 60e89bd189cbcdf50d7e923a90ec5ebe1578a6e9.
* Revert "update activesupport Changelog"
This reverts commit 0f19bc324fec7a793cc34dcfede27017b5a24e46.
* Revert "Remove unnecessary setup and teardown methods in tests"
This reverts commit e5673f179ac01c814ab44017b97e7638aad6e775.
* Revert "Switch ActiveSupport::TestCase teardown and setup callbacks to run in setup and teardown minitest lifecycle hooks."
This reverts commit d08d92d86131d8643a275397d9b0c15995730a14.
* Rescue Minitest::Assertion errors in ActiveSupport::TestCase.teardown callback code to ensure all other after_teardown methods are called.
* Fix name of test class
* remove unused MyError class
* Fix module to not be in global namespace
Co-authored-by: Rafael Mendonça França <rafael@rubyonrails.org>
In recent Ruby versions some pure C functions have been moved into
some semi-ruby code and now have an `<internal:something>` location.
They should be silenced like stdlib etc.
This commit addresses CI failure
at https://buildkite.com/rails/rails-nightly/builds/108#018d57ac-4f2a-45f1-86b9-9015a7b0a463/1165-2002
* Error addressed by this commit
```
$ ruby -v
ruby 3.4.0dev (2024-01-30T10:19:23Z master 86547fd69d) [x86_64-linux]
$ rm Gemfile.lock
$ bundle install
$ cd activesupport
$ bin/test test/core_ext/object/to_query_test.rb -n test_hash_not_sorted_lexicographically_for_nested_structure
... snip ...
E
Error:
ToQueryTest#test_hash_not_sorted_lexicographically_for_nested_structure:
NameError: uninitialized constant ToQueryTest::URI
test/core_ext/object/to_query_test.rb:90:in `test_hash_not_sorted_lexicographically_for_nested_structure'
bin/test test/core_ext/object/to_query_test.rb:79
Finished in 0.000385s, 2597.3082 runs/s, 0.0000 assertions/s.
1 runs, 0 assertions, 0 failures, 1 errors, 0 skips
$
```
This behavior has been introduced since Ruby 3.4.0dev vendors URI from top level to `Gem::URI`
via d64d0b5423 .
Here is the RubyGems one.
https://github.com/rubygems/rubygems/pull/7386
When delegating known APIs, rather that to let Delegator try to
inspect the signature, or to fallback to `...`, we can directly
specify it.
This is both faster and make for nicer delegators that have
the right signature.
`...` generates an anonymous block, it's basically a shortcut
for `*, **, &`. So to look more similar to tools that introspect
method signatures, it's best to continue to use an anonymous block.
The Rails documentation uses the `:include:` directive to inline the
README of the framework into the main documentation page. As the
README's aren't in the root directory from where SDoc is run we need to
add the framework path to the include:
# :include: activesupport/README.md
This results in a warning when installing the gems as generating the rdoc for the gem is run from the gem/framework root:
Couldn't find file to include 'activesupport/README.rdoc' from lib/active_support.rb
The `:include:` RDoc directive supports includes relative to the current
file as well:
# :include: ../README.md
This makes sure it works for the Rails API docs and the separate gems.
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
* 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
```