Commit Graph

7878 Commits

Author SHA1 Message Date
Jean Boussier ca6995a80c Update Method#duplicable? to be consistent with Ruby 3.4
Fix: https://github.com/rails/rails/issues/51075

`Method` and `UnboundMethod` used to raise on `#dup`, but not `#clone`,
this wasn't so much a feature, but a bug.

It was fixed in https://github.com/ruby/ruby/pull/9926.
2024-02-14 09:52:55 +01:00
Rafael Mendonça França efa8889ad6
Merge pull request #51017 from p8/activesupport/document-time-zone-create
Improve documentation of ActiveSupport::TimeZone.create [ci-skip]
2024-02-09 18:31:27 -05:00
Jean Boussier 7025c84eda Workaround a Ruby bug that can cause a VM crash
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.
2024-02-09 16:29:46 +01:00
Petrik 15b45953a0 Improve documentation of ActiveSupport::TimeZone.create [ci-skip]
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.
2024-02-08 21:21:57 +01:00
George Ma 3711c69ab0 Add `::` to namespace the module we delegate "to"
Only namespace when the delegation object is a module
2024-01-31 11:35:15 +01:00
Tristan Starck b0b481a7c4
Fix teardown callbacks (#50915)
* 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 f30682eb62.

* Revert "fix tests with improper setup and teardown method definitions"

This reverts commit 1d5b88c873.

* Revert "Fix linter issues in CHANGELOG"

This reverts commit 60e89bd189.

* Revert "update activesupport Changelog"

This reverts commit 0f19bc324f.

* Revert "Remove unnecessary setup and teardown methods in tests"

This reverts commit e5673f179a.

* Revert "Switch ActiveSupport::TestCase teardown and setup callbacks to run in setup and teardown minitest lifecycle hooks."

This reverts commit d08d92d861.

* 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>
2024-01-30 19:05:18 -05:00
Jean Boussier 794016aad7 BacktraceCleaner silence core internal methods by default
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.
2024-01-30 13:24:50 +01:00
Jean Boussier 8ec5219801 ActiveSupport::Delegation allow to specify the signature
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.
2024-01-25 11:51:01 +01:00
Jean Boussier f760ccd171 ActiveSupport::Delegation optimize self.class delegation
Since `self.class` can't possible be `nil`, we can skip the
useless `nil` checks and generate more efficient code.
2024-01-25 11:51:00 +01:00
Jean Boussier bffe05f246 Module#delegate stop accepting the private `as:` parameter
The feature remains usable internally, but via `ActiveSupport::Delegation`,
this way we don't allow third party use.
2024-01-25 11:51:00 +01:00
Jean Boussier 6ee0041ed2 Refactor `Module#delegate` inside ActiveSupport::Delegation
This allow to support some extra private features without exposing
them in `Module#delegate`.
2024-01-25 11:51:00 +01:00
Jean Boussier ddc32f5a47 Use an anonymous block parameter in Module#delegate
`...` 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.
2024-01-25 09:16:21 +01:00
Petrik de Heus fe81d667a7
Merge pull request #50789 from p8/docs/relative-includes
Use relative includes of README's in documentation [ci-skip]
2024-01-21 18:30:07 +01:00
m-nakamura145 a5af0a9118
Add example to parts documentation [ci-skip] 2024-01-19 22:05:59 +09:00
Petrik 8565f45100 Use relative includes of README's in documentation [ci-skip]
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>
2024-01-18 10:39:15 +01:00
Jean Boussier 4530a56e0f `RateLimiting` Cleanups
Followup: https://github.com/rails/rails/pull/50781

Some small issues I failed to address in the original PR.
2024-01-18 10:11:43 +01:00
Jean Boussier b54a287f9d Ensure all Cache store have consistent TTL behavior on increment
Make sure they all increment the counter but don't update the TTL.
2024-01-17 15:40:16 +01:00
Jean Boussier de779f2bf7 Fix `ActiveSupport::Notifications.publish_event` to preserve units
Ref: https://github.com/rails/rails/pull/43502
Fix: https://github.com/rails/rails/pull/50767
Fix: https://github.com/rails/rails/pull/50493

When republishing a an event into a `start, finish` tuple, we need
to convert the timestamps back into seconds.
2024-01-17 12:53:40 +01:00
Eugene Kenny 0656787be6
Merge pull request #50764 from eugeneius/syntax_error_proxy_nil_backtrace_locations
Handle nil backtrace_locations in SyntaxErrorProxy
2024-01-16 14:20:13 +00:00
Jean Boussier 946e46ebcc Modernize method missing implementations
`...` is both simpler an more correct since the keyword argument
separation.
2024-01-16 13:17:45 +01:00
Eugene Kenny 16d1351a93 Handle nil backtrace_locations in SyntaxErrorProxy 2024-01-16 01:05:53 +00:00
r-plus 38151711c8 Fix IPAddr prefix information missing when write to cache in msgpack serializer
* 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>
2024-01-15 00:40:44 -06:00
Jonathan Hefner aa98bc3c71 Prevent CurrentAttributes defaults from leaking
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`.
2024-01-11 12:40:14 -06:00
Jean Boussier 4bded3c00a `Module#delegate` avoid creating a unique fstring for each delegator
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)
2024-01-10 19:27:50 +01:00
Jean Boussier 61b48fe76d
Merge pull request #50686 from seanpdoyle/remove-current-attributes-method-missing
Avoid definition of methods at runtime in `CurrentAttributes`
2024-01-10 09:40:09 +01:00
Sean Doyle c8e5b0b531 Avoid definition of methods in `CurrentAttributes` at runtime
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>
2024-01-10 08:59:35 +01:00
Sean Doyle d1d6b6bce3 Add `default:` support for `ActiveSupport::CurrentAttributes.attribute`
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.
2024-01-09 19:09:57 -05:00
Sean Doyle 2cd4abcc87 Simplify `CurrentAttribute.instance` delegation
Follow-up to [#50676][]

Instead of relying on code generation, call a corresponding [delegate][]
method on the `.singleton_class`.

[#50676]: https://github.com/rails/rails/pull/50676
[delegate]: https://edgeapi.rubyonrails.org/classes/Module.html#method-i-delegate

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2024-01-09 16:54:00 -05:00
Sean Doyle 3c72983dc5 Implement `CurrentAttributes#set` in terms of `Object#with`
`CurrentAttributes` supports block-scoped overrides for its attributes
through the `#set` method. The introduction of [CurrentAttributes#set][]
predates the introduction of [Object#with][] by 6 years.

This commit changes the implementation of `#set` to delegate to `#with`.
Through that delegation, the private `#assign_attributes` and
`#compute_attributes` methods are no longer necessary.

[CurrentAttributes#set]: 2d6b02bad6/activesupport/lib/active_support/current_attributes.rb (L210)
[Object#with]: 2d6b02bad6/activesupport/lib/active_support/core_ext/object/with.rb (L26)
2024-01-09 14:34:18 -05:00
Jean Boussier a3d05309aa Get rid of `ForkTracker.check!`
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.
2024-01-09 11:18:38 +01:00
Jean Boussier 8c7e69b79b Optimize Hash#stringify_keys
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
```
2024-01-08 19:19:20 +01:00
Earlopain d96c424fab
Remove core_ext/uri.rb exception
The file was removed in da8e6f6175
2024-01-08 10:58:39 +01:00
Jonathan Hefner 3bbf21c343 Use verb form of "fallback"
"Fallback" is a noun, whereas "fall back" is a verb.
2024-01-07 17:27:23 -06:00
Jean Boussier c0b5052d92
Merge pull request #50609 from ricardotk002/use-array-intersect
Replace usage of `Array#?` with `Array#intersect?` for efficiency
2024-01-07 21:15:56 +01:00
Sean Doyle 9e64b13d8a Yield instance to `Object#with` block
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
```
2024-01-07 20:56:33 +01:00
Jonathan Hefner 53ba75d1aa Clean up AS::NumberHelper#number_to_human doc [ci-skip] 2024-01-06 18:07:26 -06:00
Jonathan Hefner 2b801dcec4 Clean up AS::NumberHelper#number_to_human_size doc [ci-skip] 2024-01-06 18:07:26 -06:00
Jonathan Hefner 8a04207991 Clean up AS::NumberHelper#number_to_rounded doc [ci-skip] 2024-01-06 18:07:26 -06:00
Jonathan Hefner a0e7aaa085 Clean up AS::NumberHelper#number_to_delimited doc [ci-skip] 2024-01-06 18:07:26 -06:00
Jonathan Hefner 1d02d9b472 Clean up AS::NumberHelper#number_to_percentage doc [ci-skip] 2024-01-06 18:07:26 -06:00
Jonathan Hefner 9acf9e6e49 Clean up AS::NumberHelper#number_to_currency doc [ci-skip] 2024-01-06 18:07:26 -06:00
Jonathan Hefner 6be13c827a Clean up AS::NumberHelper#number_to_phone doc [ci-skip] 2024-01-06 18:07:26 -06:00
Ricardo Díaz de154095ed Replace usage of `Array#?` with `Array#intersect?` for efficiency
`Array#intersect?` was introduced in Ruby 3.1.0 and it's more efficient and
useful when the result of the intersection is not needed as the
following benchmarks show:

```
require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails", path: "./"
  # If you want to test against edge Rails replace the previous line with this:
  # gem "rails", github: "rails/rails", branch: "main"
  gem "benchmark-ips"
end

require "active_support"

SCENARIOS = [
  [(1..100).to_a, (90..200).to_a],    # Case 1
  [("a".."m").to_a, ("j".."z").to_a], # Case 2
  [(1..100).to_a, (101..200).to_a],   # Case 3
]

SCENARIOS.each_with_index do |values, n|
  puts
  puts " Case #{n + 1} ".center(80, "=")
  puts
  Benchmark.ips do |x|
    x.report("Array#?") { !(values[0] & values[1]).empty? }
    x.report("Array#intersect?")      { values[0].intersect?(values[1]) }
    x.compare!
  end
end
```

Results:

```
==================================== Case 1 ====================================

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21]
Warming up --------------------------------------
             Array#?    34.221k i/100ms
    Array#intersect?    62.035k i/100ms
Calculating -------------------------------------
             Array#?    343.119k (± 1.1%) i/s -      1.745M in   5.087078s
    Array#intersect?    615.394k (± 1.1%) i/s -      3.102M in   5.040838s

Comparison:
    Array#intersect?:   615393.7 i/s
             Array#?:   343119.4 i/s - 1.79x  slower

==================================== Case 2 ====================================

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21]
Warming up --------------------------------------
             Array#?   103.256k i/100ms
    Array#intersect?   185.104k i/100ms
Calculating -------------------------------------
             Array#?      1.039M (± 1.3%) i/s -      5.266M in   5.066847s
    Array#intersect?      1.873M (± 1.6%) i/s -      9.440M in   5.041740s

Comparison:
    Array#intersect?:  1872932.7 i/s
             Array#?:  1039482.4 i/s - 1.80x  slower

==================================== Case 3 ====================================

ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin21]
Warming up --------------------------------------
             Array#?    37.070k i/100ms
    Array#intersect?    41.438k i/100ms
Calculating -------------------------------------
             Array#?    370.902k (± 0.8%) i/s -      1.891M in   5.097584s
    Array#intersect?    409.902k (± 1.0%) i/s -      2.072M in   5.055185s

Comparison:
    Array#intersect?:   409901.8 i/s
             Array#?:   370902.3 i/s - 1.11x  slower
```
2024-01-05 15:15:30 -05:00
Jean Boussier 21090a1319
Merge pull request #50602 from Shopify/symbol-name-cleanup
Update shims for older rubies
2024-01-05 17:22:23 +01:00
Jean Boussier 6c357e9571 Update shims for older rubies
`Symbol#name` and `Time#floor` are now present on the minimum required version.
2024-01-05 17:04:05 +01:00
Rafael Mendonça França 4760e07c1e
Merge pull request #50590 from sato11/cache-can-drop-ruby-2-7-compatibility
Remove workaround for Ruby 2.7 at ActiveSupport::Cache#lookup_store
2024-01-05 10:03:24 -05:00
Jean Boussier 27140247c2 Cleanup `defined?` usage
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.
2024-01-05 15:05:35 +01:00
Jean Boussier ef65e5fb32 Cleanup usage of ruby2_keywords
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.
2024-01-05 14:40:18 +01:00
Junichi Sato de77bca9c5
Remove workaround for Ruby 2.7 at ActiveSupport::Cache#lookup_store
Since the minimum required version is now 3.1,
this can be got rid of like the comment commands.
2024-01-05 12:22:08 +09:00
Sean Doyle 7abaeea4d3 Remove `MethodCallAssertions` Ruby 2.7 work-around
By dropping support for 2.7, Rails 8 will not need to work-around
positional and keyword argument splatting.
2024-01-04 11:10:30 -05:00