Commit Graph

7932 Commits

Author SHA1 Message Date
Rafael Mendonça França
db2ef1d250
Merge pull request #49417 from Edouard-chin/ec-logger-fix
Fix the BroadcastLogger being initialized too late:
2023-09-29 15:45:03 -04:00
Bart de Water
95b6fbd00f Stop building AS::Notifications::Event manually
It's possible since Rails 6 (3ea2857943dc294d7809930b4cc5b318b9c39577) to let the framework create Event objects, but the guides and docs weren't updated to lead with this example.

Manually instantiating an Event doesn't record CPU time and allocations, I've seen it more than once that people copy-pasting the example code get confused about these stats returning 0. The tests here show that - just like the apps I've worked on - the old pattern keeps getting copy-pasted.
2023-09-29 12:34:23 -04:00
Edouard CHIN
40cb50e06e Fix the BroadcastLogger being initialized too late:
- An oversight of #48615 is that it changes the `Rails.logger` to be
  a broadcast logger after the app is booted. Anything referencing
  `Rails.logger` during the boot process will get a simple logger and
  ultimately resulting in logs not being broadcasted.

  For example `ActionController::Base.logger.info("abc")` would
  just output logs in the `development.log` file, not on STDOUT.

  ----

  The only solution I could think of is to create a BroadcastLogger
  earlier at boot, and add logger to that broadcast when needed (instead
  of modiyfing the `Rails.logger` variable).
2023-09-29 15:42:47 +02:00
fatkodima
7ef86b6a49 Enable Lint/RedundantSafeNavigation rubocop cop 2023-09-27 14:55:07 +03:00
Rafael Mendonça França
fb6c6007d0
Development of Rails 7.2 starts now
🎉
2023-09-27 03:59:11 +00:00
Rafael Mendonça França
e5386cb402
Preparing for 7.1.0.rc1 release 2023-09-27 03:08:31 +00:00
Rafael Mendonça França
54f30488e1
Also rescue TZInfo::ZoneinfoDirectoryNotFound when loading time zone data
Fixes #49375.
2023-09-27 02:52:20 +00:00
Rafael Mendonça França
4c72cc2b04
Merge pull request #48615 from Edouard-chin/ec-logger
Add a public API for broadcasting logs
2023-09-25 17:13:58 -04:00
Edouard CHIN
1fbd812c47
Add a public API for broadcasting logs:
- ## Context

  While working on https://github.com/rails/rails/pull/44695, I
  realised that Broadcasting was still a private API, although it’s
  commonly used. Rafael mentioned that making it public would require
  some refactor because of the original implementation which was hard
  to understand and maintain.

  ### Changing how broadcasting works:

  Broadcasting in a nutshell worked by “transforming” an existing
  logger into a broadcasted one.
  The logger would then be responsible to log and format its own
  messages as well as passing the message along to other logger it
  broadcasts to.

  The problem with this approach was the following:

  - Heavy use of metaprogramming.
  - Accessing the loggers in the broadcast wasn’t possible.
    Removing a logger from the broadcast either.
  - More importantly, modifying the main logger (the one that broadcasts
    logs to the others) wasn’t possible and the main source of
    misunderstanding.

    ```ruby
      logger = Logger.new(STDOUT)
      stderr_logger = Logger.new(STDER))
      logger.extend(AS::Logger.broadcast(stderr_logger))

      logger.level = DEBUG # This modifies the level on all other loggers
      logger.formatter = … # Modified the formatter on all other loggers
    ```

  To keep the contract unchanged on what Rails.logger returns, the new
  BroadcastLogger class implement duck typing with all methods
  that has the vanilla Ruby Logger class.

  It's a simple and boring PORO that keeps an array of all the loggers
  that are part of the broadcast and iterate over whenever a log is
  sent.
  Now, users can access all loggers inside the broadcast and modify
  them on the fly. They can also remove any logger from the broadcast
  at any time.

  ```ruby
  # Before

  stdout_logger = Logger.new(STDOUT)
  stderr_logger = Logger.new(STDER)
  file_logger = Logger.new(“development.log”)
  stdout_logger.extend(AS::Logger.broadcast(stderr_logger))
  stdout_logger.extend(AS::Logger.broadcast(file_logger))

  # After

  broadcast = BroadcastLogger.new(stdout_logger, stderr_logger, file_logger)
  ```

  I also think that now, it should be more clear for users that the
  broadcast sole job is to pass everything to the whole loggers in
  the broadcast. So there should be no surprise that all loggers in
  the broadcast get their level modified when they call
  `broadcast.level = DEBUG` .

  It’s also easier to wrap your head around more complex setup such
  as broadcasting logs to another broadcast:
  `broadcast.broadcast_to(stdout_logger, other_broadcast)`
2023-09-25 20:40:51 +00:00
Rafael Mendonça França
71613d3da9
Merge pull request #45411 from jonathanhefner/add-deep_mergeable
Factor out `deep_merge` into `AS::DeepMergeable`
2023-09-25 16:25:44 -04:00
Hartley McGuire
0f5e7a6614
Update requires in LoggerThreadSafeLevel
The `concurrent` require was [added][1] previously because a
`Concurrent::Map` was added to hold all of the log levels by `Thread` id.
However, the `Map` was later [removed][2] by storing the log levels in the
`Thread` locals (and later in `IsolatedExecutionState`) instead. The new
implementation additionally removed the `cattr_accessor` (removing the
need to require the "attribute_accessors" core_ext) and also replaced
the [usage][3] of `Fiber` with `Thread` (so the require for `Fiber` is also no
longer necessary).

Since `Concurrent`, `Fiber`, and `cattr_accessor` are no longer used here, we
can remove the requires. Since `Logger` is used here (but was previously
required by `concurrent`), a require was added for it.

[1]: 629efb605728b31ad9644f6f0acaf3760b641a29
[2]: 2379bc5d2a7d9580f270eebfde87d9f94b3da6c9
[3]: 56ec504db6c130d448ffc1d68c9fdd95fdfc1130
2023-09-24 21:59:55 -04:00
Hartley McGuire
64184c6f50
Fix Range#overlap? ignoring empty ranges
Previously, #overlap? would incorrectly return true when one of the
ranges is effectively "empty":

```ruby
(2...2).overlap? 1..2 # => true
(1..2).overlap? 2...2 # => true
```

This is fixed in the Ruby 3.3 implementation of Range#overlap?, so this
commit fixes it for Ruby < 3.3 as well.

The tests added are from the Ruby repository and the implementation is
effectively a Ruby version of the fix in C.

Co-authored-by: Nobuyoshi Nakada <nobu@ruby-lang.org>
Co-authored-by: Shouichi Kamiya <shouichi.kamiya@gmail.com>
2023-09-22 19:48:49 -04:00
Hartley McGuire
b5dbcb1a3e
Add links to Callbacks, note example definition
Reading this doc left me confused where PersonRecord was defined, this
commit is intended to make it easier to find where it is.
2023-09-20 21:54:31 -04:00
Yasuo Honda
1d76d45411 Use Ruby 3.3 Range#overlap? if available
This commit uses Ruby 3.3 `Range#overlap?` that has been added to Ruby via https://github.com/ruby/ruby/pull/8242 .
Rails 7.1 renames `Range#overlaps?` to `Range#overlap?` via https://github.com/rails/rails/pull/48565 ,
This commit is not feasible to backport because there is no `Range#overlap?` in Rails 7.0.z

This commit addresses the CI faiilure at https://buildkite.com/rails/rails/builds/99745#018a9ea8-82f0-40a6-90c3-cdaa6dabebab/1092-1095
because without this commit, it shows `warning: method redefined; discarding old overlap?`.
```ruby
$ ruby -v
ruby 3.3.0dev (2023-09-16T05:57:19Z master e9b503f1bb) [x86_64-linux]
$ RAILS_STRICT_WARNING=true bundle exec ruby -w -Itest test/core_ext/range_ext_test.rb
/home/yahonda/src/github.com/rails/rails/activesupport/lib/active_support/core_ext/range/overlap.rb:7: warning: method redefined; discarding old overlap?
Running 46 tests in a single process (parallelization threshold is 50)
Run options: --seed 583

\# Running:

..............................................

Finished in 0.011672s, 3940.9670 runs/s, 4883.3722 assertions/s.
46 runs, 57 assertions, 0 failures, 0 errors, 0 skips
```
2023-09-19 19:47:35 +09:00
Jean Boussier
c15996c1bc Make ErrorReporterAssertions methods public
Since the methods are private they weren't documented.
2023-09-15 10:57:14 +02:00
Rafael Mendonça França
699dfdb426
Preparing for 7.1.0.beta1 release 2023-09-13 00:36:01 +00:00
Alex Ghiculescu
ff6881d2b2
Remove old raise_on_missing_translations behaviour
ref: https://github.com/rails/rails/pull/47105#issuecomment-1400843060

Removes the old `raise_on_missing_translations` accessors, that used to live [here](fee61e3abc/actionpack/lib/abstract_controller/translation.rb (L7)) and [here](5c835bd669/actionview/lib/action_view/helpers/translation_helper.rb (L15)).

Closes https://github.com/rails/rails/pull/45361
2023-09-09 19:59:49 +00:00
Rafael Mendonça França
fea9ad3337
Make sure the message format doesn't change
When `use_message_serializer_for_metadata` is false, the message
format should be enveloped in the same way it was in Rails 7.0
to make sure we don't have inconsistent formats.
2023-09-01 23:03:24 +00:00
Rafael Mendonça França
946550b467
Merge pull request #49067 from p8/performance/securerandom-choose
Use SecureRandom.alphanumeric for SecureRandom.base36/base58
2023-09-01 16:40:34 -04:00
Jean Boussier
553b44a044 Fix DescendantsTrackerTest when ran in isolation
Followup: https://github.com/rails/rails/pull/49108

DescendantsTracker need to work whether the `Class#descendants` core
ext is loaded or not. I missed that in the previous PR.
2023-09-01 18:19:40 +02:00
Jean Boussier
7407fe8a63 Remove useless include in DescendantsTracker
Either we are on a modern Ruby with `Class#subclass`, in which case
`DescendantsTracker#subclass` isn't defined, so we don't need the
filtering module.

Or we're on an old Ruby, and `DescendantsTracker.subclass` already does
the filtering.
2023-09-01 16:52:01 +02:00
Jean Boussier
a7fdc1cdb4 Optimize ActiveRecord::LogSubscriber#query_source_location
`Kernel#caller` has linear performance based on how deep the
stack is. While this is a development only feature, it can end
up being quite slow.

Ruby 3.2 introduced `Thread.each_caller_location`, which lazily
yield `Backtrace::Location` objects.

Ref: https://bugs.ruby-lang.org/issues/16663

This is perfect for this use case as we are searching for the
closest frame that matches a pattern, saving us from collecting
the entire backtrace.
2023-08-31 11:56:55 +02:00
Petrik
9aeffae14f Use SecureRandom.alphanumeric for SecureRandom.base36
Ruby 3.3 allows passing a list of characters to
`SecureRandom.alphanumeric`. For `SecureRandom.base36` using `choose` is
faster than the current implementation. For `SecureRandom.base58` it is
a bit slower.

I've also added a test to make sure passing nil as the length defaults
the length to 16.

_Benchmark__

```ruby

require "bundler/inline"

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

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

  gem "rails", github: "rails/rails", branch: "main"
  gem "benchmark-ips"
end

require "active_support"
require "active_support/core_ext/securerandom"

module SecureRandom
  def self.fast_base36(n)
    alphanumeric(n, chars: BASE36_ALPHABET)
  end
end

[10, 100, 1000, 10000].each do |length|
  puts
  puts " #{length} ".center(80, "=")
  puts

  Benchmark.ips do |x|
    x.report("base36")      { SecureRandom.base36(length) }
    x.report("fast_base36") { SecureRandom.fast_base36(length) }
    x.compare!
  end
end
```

```
====================================== 10 ======================================

Warming up --------------------------------------
              base36    20.513k i/100ms
         fast_base36    24.843k i/100ms
Calculating -------------------------------------
              base36    200.940k (±13.8%) i/s -    984.624k in   5.060203s
         fast_base36    235.531k (± 5.7%) i/s -      1.192M in   5.080574s

Comparison:
         fast_base36:   235530.9 i/s
              base36:   200939.9 i/s - same-ish: difference falls within error

===================================== 100 ======================================

Warming up --------------------------------------
              base36     2.746k i/100ms
         fast_base36     2.995k i/100ms
Calculating -------------------------------------
              base36     25.559k (± 8.5%) i/s -    129.062k in   5.087961s
         fast_base36     30.265k (± 6.6%) i/s -    152.745k in   5.070263s

Comparison:
         fast_base36:    30264.7 i/s
              base36:    25558.8 i/s - 1.18x  slower

===================================== 1000 =====================================

Warming up --------------------------------------
              base36   278.000  i/100ms
         fast_base36   308.000  i/100ms
Calculating -------------------------------------
              base36      2.595k (±11.6%) i/s -     12.788k in   5.007921s
         fast_base36      3.133k (± 6.1%) i/s -     15.708k in   5.033310s

Comparison:
         fast_base36:     3132.6 i/s
              base36:     2594.9 i/s - 1.21x  slower

==================================== 10000 =====================================

Warming up --------------------------------------
              base36    24.000  i/100ms
         fast_base36    34.000  i/100ms
Calculating -------------------------------------
              base36    256.601  (± 8.6%) i/s -      1.296k in   5.089604s
         fast_base36    322.119  (± 6.5%) i/s -      1.632k in   5.089614s

Comparison:
         fast_base36:      322.1 i/s
              base36:      256.6 i/s - 1.26x  slower

```
2023-08-30 11:28:42 +02:00
Matt Brictson
0bbaaf69ad
Remove outdated libxml-ruby version spec in XmlMini doc comment
XmlMini is currently being tested against libxml-ruby 4.0.0[^1], but the
doc comment for XmlMini says:

```
To use the much faster libxml parser:
gem 'libxml-ruby', '=0.9.7'
```

This comment seems to be very much out of date and could mislead users.

Fix by removing the version specifier so that the documentation simply
recommends:

```
To use the much faster libxml parser:
gem 'libxml-ruby'
XmlMini.backend = 'LibXML'
```

[^1]: 621bc68548/Gemfile.lock (L310)
2023-08-29 13:45:42 -07:00
Jean Boussier
6beb15c04a Add some :nodoc: to SyntaxErrorProxy and friends
None of this is public API.
2023-08-24 09:52:18 +02:00
Aaron Patterson
3338621efa
Use a temporary file for storing unencrypted files while editing
When we're editing the contents of encrypted files, we should use the
`Tempfile` class because it creates temporary files with restrictive
permissions.  This prevents other users on the same system from reading
the contents of those files while the user is editing them.

[CVE-2023-38037]
2023-08-22 10:23:15 -07:00
Jean Boussier
bef4c6904e
Merge pull request #48988 from jdelStrother/redis-distr-pipeline
Add Redis::Distributed support for pipelined write_multi
2023-08-21 15:30:53 +02:00
Jonathan del Strother
c47b4095cc
Add Redis::Distributed support for pipelined write_multi 2023-08-20 19:17:25 +01:00
Guillermo Iguaran
4ec3a986d5
Merge pull request #48959 from skipkayhil/hm-clean-filters-requires
Remove uneeded requires of core_ext/string/filters
2023-08-18 16:03:44 -07:00
Jonathan del Strother
1b3d884bb3
Add test coverage for RedisCacheStore with Redis::Distributed / ConnectionPool
This 'forward-ports' some tests added against 7-0-stable here: #48952

Also fixes a bug in supports_expire_nx? when using distributed-redis.
2023-08-17 12:34:28 +01:00
Hartley McGuire
ff6e885d59
Remove uneeded requires of core_ext/string/filters
`actionpack/lib/action_dispatch/routing.rb`
- added: 013745151be062aa4d0fc1f2a008a7303fdb6e04
- removed: 93034ad7fea7e00562103a7cd0acfab19bbfadf9

`activejob/lib/active_job/log_subscriber.rb`
- added: b314ab555e0d85e6efb41be94fb5f3a157bb12fe
- removed: 5ab2034730feacfc2caee418f8c0b55191d27427

`activemodel/lib/active_model/errors.rb`
- added: cf7fac7e29bb2816412c949fdaed3d61a923eb23
- removed: 9de6457ab0767ebab7f2c8bc583420fda072e2bd

`activerecord/lib/active_record/core.rb`
- added: b3bfa361c503e107aff4dee5edf79bd7fd3d3725
- removed: e1066f450d1a99c9a0b4d786b202e2ca82a4c3b3

`activesupport/lib/active_support/core_ext/module/introspection.rb`
- added: 358ac36edf1695fcbec0aa21f126a3d8b83d4b5a
- removed: 167b4153cac0069a21e0bb9689cb16f34f6abbaa

`activesupport/lib/active_support/duration.rb`
- added: 75924c4517c8f87712d3f59c11f10152ed57b9d8
- removed: a91ea1d51048342d13fc73f9b09ce4cfd086bb34

`railties/lib/rails/commands/server/server_command.rb`
- added: f2173648938b418d120f5a68d8f3862d8ae9dace
- removed: 553b86fc751c751db504bcbe2d033eb2bb5b6a0b

`railties/lib/rails/command/base.rb`
- added: 6813edc7d926965e5644cd8befaf229a35b9d8ca
- removed: b617a561d865a65cfc140caa0e3c4af4350bfcef
2023-08-16 17:39:25 -04:00
Jonathan Hefner
35b3db4945 Document and test nil cache coder
Since Rails 6.1 (via c4845aa7791839fcdf723dc77e3df258e7274496), it has
been possible to specify `coder: nil` to allow the store to handle cache
entries directly.

This commit adds documentation and regression tests for the behavior.
2023-08-06 13:03:52 -05:00
Rafael Mendonça França
5c69954d6b
Don't pring parallelization info when running in a single process 2023-08-05 02:32:12 +00:00
Rafael Mendonça França
5e3ca3b158
Set the variable to false instead of checking if it is defined
We already have an initalize method, so we can just set the variable to
false there.
2023-08-05 02:15:33 +00:00
Rafael Mendonça França
d1729d5332
Allow setting number of parallel workers to 1
In the case that one single test file can't run with more than 1
parallel workers, but the base class has parallelization enabled, we
should still allow the user to set the number of workers to 1.
2023-08-05 02:15:32 +00:00
John Hawthorn
6a5efd9777
Merge pull request #48669 from jhawthorn/faster_json_escape
Improve performance of JSON HTML entity escaping
2023-07-31 14:15:44 -07:00
Jonathan Hefner
36f9cfdd90 Lazily deserialize cache entries
This adds a cache optimization such that expired and version-mismatched
cache entries can be detected without deserializing their values.  This
optimization is enabled when using cache format version >= 7.1 or a
custom serializer.

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-07-27 14:58:35 -05:00
Petrik
3601a236dd Make all cache stores return a boolean for #delete
`Rails.cache.delete('key')` is supposed to return `true` if an entry
exists and `false` otherwise. This is how most stores behave.

However, the `RedisCacheStore` would return a `1` when deleting an entry
that does exist and a `0` otherwise.
As `0` is truthy this is unexpected behaviour.

`RedisCacheStore` now returns true if the entry exists and false
otherwise, making it consistent with the other cache stores.

Similarly the `FileCacheStore` now returns `false` instead of `nil` if
the entry doesn't exist.

A test is added to make sure this behaviour applies to all stores.

The documentation for `delete` has been updated to make the behaviour
explicit.
2023-07-27 08:48:30 +02:00
Jonathan Hefner
3efb84486e Support replacing cache compressor
This commit adds support for replacing the compressor used for
serialized cache entries.  Custom compressors must respond to `deflate`
and `inflate`.  For example:

  ```ruby
  module MyCompressor
    def self.deflate(string)
      # compression logic...
    end

    def self.inflate(compressed)
      # decompression logic...
    end
  end

  config.cache_store = :redis_cache_store, { compressor: MyCompressor }
  ```

As part of this work, cache stores now also support a `:serializer`
option.  Similar to the `:coder` option, serializers must respond to
`dump` and `load`. However, serializers are only responsible for
serializing a cached value, whereas coders are responsible for
serializing the entire `ActiveSupport::Cache::Entry` instance.
Additionally, the output from serializers can be automatically
compressed, whereas coders are responsible for their own compression.

Specifying a serializer instead of a coder also enables performance
optimizations, including the bare string optimization introduced by cache
format version 7.1.
2023-07-26 11:59:09 -05:00
Jonathan Hefner
3bdd57fba6 Support option aliases in RedisCacheStore#initialize
`ActiveSupport::Cache::UNIVERSAL_OPTIONS` already defines the list of
base options, and it includes option aliases such as `:expire_in` and
`:expired_in`.  Thus, using `UNIVERSAL_OPTIONS` allows `RedisCacheStore`
to support these aliases.
2023-07-25 15:57:21 -05:00
Jonathan Hefner
c3bf6bf38d Raise more specific error for cache format version
Follow-up to #48449.

Since #48449 changed the list of accepted cache format versions back to
just `6.1`, `7.0`, and `7.1`, we can raise a more specific error.
2023-07-25 15:57:21 -05:00
James Robinson
016b81f0b2 Fix ActiveSupport::Inflector.humanize(nil) 2023-07-25 17:00:28 +01:00
Étienne Barrié
b6ce10bcc6 Fix inconsistencies writing credentials values
Using [] or the dynamic accessors don't result in the same value because
`[]` is delegated to `config` (the decrypted deserialized YAML), whereas
`[]=` and the dynamic accessors are delegated to `options`, an
ActiveSupport::OrderedOptions instance.
2023-07-21 11:32:57 +02:00
Ufuk Kayserilioglu
c2b195e1e3
Change load error messages to use Kernel#warn instead of $stderr.puts
When development tools try to load Rails components, they sometimes end up loading files that will error out since a dependency is missing. In these cases, the tooling can catch the error and change its behaviour.

However, since the warning is printed directly to `$stderr`, the tooling cannot catch and suppress it easily, which ends up causing noise in the output of the tool.

This change makes Rails print these warnings using `Kernel#warn` instead, which can be suppressed by the tooling.
2023-07-21 00:38:12 +03:00
Jonathan Hefner
c0a5929f3a Fix serialization of non-ASCII-only bare strings
To use a binary-encoded string as a byte buffer, appended strings should
be force-encoded as binary.  Otherwise, appending a non-ASCII-only
string will raise `Encoding::CompatibilityError`.

Fixes #48748.
2023-07-17 13:02:32 -05:00
Guillermo Iguaran
66676ce499
Merge pull request #48680 from p8/activesupport/message-verifier-inspect
Don't show secrets for `MessageVerifier#inspect` and `KeyGenerator#inspect`
2023-07-10 10:56:18 -07:00
Petrik de Heus
2e597fa423
Merge pull request #48661 from p8/activesupport/document-cache-multiple-return-value
Document return value of `Rails.cache.delete_multi`
2023-07-06 21:58:42 +02:00
Petrik
5117da2b65 Don't show secrets for MessageVerifier#inspect and KeyGenerator#inspect
Before:

```ruby
ActiveSupport::MessageVerifier.new(secret).inspect
"#<ActiveSupport::MessageVerifier:0x0000000104888038 ... @secret=\"\\xAF\\bFh]LV}q\\nl\\xB2U\\xB3 ... >"
ActiveSupport::KeyGenerator.new(secret).inspect
"#<ActiveSupport::KeyGenerator:0x0000000104888038 ... @secret=\"\\xAF\\bFh]LV}q\\nl\\xB2U\\xB3 ... >"
```

After:

```ruby
ActiveSupport::MessageVerifier::Aes256Gcm(secret).inspect
"#<ActiveSupport::MessageVerifier:0x0000000104888038>"
ActiveSupport::KeyGenerator::Aes256Gcm(secret).inspect
"#<ActiveSupport::KeyGenerator:0x0000000104888038>"
```
2023-07-06 21:51:22 +02:00
Eileen M. Uchitelle
1cbd88f918
Merge pull request #48662 from skipkayhil/hm-fix-memcache-6-1-deprecation
Fix MemCacheStore not warning on 6.1 cache format
2023-07-06 09:10:51 -04:00
Jonathan del Strother
9e31b19444
Remove unused RedisCacheStore#redis_options
The underlying ivar was removed in c07812cee2be727b334ed2c89ef1ceaa6b467447
2023-07-06 12:59:01 +01:00
Jean Boussier
55a852a63f Make Active Support Cache treat deserialization errors like cache misses
This help treating caches entries as expandable.

Because Marshal will hapily serialize almost everything, It's not uncommon to
inadvertently cache a class that's not particularly stable, and cause deserialization
errors on rollout when the implementation changes.

E.g. https://github.com/sporkmonger/addressable/pull/508

With this change, in case of such event, the hit rate will suffer for a
bit, but the application won't return 500s responses.
2023-07-06 10:51:27 +02:00
Jonathan Hefner
67dd6ee325 Refactor RedisCacheStore options handling
This makes it easier to add new base options with default values to
`Cache::Store`.
2023-07-05 17:11:26 -05:00
Jonathan Hefner
c7a23018f5 Extract Cache::Entry into a separate file
`activesupport/lib/active_support/cache.rb` is already rather long, and
having a separate file allows subclasses of `Cache::Entry` to be defined
without first requiring `activesupport/lib/active_support/cache.rb`.
2023-07-05 17:11:26 -05:00
Jonathan Hefner
ef703fecac Replace NullCoder with :passthrough coder
`NullCoder` is functionally equivalent to the `:passthrough` coder.
Additionally, `NullCoder` was added in c4845aa7791839fcdf723dc77e3df258e7274496
to serve as the default coder for `MemCacheStore`, but `MemCacheStore`
now uses `:passthrough` as its (legacy) default coder.
2023-07-05 17:11:26 -05:00
Jonathan Hefner
860271ee02 Always set :compress_threshold default value
Falsy `:compress_threshold` values are not supported, so we can always
set the default value.
2023-07-05 17:11:26 -05:00
Jonathan Hefner
416a32956c Format options doc as a description list [ci-skip] 2023-07-05 17:11:26 -05:00
John Hawthorn
ebe0c4066b Improve performance of JSON HTML entity escaping
Running gsub! 5 times with string arguments seems to be faster than
running it once with a regex and Hash.

When there are matches to the regex (there are characters to escape)
this is faster in part because CRuby will allocate a new match object
and string as a key to lookup in the map hash provided. It's possible
that could be optimized upstream, but at the moment this avoids those
allocations.

Surprisingly (at least to me) this is still much faster when there is no
replacement needed: in my test ~3x faster on a short ~200 byte string,
and ~5x faster on a pre-escaped ~600k twitter.json.
2023-07-05 12:45:31 -07:00
Hartley McGuire
89ed0afd30
Fix MemCacheStore not warning on 6.1 cache format
While working on fixing some deprecation warnings introduced when the
6.1 cache_format deprecation was [moved][1] to be on usage, I found that
the MemCacheStore actually has its own `default_coder` method.

This adds the warning to MemCacheStore's `default_coder` method so that
every cache store will warn on using the 6.1 format.

[1]: 088551c802bb4005a667aaa33814cfbb1feb3927
2023-07-05 10:20:55 -04:00
Petrik
55190de46b Document return value of Rails.cache.delete_multi 2023-07-05 15:46:20 +02:00
Jean Boussier
cf67782e43 Fix AS::Cache 7.1 format to properly compress bare strings
The bare string and compression features weren't working properly
together.

I discovered this while trying to fix deprecation warnings.
2023-07-05 13:56:05 +02:00
Hartley McGuire
f31172582c
Add lower bound to Listen gem requirement
An issue was recently opened with the following error message:

```
.../activesupport-7.0.6/lib/active_support/evented_file_update_checker.rb:120:in `start': undefined method `wait_for_state' for #<Listen::Listener ...>
```

The issue is that the user was using Listen 3.0.8, however the
`wait_for_state` method was [added][1] in Listen 3.3.0

We can make the error message a little better by defining a lower bound
on Listen 3.3.0 (like we do for other optional dependencies):

```
.../bundler/rubygems_integration.rb:280:in `block (2 levels) in replace_gem': can't activate listen (~> 3.3), already activated listen-3.0.8. Make sure all dependencies are added to Gemfile. (Gem::LoadError)
```

There is definitely still room for improvement here, but this should
be much more helpful in figuring out that the issue is a bad Listen
version and not a bug in Rails.

[1]: 12b4fc54a9
2023-07-02 00:04:48 -04:00
John Hawthorn
ab01f9f3da Consider Symbol "JSON-ready", improve jsonify
Previously jsonify would call `.as_json` for Integer, nil, true, and
false, even though those types are considered "JSON-ready". Technically
a user could have overridden `.as_json` for these types but I can't
imagine a use case and I don't think we should support that.

I left the same behaviour of calling `.as_json` for generic "Numeric" as
that can have user subclasses where one may have implemented as_json.
This behaviour is also used for Float (which coerces
NaN/Infinity/-Infinity into nil).

This also adds Symbol to the list of "JSON-ready" types, to avoid
unnecessarily casting them to strings (possible as we no longer perform
escaping on input). The output of jsonify should never be user visible
before it is passed through JSON.generate, so I don't think this should
be a user facing
change.

This also corrects our handling of Hash to call to_s on all keys,
matching the behaviour of `.as_json` and JSON's requirement that keys
are Strings (Symbols are also permitted as JSON knows to convert them to
a String).
2023-06-30 11:38:38 -07:00
John Hawthorn
52be530755 Escape JSON output instead of string inputs
Rails performs additional escaping of strings compared to the JSON gem,
and will escape U+2028, U+2029, <, >, and &.

In JSON, the only places those characters are valid is inside of a
JSON string, so it should be equivalent (and faster) to perform the
escaping on the output.
2023-06-29 15:37:57 -07:00
John Hawthorn
66db67436d Avoid extra pass on AS::JSON.dump with no options
JSONGemEncoder.encode previously would always perform two passes. First
it would call `.as_json(options)`, but then would perform a second pass
"jsonify" to recursively call `.as_json` (this time without options)
until the data converges into a "JSON-ready" representation.

When options are not given, the second pass should be equivalent to the
first, so we can detect that, and only perform the "jsonify" step.

The only user-visible effect of this should be that we will pass no
options to `as_json` instead of an empty Hash, but implementations of
`as_json` should already be expected to accept that.
2023-06-29 15:37:57 -07:00
Hartley McGuire
088551c802
Move 6.1 cache format warning to where its used
A deprecation warning was [added][1] to ensure that applications
manually setting `config.active_support.cache_format_version` to `6.1`
will be aware that they need to migrate. However, if an app is not using
a `config.load_defaults` of `7.0` or greater, this warning will never be
triggered.

This commit moves the deprecation warning to where the `cache_format`
value gets used to cover both cases.

[1]: 2ba3ac29c36f4c6d23b1dd302584f15739ff2aff
2023-06-28 18:09:04 -04:00
Jean Boussier
2ba3ac29c3 Deprecate active_support.cache_format_version = 6.1
Once we finally get rid of it, we're no longer constrained
on the `Entry` internal representation as other coders don't
directly marshal the instance.
2023-06-28 10:52:54 +02:00
Étienne Barrié
78c7343057 Add :report behavior to ActiveSupport::Deprecation
This behavior uses the ErrorReporter to report a deprecation as a
handled error with :warning severity.
2023-06-26 16:23:19 +02:00
Christian Schmidt
11ed5b5c52 Rename Range#overlaps? to Range#overlap?
Rename for consistency with #include? and #cover?
2023-06-23 10:25:59 +02:00
zzak
dd89f600f7
🔗 Remove RDoc auto-link from Rails module everywhere 2023-06-23 10:49:30 +09:00
Hartley McGuire
8355658a56
Fix EncryptedConfiguration not behaving like Hash
Previously, `EncryptedConfiguration` was [updated][1] to use
`InheritableOptions` so that keys could be called like methods. It was
later [updated again][2] to ensure that it behaved both like a `Hash` and
`OrderedOptions`. In this second change, the `InheritableOptions`
instance was accidentally nested with another `InheritableOptions`
instance.

This continued to mostly work as expected because `InheritableOptions`
will fall back to the inner `InheritableOptions` when the outer one
doesn't have a key. However, any methods that try to treat the outer
`InheritableOptions` like it should know about all of its keys will fail
(for example, `#keys`, `#to_h`, `#to_json`, etc.)

This commit fixes the issue by removing the extraneous outer
`InheritableOptions` instance.

[1]: a6a9fed1719a3cfe47eb1566ae4d6034374fd809
[2]: 80585daf2def5bf94854be69f81e24a16ce14c55
2023-06-21 23:29:33 -04:00
Akira Matsuda
5bdd132d00
minitest is no longer a stdlib on any of the supported versions of Rubies
CRuby's bundled minitest had been removed in 2.2.0 at
7cda8222ca
2023-06-22 02:03:33 +09:00
zzak
3e8d8e67a9
Document ActiveSupport::TestCase.fixture_paths= and getter
These methods are only available when requiring the railties
"rails/test_help" file, which is included by default in the
test_helper.rb for newly generated Rails applications.

Since requiring that file is the only way, and some applications may
have removed it or not used test-unit when generating their application,
I think it's worth calling out explicitly here.
2023-06-20 15:47:12 +09:00
Sampat Badhe
3d5fa4c105
update doc to use to_fs over deprecated to_s 2023-06-19 13:18:28 +05:30
Guillermo Iguaran
811b103b3e
Merge pull request #48494 from ghiculescu/range-tofs
Support beginless and endless ranges in `to_fs`
2023-06-17 02:36:14 -07:00
Petrik
3f1526ae76 Don't show secrets for MessageEncryptor#inspect
If anyone calls a message encryptor in the console it will
show the secret of the encryptor.

By overriding the `inspect` method to only show the class name we can
avoid accidentally outputting sensitive information.

Before:

```ruby
ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm").inspect
"#<ActiveSupport::MessageEncryptor:0x0000000104888038 ... @secret=\"\\xAF\\bFh]LV}q\\nl\\xB2U\\xB3 ... >"
```

After:

```ruby
ActiveSupport::MessageEncryptor.new(secret, cipher: "aes-256-gcm").inspect
"#<ActiveSupport::MessageEncryptor:0x0000000104888038>"
```
2023-06-16 21:14:13 +02:00
Petrik
7ecd72e5c1 Don't show contents for EncryptedConfiguration#inspect
If anyone calls `Rails.application.credentials` in the console it will
show the unencrypted contents of the credentials.

By overriding the `inspect` method to only show the class name we can
avoid accidentally outputting sensitive information.

Before:
```ruby
Rails.application.credentials.inspect
"#<ActiveSupport::EncryptedConfiguration:0x000000010d2b38e8 ... @config={:secret=>\"something secret\"} ... @key_file_contents=\"915e4ea054e011022398dc242\" ...>"
```

After:
```ruby
Rails.application.credentials.inspect
"#<ActiveSupport::EncryptedConfiguration:0x000000010d2b38e8>"
```
2023-06-16 15:35:16 +02:00
Alex
3bfede1f22 Support beginless and endless ranges in to_fs
Fixes https://github.com/rails/rails/issues/48485
2023-06-16 16:47:48 +10:00
Jonathan Hefner
bd15567fe9 Use cache :coder option to specify :message_pack
In #48104, `:message_pack` was added as a supported value for the cache
format version because the format version essentially specifies the
default coder.  However, as an API, the format version could potentially
be used to affect other aspects of serialization, such as the default
compression format.

This commit removes `:message_pack` as a supported value for the format
version, and, as a replacement, adds support for specifying
`coder: :message_pack`:

  ```ruby
  # BEFORE
  config.active_support.cache_format_version = :message_pack

  # AFTER
  config.cache_store = :redis_cache_store, { coder: :message_pack }
  ```
2023-06-11 15:30:10 -05:00
Andrew Novoselac
745976bd57 Fix defect in Enumerable#many introduced in rails/rails@d862dff
This changed `many?` to yield with `element, *args`. The causes elements that are arrays to be destructured which can lead to defective behaviour. Since the introduction of `element, *args` was for readability purposes, we can just use `*args` instead and get the same behaviour without the defect.
2023-06-06 17:41:44 -04:00
Jean Boussier
9812641891 ActiveSupport::Deprecator stop using Singleton
Followup: https://github.com/rails/rails/pull/47354

It does a bit more than just giving you a `.instance` method
it also change the behavior of dup and clone, we don't need
any of that, and `.instance` is deprecated anyway.
2023-06-05 08:43:37 +02:00
Jean Boussier
c07812cee2 Eagerly validate pool arguments in Redis and MemCache stores
Fix: https://github.com/rails/rails/issues/48352

While we should ensure instantiating the store doesn't immediately
attempt to connect, we should eagerly process arguments so that
if they are somehow invalid we fail early during boot rather than at
runtime.

Additionally, since it's common to get pool parameters from environment
variable, we can use `Integer` and `Float` so that string representations
are valid.
2023-06-02 16:01:09 +02:00
Jean Boussier
a2f685c7c0 Cleanly fallback when failing to tokenize ERB templates
Fix: https://github.com/rails/rails/issues/48319
Followup: https://github.com/rails/rails/pull/48184
2023-06-01 11:35:31 +02:00
fatkodima
e3c546eb0c Fix humanize for strings ending with id 2023-05-31 02:16:37 +03:00
Jonathan Hefner
14676466b6 Avoid double marshal with MemoryStore::DupCoder
Prior to this commit, `MemoryStore::DupCoder` called `Entry#dup_value!`
in both `dump` and `load` to guard against external mutation of cached
values.  `Entry#dup_value!` calls `Marshal.dump` then `Marshal.load` to
dup complex objects.  However, to prevent external mutation, we only
need to call `Marshal.dump` once for `DupCoder.dump` and `Marshal.load`
once for `DupCoder.load`.

This commit changes `DupCoder` to call `Marshal.dump` and `Marshal.load`
directly instead of relying on `Entry#dup_value!`, thus halving the work
done by `Marshal` when writing and reading complex objects.

__Benchmark__

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support"
  require "active_support/cache"

  LARGE_OBJECT = 100.times.map { "x" * 100 }
  LARGE_STRING = LARGE_OBJECT.join

  cache = ActiveSupport::Cache.lookup_store(:memory_store)

  Benchmark.ips do |x|
    x.report("large string") do
      cache.write("x", LARGE_STRING)
      cache.read("x")
    end

    x.report("large object") do
      cache.write("x", LARGE_OBJECT)
      cache.read("x")
    end
  end
  ```

__Before__

  ```
  Warming up --------------------------------------
          large string     2.667k i/100ms
          large object   332.000  i/100ms
  Calculating -------------------------------------
          large string     26.539k (± 1.8%) i/s -    133.350k in   5.026373s
          large object      3.336k (± 0.9%) i/s -     16.932k in   5.076458s
  ```

__After__

  ```
  Warming up --------------------------------------
          large string     2.541k i/100ms
          large object   715.000  i/100ms
  Calculating -------------------------------------
          large string     25.117k (± 1.7%) i/s -    127.050k in   5.059945s
          large object      7.111k (± 1.2%) i/s -     35.750k in   5.028267s
  ```

Closes #46403.

Co-authored-by: Breno Gazzola <breno.gazzola@gmail.com>
Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-05-25 16:13:04 -05:00
zzak
38bef29064
Replace all occurrences of '<tt>(\w+::\w+::\w+)</tt>' with '+$1+'
E.g.:

* <tt>Rails::Command::NotesCommand</tt> -> +Rails::Command::NotesCommand+

Co-authored-by: Hartley McGuire <skipkayhil@gmail.com>
2023-05-25 06:56:17 +09:00
zzak
e3c73fd183
Replace all occurrences of '<tt>(\w+::\w+)</tt>' with '+$1+'
E.g.:

* <tt>ActiveRecord::Base</tt> -> +ActiveRecord::Base+

Co-authored-by: Hartley McGuire <skipkayhil@gmail.com>
Co-authored-by: Petrik de Heus <petrik@deheus.net>
2023-05-25 06:52:32 +09:00
Mike Dalessio
aea8849821 ERB::Util.html_escape_once always returns an html_safe string
This method previously maintained the `html_safe?` property of a string on the return
value. Because this string has been escaped, however, not marking it as `html_safe` causes
entities to be double-escaped.

As an example, take this view snippet:

  ```html
  <p><%= html_escape_once("this & that &amp; the other") %></p>
  ```

Before this change, that would be double-escaped and render as:

  ```html
  <p>this &amp;amp; that &amp;amp; the other</p>
  ```

After this change, it renders correctly as:

  ```html
  <p>this &amp; that &amp; the other</p>
  ```

[Fix #48256]
2023-05-22 12:02:03 +02:00
Mike Dalessio
4b6894168b
Deprecate SafeBuffer#clone_empty
This method is unused within the Rails codebase, the last caller was
removed by 479c7cac in 2014 (Rails 4.2.0).
2023-05-20 12:45:43 -04:00
Hartley McGuire
b3c6a9adf0
Remove explicit "aliased as" documentation
Most of these are redundant because rdoc handles these itself, but
`titlecase` on `ActiveSupport::Inflector` does not exist so that one is
just incorrect.
2023-05-18 11:04:04 -04:00
Jonathan Hefner
76ea586c6a Handle non-ASCII-only in AS::MessagePack#signature?
Because `ActiveSupport::MessagePack::Serializer::SIGNATURE` includes a
non-ASCII-only byte (`"\xCC"`), it raises `Encoding::CompatibilityError`
when compared with another string that is not encoded with
`Encoding::BINARY` and also includes a non-ASCII-only byte.

To prevent that, this commit changes `AS::MessagePack#signature?` to
directly compare the first two bytes of both strings.

Fixes #48196.
2023-05-11 21:13:45 -05:00
Juan Manuel Ramallo
1a63f9410d [Documentation] ActiveSupport::InheritableOptions with string keys
Updated the ActiveSupport::InheritableOptions documentation to exemplify
how to use a parent hash that contains string keys.

    parent = { "foo" => true }
    child = ActiveSupport::InheritableOptions.new(parent.symbolize_keys)
    child.foo # => true

This example is helpful because ActiveSupport::OrderedOptions only deals
with symbol keys and this implementation detail is hidden from the API
docs.

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2023-05-10 13:05:40 -05:00
Jonathan Hefner
af6d83521c Support :message_pack as message serializer
This commit adds support for `:message_pack` as a serializer for
`MessageEncryptor` and `MessageVerifier`, and, consequently, as an
option for `config.active_support.message_serializer`.

The `:message_pack` serializer is implemented via
`ActiveSupport::Messages::SerializerWithFallback` and can fall back to
deserializing with `AS::JSON`.  Additionally, the `:marshal`, `:json`,
and `:json_allow_marshal` serializers can now fall back to deserializing
with `AS::MessagePack`.

This commit also adds support for `:message_pack_allow_marshal` as a
serializer, which can fall back to deserializing with `Marshal` as well
as `AS::JSON`.
2023-05-08 14:39:00 -05:00
Jonathan Hefner
4143c0b3e8 Default message serializer to :json_allow_marshal
Prior to this commit, `config.load_defaults 7.1` would cause old
messages (or messages from older apps) to become unreadable, and
developers were encouraged to manually set
`config.active_support.message_serializer = :json_allow_marshal` in
order to prevent this.

This commit changes the default message serializer set by
`config.load_defaults 7.1` from `:json` to `:json_allow_marshal` so that
upgraded apps can continue to read old messages without additional
configuration.

The intention is to eventually change the default to `:json` (with no
`Marshal` fallback) in Rails 7.2, after some time has passed with apps
generating JSON-serialized messages.

Apps can opt in to JSON-only serialization before Rails 7.2 by manually
setting `config.active_support.message_serializer = :json`.

Fixes #48118.
2023-05-08 12:41:53 -05:00
Jonathan Hefner
9fbfd8100b Unify Message{Encryptor,Verifier} serializer config
In #42843 and #42846, several config settings were added to control the
default serializer for `MessageEncryptor` and `MessageVerifier`, and to
provide a migration path from a default `Marshal` serializer to a
default `JSON` serializer:

* `config.active_support.default_message_encryptor_serializer`
  * Supports `:marshal`, `:hybrid`, or `:json`.
* `config.active_support.default_message_verifier_serializer`
  * Supports `:marshal`, `:hybrid`, or `:json`.
* `config.active_support.fallback_to_marshal_deserialization`
  * Affects `:hybrid` for both `MessageEncryptor` and `MessageVerifier`.
* `config.active_support.use_marshal_serialization`
  * Affects `:hybrid` for both `MessageEncryptor` and `MessageVerifier`.

This commit unifies those config settings into a single setting,
`config.active_support.message_serializer`, which supports `:marshal`,
`:json_allow_marshal`, and `:json` values.  So, for example,

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = true
  config.active_support.use_marshal_serialization = false
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :json_allow_marshal
  ```

and

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = false
  config.active_support.use_marshal_serialization = false
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :json
  ```

This commit also replaces `ActiveSupport::JsonWithMarshalFallback` with
`ActiveSupport::Messages::SerializerWithFallback`, which implements a
generic mechanism for serializer fallback.  The `:marshal` serializer
uses this mechanism too, so

  ```ruby
  config.active_support.default_message_encryptor_serializer = :hybrid
  config.active_support.default_message_verifier_serializer = :hybrid
  config.active_support.fallback_to_marshal_deserialization = false
  config.active_support.use_marshal_serialization = true
  ```

becomes

  ```ruby
  config.active_support.message_serializer = :marshal
  ```

Additionally, the logging behavior of `JsonWithMarshalFallback` has been
replaced with notifications which include the names of the intended and
actual serializers, as well as the serialized and deserialized message
data.  This provides a more targeted means of tracking serializer
fallback events.  It also allows the user to "silence" such events, if
desired, without an additional config setting.

All of these changes make it easier to add migration paths for new
serializers such as `ActiveSupport::MessagePack`.
2023-05-08 12:09:45 -05:00
Jonathan Hefner
bda3539053
Merge pull request #48150 from jonathanhefner/cache-summarize-logged-multi-keys
Log key summary for `*_multi` cache operations
2023-05-07 22:21:49 -05:00
Jean Boussier
2dc1edec2b
Merge pull request #48159 from jonathanhefner/cache-delete_multi-empty-key-list 2023-05-08 02:04:46 +02:00
Hartley McGuire
dcf20cbc56
Use "library.name" format in initializer examples
This updates two instances of custom initializer examples in the docs to
use the standard "library.name" format. Previously, one used just "name"
which can lead to hard to debug "cyclic dependency" errors, and the
other used "name.library" which is not the suggested format.
2023-05-07 18:11:16 -04:00
Jonathan Hefner
15df103efa Handle empty key list for delete_multi
Follow-up to #48154.

This adds short-circuiting behavior to `delete_multi` when an empty key
list is specified in order to prevent an `ArgumentError` from being
raised, similar to `read_multi`.
2023-05-07 12:35:49 -05:00
Jonathan Hefner
3a0b8f7427 Log key summary for *_multi cache operations
This commit addresses a few problems:

1.  `read_multi` (and `fetch_multi` and `delete_multi`) logs multiple
    keys as if they were a single composite key.  For example,
    `read_multi("posts/1", "posts/2")` will log "Cache read_multi:
    posts/1/posts/2".  This can make the log confusing or indecipherable
    when keys contain more slashes, such as with view fragments.

2.  `write_multi` logs its entire argument as a single composite key.
    For example, `write_multi("p1" => post1, "p2" => post2)` will log
    "Cache write_multi: p1=#<Post:0x...>/p2=#<Post:0x...>".

3.  `MemoryStore#cleanup` logs its instrumentation payload instead of
    setting it on the event.  For example, when 10 entries are in the
    cache, `cleanup` will log "Cache cleanup: size=10" instead of
    merging `{ size: 10 }` into the event payload.

Multi-key logging was first added in ca6aba7f30ad9910f17e4c5b39667889d9518794,
then reverted in c4a46fa781d39f1b4607cb1613a1c67fa044ce54 due to being
unwieldy, and then re-added in 2b96d5822bfe407be7589e293f3265c0c7a6726c
(for `write_multi`) and 62023884f76c108127c8966f4d67bb717338dd66 (for
`read_multi`) but without any handling or formatting.

This commit changes the way multi-key operations are logged in order to
prevent these problems.  For example, `read_multi("posts/1", "posts/2")`
will now log "Cache read_multi: 2 key(s) specified", and
`write_multi("p1" => post1, "p2" => post2)` will now log "Cache
write_multi: 2 key(s) specified".
2023-05-07 12:17:51 -05:00
Jean Boussier
431f9f47f5
Merge pull request #48154 from Shopify/empty-collection-cache-key 2023-05-07 11:01:57 +02:00
Jean Boussier
d3b5cdf220 Handle empty list of cache keys
Fix: https://github.com/rails/rails/pull/48145

`read_multi`, `write_multi` and `fetch multi` should all
bail out early if somehow called with an empty list.

Co-Authored-By: Joshua Young <djry1999@gmail.com>
2023-05-07 17:50:54 +09:00
Rafael Mendonça França
15640eeb4d
Merge pull request #47826 from skipkayhil/doc-test-case-aliases
Add docs for assert_not TestCase aliases
2023-05-05 12:23:31 -04:00
Rafael Mendonça França
7d9b5b6313
Merge pull request #48088 from skipkayhil/hm-log-sub-docs
More Subscriber and LogSubscriber doc uniformity [ci skip]
2023-05-05 12:21:50 -04:00
Jonathan Hefner
490804f7d5
Merge pull request #48125 from jonathanhefner/message_pack-cache-serializer-silence-warning
Silence msgpack warnings when detecting format
2023-05-04 16:37:32 -05:00
Jonathan Hefner
daa0cb80db Improve cache performance for bare string values
This commit introduces a performance optimization for cache entries with
bare string values such as view fragments.

A new `7.1` cache format has been added which includes the optimization,
and the `:message_pack` cache format now includes the optimization as
well.  (A new cache format is necessary because, during a rolling
deploy, unupgraded servers must be able to read cache entries from
upgraded servers, which means the optimization cannot be enabled for
existing apps by default.)

New apps will use the `7.1` cache format by default, and existing apps
can enable the format by setting `config.load_defaults 7.1`.  Cache
entries written using the `6.1` or `7.0` cache formats can be read when
using the `7.1` format.

**Benchmark**

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"

  serializer_7_0 = ActiveSupport::Cache::SerializerWithFallback[:marshal_7_0]
  serializer_7_1 = ActiveSupport::Cache::SerializerWithFallback[:marshal_7_1]
  entry = ActiveSupport::Cache::Entry.new(Random.bytes(10_000), version: "123")

  Benchmark.ips do |x|
    x.report("dump 7.0") do
      $dumped_7_0 = serializer_7_0.dump(entry)
    end

    x.report("dump 7.1") do
      $dumped_7_1 = serializer_7_1.dump(entry)
    end

    x.compare!
  end

  Benchmark.ips do |x|
    x.report("load 7.0") do
      serializer_7_0.load($dumped_7_0)
    end

    x.report("load 7.1") do
      serializer_7_1.load($dumped_7_1)
    end

    x.compare!
  end
  ```

  ```
  Warming up --------------------------------------
              dump 7.0     5.482k i/100ms
              dump 7.1    10.987k i/100ms
  Calculating -------------------------------------
              dump 7.0     73.966k (± 6.9%) i/s -    367.294k in   5.005176s
              dump 7.1    127.193k (±17.8%) i/s -    615.272k in   5.081387s

  Comparison:
              dump 7.1:   127192.9 i/s
              dump 7.0:    73966.5 i/s - 1.72x  (± 0.00) slower

  Warming up --------------------------------------
              load 7.0     7.425k i/100ms
              load 7.1    26.237k i/100ms
  Calculating -------------------------------------
              load 7.0     85.574k (± 1.7%) i/s -    430.650k in   5.034065s
              load 7.1    264.877k (± 1.6%) i/s -      1.338M in   5.052976s

  Comparison:
              load 7.1:   264876.7 i/s
              load 7.0:    85573.7 i/s - 3.10x  (± 0.00) slower
  ```

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-05-04 16:10:22 -05:00
Jonathan Hefner
bbedd4f2ba Silence msgpack warnings when detecting format
This silences a "ActiveSupport::MessagePack requires the msgpack gem"
warning that can occur when using a non-`:message_pack` serializer and
trying to detect the format of a serialized cache entry.

`silence_warnings` is a blunt instrument, but this `require` is only for
decoding legacy cache entries that were encoded using `:message_pack`,
if any.  (i.e. If `:message_pack` is currently being used as a
serializer, then `SerializerWithFallback::[]` should have already loaded
`active_support/message_pack`.)  Therefore, it seems less hazardous to
inadvertently silence other warnings that may occur when loading the
file.

Co-authored-by: Alex Ghiculescu <alex@tanda.co>
2023-05-03 21:34:08 -05:00
Jonathan Hefner
e2524e574b Support :message_pack as a cache serializer format
This commit adds support for `:message_pack` as an option for
`config.active_support.cache_format_version`.

Cache entries written using the `6.1` or `7.0` formats can be read when
using the `:message_pack` format. Additionally, cache entries written
using the `:message_pack` format can now be read when using the `6.1` or
`7.0` format. These behaviors makes it easy to migrate between formats
without invalidating the entire cache.
2023-05-03 14:22:20 -05:00
Jean Boussier
e54ef0ab14 Object#deep_dup no longer duplicate named classes and modules.
It makes very little sense to duplicate named classes and modules.

Before:

```ruby
hash = { class: Object, module: Kernel }
hash.deep_dup # => {:class=>#<Class:0x00000001063ffc80>, :module=>#<Module:0x00000001063ffa00>}
```

After:

```ruby
hash = { class: Object, module: Kernel }
hash.deep_dup # => {:class=>Object, :module=>Kernel}
```
2023-05-02 10:54:46 +02:00
Hartley McGuire
2384eb401e
More Subscriber and LogSubscriber doc uniformity
attach_to was previously [improved][1] to allow defining methods after
attaching instead of having to attach after all methods have been
defined. The docs for Subscriber were [updated][2], but LogSubscriber
docs were not.

This commit copies the attach_to doc changes from Subscriber to
LogSubscriber. In addition, there are other improvements to linking and
applying the monospace formatting that were present in one of Subscriber
or LogSubscriber and are now applied to both. The final change is
updating the description of how flush_all! is used, because its usage
has [changed][3] since this doc was written.

[1]: 34088572270a1dd5a2164b6aa5fc3642cb0479cb
[2]: 25b3f738e4f5b09e4d6a66e1454e697defcdda2c
[3]: 378464a2e47bb849f3351cb8c87366554b7ce74d
2023-04-27 22:16:51 -04:00
Joshua Young
2b88e78067 Consistently raise an ArgumentError if the ActiveSupport::Cache key is blank 2023-04-26 22:14:31 +10:00
Miha Rekar
ffa27f314a
Only flatten first level to preserve nested 2023-04-25 16:56:09 +02:00
Petrik
ec28de4260 Add missing headers to Active Support docs [ci-skip]
Having a h1 heading will improve SEO and makes things look more consistent.
2023-04-23 16:02:56 +02:00
Xavier Noria
92e088a4d5 stub_const polishing 2023-04-21 22:19:39 +02:00
Jean Boussier
d78397916e Module#delegate directly define methods as private
Fix: https://github.com/rails/rails/issues/47999

Useful for `method_added` hooks, but also should perform
very sligtly better for large list of delegators.
2023-04-20 23:38:09 +02:00
Jean Boussier
138a9d2031
Merge pull request #47982 from nobu/patch-3
Reduce captures in `ActiveSupport::Inflector#underscore`
2023-04-20 18:03:05 +02:00
Nobuyoshi Nakada
4c0cee83dc Reduce captures in ActiveSupport::Inflector#underscore 2023-04-20 18:01:53 +02:00
Guillermo Iguaran
c2ec6e7b88 Add ActiveSupport::Cache::MemCacheStore#inspect
Make the output more readable.
This is a follow up of https://github.com/rails/rails/pull/47944
2023-04-19 21:02:32 -07:00
Jean Boussier
8d9eaf366b
Merge pull request #47944 from the-spectator/inspect_for_cache_store
Implement #inspect for file & null cache store
2023-04-19 23:35:12 +02:00
Jonathan Hefner
a2a6331451 Add ActiveSupport::MessagePack
`ActiveSupport::MessagePack` is a serializer that integrates with the
`msgpack` gem to serialize a variety of Ruby objects.  `AS::MessagePack`
supports several types beyond the base types that `msgpack` supports,
including `Time` and `Range`, as well as Active Support types such as
`AS::TimeWithZone` and `AS::HashWithIndifferentAccess`.

Compared to `JSON` and `Marshal`, `AS::MessagePack` can provide a
performance improvement and message size reduction.  For example, when
used with `MessageVerifier`:

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support/all"
  require "active_support/message_pack"

  marshal_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: Marshal)
  json_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)
  asjson_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: ActiveSupport::JSON)
  msgpack_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: ActiveSupport::MessagePack)

  ActiveSupport::Messages::Metadata.use_message_serializer_for_metadata = true
  expiry = 1.year.from_now
  data = { bool: true, num: 123456789, string: "x" * 50 }

  Benchmark.ips do |x|
    x.report("Marshal") do
      marshal_verifier.verify(marshal_verifier.generate(data, expires_at: expiry))
    end

    x.report("JSON") do
      json_verifier.verify(json_verifier.generate(data, expires_at: expiry))
    end

    x.report("AS::JSON") do
      asjson_verifier.verify(asjson_verifier.generate(data, expires_at: expiry))
    end

    x.report("MessagePack") do
      msgpack_verifier.verify(msgpack_verifier.generate(data, expires_at: expiry))
    end

    x.compare!
  end

  puts "Marshal size: #{marshal_verifier.generate(data, expires_at: expiry).bytesize}"
  puts "JSON size: #{json_verifier.generate(data, expires_at: expiry).bytesize}"
  puts "MessagePack size: #{msgpack_verifier.generate(data, expires_at: expiry).bytesize}"
  ```

  ```
  Warming up --------------------------------------
               Marshal     1.206k i/100ms
                  JSON     1.165k i/100ms
              AS::JSON   790.000  i/100ms
           MessagePack     1.798k i/100ms
  Calculating -------------------------------------
               Marshal     11.748k (± 1.3%) i/s -     59.094k in   5.031071s
                  JSON     11.498k (± 1.4%) i/s -     58.250k in   5.066957s
              AS::JSON      7.867k (± 2.4%) i/s -     39.500k in   5.024055s
           MessagePack     17.865k (± 0.8%) i/s -     89.900k in   5.032592s

  Comparison:
           MessagePack:    17864.9 i/s
               Marshal:    11747.8 i/s - 1.52x  (± 0.00) slower
                  JSON:    11498.4 i/s - 1.55x  (± 0.00) slower
              AS::JSON:     7866.9 i/s - 2.27x  (± 0.00) slower

  Marshal size: 254
  JSON size: 234
  MessagePack size: 194
  ```

Additionally, `ActiveSupport::MessagePack::CacheSerializer` is a
serializer that is suitable for use as an `ActiveSupport::Cache` coder.
`AS::MessagePack::CacheSerializer` can serialize `ActiveRecord::Base`
instances, including loaded associations.  Like `AS::MessagePack`, it
provides a performance improvement and payload size reduction:

  ```ruby
  # frozen_string_literal: true

  require "benchmark/ips"
  require "active_support/message_pack"

  ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")

  ActiveRecord::Schema.define do
    create_table :posts, force: true do |t|
      t.string :body
      t.timestamps
    end

    create_table :comments, force: true do |t|
      t.integer :post_id
      t.string :body
      t.timestamps
    end
  end

  class Post < ActiveRecord::Base
    has_many :comments
  end

  class Comment < ActiveRecord::Base
    belongs_to :post
  end

  post = Post.create!(body: "x" * 100)
  2.times { post.comments.create!(body: "x" * 100) }
  post.comments.load
  cache_entry = ActiveSupport::Cache::Entry.new(post)

  Rails70Coder = ActiveSupport::Cache::Coders::Rails70Coder
  CacheSerializer = ActiveSupport::MessagePack::CacheSerializer

  Benchmark.ips do |x|
    x.report("Rails70Coder") do
      Rails70Coder.load(Rails70Coder.dump(cache_entry))
    end

    x.report("CacheSerializer") do
      CacheSerializer.load(CacheSerializer.dump(cache_entry))
    end

    x.compare!
  end

  puts "Rails70Coder size: #{Rails70Coder.dump(cache_entry).bytesize}"
  puts "CacheSerializer size: #{CacheSerializer.dump(cache_entry).bytesize}"
  ```

  ```
  Warming up --------------------------------------
          Rails70Coder   329.000  i/100ms
       CacheSerializer   492.000  i/100ms
  Calculating -------------------------------------
          Rails70Coder      3.285k (± 1.7%) i/s -     16.450k in   5.008447s
       CacheSerializer      4.895k (± 2.4%) i/s -     24.600k in   5.028803s

  Comparison:
       CacheSerializer:     4894.7 i/s
          Rails70Coder:     3285.4 i/s - 1.49x  slower

  Rails70Coder size: 808
  CacheSerializer size: 593
  ```

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-04-17 11:56:06 -05:00
Jonathan Hefner
0b5505e826 Fix typos for AS::Notifications::Fanout::Handle [ci-skip] 2023-04-16 18:26:50 -05:00
Jonathan Hefner
2f9b55330b Fix monospace formatting [ci-skip]
Follow-up to #47916.

RDoc requires `<tt>` in this case.
2023-04-16 18:15:01 -05:00
Jean Boussier
b71b08722b
Merge pull request #47912 from nashby/delegate-implicit-block
Fix delegation to a method with implicit block.
2023-04-16 13:33:32 +02:00
Étienne Barrié
b3c3bb6792 Configure serialization of metadata per MessageVerifier object 2023-04-15 17:14:58 -05:00
Akshay Birajdar
7da4f7ffd4 Implement #inspect for file & null cache store 2023-04-15 09:09:58 +05:30
Hartley McGuire
5b2fb49ed0
Add docs for assert_not TestCase aliases
Previously, these could be found in the Testing guide, but not in the
api documentation.

The assert_raise alias was moved to ActiveSupport::Testing::Assertions
because that is where assert_raises is defined (and rdoc handles that
case).

The Minitest aliases are not easily handled by rdoc, so they are written
by hand. We could copy the documentation for the aliased methods here,
but linking to them seems sufficient.
2023-04-11 19:18:23 -04:00
Jonathan Hefner
50189c46d3 Simplify reference links [ci-skip]
These references will be properly linked even without the fully
qualified module name.
2023-04-11 16:36:53 -05:00
Jonathan Hefner
2c371c67b7 Linkify code references [ci-skip]
Follow-up to #47354.
2023-04-11 16:28:31 -05:00
Vasiliy Ermolovich
bbb13631f8 Fix delegation to a method with implicit block.
When 8b2f57dc6f was
introduced it broke delegation of a method with implicit block like this:

```ruby
class Foo
  def self.bar
    yield
  end
end

class Bar
  delegate :bar, to: Foo
end

Bar.new.bar { } # raises LocalJumpError: no block given (yield)
```

It happens because it's impossible to detect a method with implicit block and
we rely on method's parameters when we generate delegated methods.

I've found only one solution to this issue: we always generate a method that explicitly accepts
block. It kind of makes sense since evey Ruby method accepts block implicitly anyway.

Fixes https://github.com/rails/rails/issues/47624
2023-04-10 19:28:26 +02:00
Alex Ghiculescu
f4aa7ad9a6 Cache: warning if expires_in is given an incorrect value
In Rails 7, if you do `Rails.cache.write(key, value, expires_in: 1.minute.from_now)`, it will work. The actual expiration will be much more than a minute away, but it won't raise. (The correct code is `expires_in: 1.minute` or `expires_at: 1.minute.from_now`.)

Since https://github.com/rails/rails/pull/45892 the same code will error with:

```
NoMethodError: undefined method `negative?' for 2008-04-24 00:01:00 -0600:Time
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:743:in `merged_options'
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:551:in `write'
```

To make it a bit easier to upgrade to Rails 7.1, this PR introduces a better error if you pass a `Time` object to `expires_in:`

```
ArgumentError: expires_in parameter should not be a Time. Did you mean to use expires_at? Got: 2023-04-07 14:47:45 -0600
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:765:in `handle_invalid_expires_in'
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:745:in `merged_options'
    /Users/alex/Code/rails/activesupport/lib/active_support/cache.rb:551:in `write'
```
2023-04-09 09:57:47 -06:00
Rafael Mendonça França
2f08f9ac2c
Merge pull request #47354 from etiennebarrie/deprecate-ActiveSupport-Deprecation-usage
Deprecate ActiveSupport::Deprecation singleton usage
2023-04-06 10:20:38 -04:00
Petrik de Heus
dc0f20595d
Merge pull request #47717 from p8/docs/include-readmes
Include READMEs in main framework pages of the API documentation
2023-03-30 16:43:14 +02:00
Rafael Mendonça França
ddd6f8ecfe
Merge pull request #47771 from a5-stable/add-more-storage-units
Add zettabyte to `number_to_human_size`
2023-03-29 11:52:45 -04:00
fatkodima
ba2a26e083 Fix Enumerable#in_order_of to preserve duplicates 2023-03-29 15:15:14 +03:00
Rafael Mendonça França
920b930856
Use compute_if_absent to make sure we don't get race conditions 2023-03-28 17:38:45 +00:00
Rafael Mendonça França
689fafa1a1
Merge pull request #47736 from rails/thread-safety
Fix Fanout thread safety
2023-03-28 13:37:55 -04:00
Jean Boussier
49f6870db0
Merge pull request #47788 from ghiculescu/frozen-inflections
Ensure `Inflector` methods return un-frozen strings
2023-03-28 17:36:01 +02:00
Alex Ghiculescu
130c675069 Ensure Inflector methods return un-frozen strings
3d00c8b97f introduced a regression:

```ruby
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym("API")
end
```

If you have a controller called `APIController`, you may get a crash backtrace that looks like this:

```
FrozenError: can't modify frozen String: "API"
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/http/request.rb:89:in `controller_class_for'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/request/utils.rb💯in `action_encoding_template'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/request/utils.rb:85:in `encode'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/request/utils.rb:45:in `set_binary_encoding'
    rails (b8a399cd6ffe) actionpack/lib/action_dispatch/http/parameters.rb:68:in `path_parameters='
```

And you can see the issue in action like this:

```ruby
Tanda @ Rails 7.1.0.alpha (test) :004 > "API".freeze.underscore.frozen?
false # this is expected
Tanda @ Rails 7.1.0.alpha (test) :004 > "API".freeze.underscore.camelize.frozen?
false # this is *not* expected, and is what causes the above code to crash
```

I think the correct behaviour is for the inflector to always return non-frozen strings, even if it's using a cached frozen string as is the case in 3d00c8b97f

Other semi-related PRs: https://github.com/rails/rails/pull/41174, https://github.com/rails/rails/pull/41881
cc @amatsuda @byroot
2023-03-28 08:50:54 -06:00
a5-stable
b9a5a4bed8 add zetta to number_to_human_size 2023-03-28 19:50:13 +09:00
zzak
784f1c75a0
Merge pull request #47548 from the-spectator/tagged_logging_example
[ci-skip] Improve tagged logging with block doc
2023-03-28 16:54:55 +09:00
zzak
3d9edd4ced
Merge pull request #47774 from zzak/fix-race-condition-in-evented-file-update-checker
Fix race condition in evented file update checker
2023-03-27 09:49:28 +09:00
zzak
754442a0cb
Use original start instead of restart here 2023-03-27 07:59:06 +09:00
Gert Goet
23674c5853 Bring truncate* docs in line with signatures 2023-03-25 15:49:07 +01:00
Jean Boussier
c650ef4230 Improve reliability of EventedFileUpdateCheckerTest fork test
`Listen.to` starts a bunch of background threads that need to perform
some work before they are able to receive events, but it doesn't block
until they are ready, which expose us to a race condition.

With `wait_for_state(:processing_events)` we can ensure that it's ready on
Linux, however on macOS, the Darwin backend has a second background thread
we can't wait on.

As a workaround we wait a bit after the fork to allow that thread to
reach it's listning state.
2023-03-24 14:24:07 +01:00
Aaron Patterson
ea2870c86e
Fix Fanout thread safety
Fanout is a singleton shared among threads, so all of its hash maps
should be concurrent hash maps otherwise we'll see errors like this:

```
RuntimeError: can't add a new key into hash during iteration
```
2023-03-22 12:49:54 -07:00
Jean Boussier
e55ccf0f10 Mark RaiseWarnings as :nodoc:
I didn't expect RDoc to document this code.
2023-03-22 18:38:23 +01:00
Petrik
7c94708d24 Include READMEs in main framework pages of the API documentation
Currently when opening the main framework pages there is no introduction
to the framework. Instead we only see a whole lot of modules and the
`gem_version` and `version` methods.

By including the READMEs using the `:include:` directive each frameworks
has a nice introduction.
For markdown READMEs we need to add the :markup: directive.

[ci-skip]

Co-authored-by: zzak <zzakscott@gmail.com>
2023-03-21 21:16:28 +01:00
Jean Boussier
210932662b Also undefine with on Symbol 2023-03-16 11:04:19 +00:00
Jean Boussier
dc6bfecdef Undefine with on NilClass and other common immediate types
with isn't usable on immediates, so we might as well undefine the
method in common immediate classes to avoid potential confusion.
2023-03-16 10:29:54 +00:00
Akshay Birajdar
f14cbc5967 [ci-skip] Add yielded logger instance for tagged logging with block example 2023-03-15 01:14:46 +05:30
Étienne Barrié
a7feaaf338 Use the singleton instance as ActiveSupport deprecator 2023-03-14 17:33:48 +01:00
Étienne Barrié
a3061e902e Document deprecators for library authors 2023-03-14 17:33:48 +01:00
Étienne Barrié
c8eeac52f8 Deprecate ActiveSupport::Deprecation.instance 2023-03-14 17:33:48 +01:00
Étienne Barrié
0f0aa86091 Apply instance delegators to application deprecators when available
This restores the 7.0 behavior of the instance delegator impacting all
deprecations from Rails.
2023-03-14 17:33:48 +01:00
Étienne Barrié
e5af9c298a Deprecate ActiveSupport::Deprecation instance delegation
All methods called on ActiveSupport::Deprecation are delegated to the
singleton instance, but now that each framework has its own Deprecation
instance, it's no longer necessary for internal deprecations.

For external code, instead of using ActiveSupport::Deprecation.warn,
they should create their own instance of the class and call warn on it.
2023-03-14 17:32:52 +01:00
Akira Matsuda
217411564e Implement SafeBuffer#bytesplice
[CVE-2023-28120]
2023-03-13 12:13:31 -07:00
Étienne Barrié
33c5a60c40 Deprecate DeprecatedConstantProxy without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
8f6eda6bfe Deprecate DeprecatedInstanceVariableProxy without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
83df54cced Deprecate DeprecatedObjectProxy without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
8171ee0722 Deprecate deprecation assertions without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
6ce3dd9daf Deprecate deprecate_constant without a deprecator 2023-03-13 13:33:16 +01:00
Étienne Barrié
675cf7928d Deprecate Module.deprecate without a deprecator 2023-03-13 13:32:40 +01:00
Petrik
c3984c250e Fix small typo in Object#With rdoc [ci-skip] 2023-03-06 17:23:37 +01:00
Jean Boussier
1884323574 Implement Object#with
Use case

A very common pattern in Ruby, especially in testing is to save the value of an attribute, set a new value, and then restore the old value in an `ensure` clause.

e.g. in unit tests

```ruby
def test_something_when_enabled
  enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true
  # test things
ensure
  SomeLibrary.enabled = enabled_was
end
```

Or sometime in actual APIs:

```ruby
def with_something_enabled
  enabled_was = @enabled
  @enabled = true
  yield
ensure
  @enabled = enabled_was
end
```

There is no inherent problem with this pattern, but it can be easy to make a mistake, for instance the unit test example:

```ruby
def test_something_when_enabled
  some_call_that_may_raise
  enabled_was, SomeLibrary.enabled = SomeLibrary.enabled, true
  # test things
ensure
  SomeLibrary.enabled = enabled_was
end
```

In the above if `some_call_that_may_raise` actually raises, `SomeLibrary.enabled` is set back to `nil` rather than its original value. I've seen this mistake quite frequently.

Object#with

I think it would be very useful to have a method on Object to implement this pattern in a correct and easy to use way.

NB: `public_send` is used because I don't think such method should be usable if the accessors are private.

With usage:

```ruby
def test_something_when_enabled
  SomeLibrary.with(enabled: true) do
    # test things
  end
end
```

```ruby
GC.with(measure_total_time: true, auto_compact: false) do
  # do something
end
```

Lots of tests in Rails's codebase could be simplified, e.g.:
  - Changing `Thread.report_on_exception`: 2d2fdc941e/activerecord/test/cases/connection_pool_test.rb (L583-L595)
  - Changing a class attribute: 2d2fdc941e/activerecord/test/cases/associations/belongs_to_associations_test.rb (L136-L150)
2023-03-06 09:02:41 +01:00
Rafael Mendonça França
5aa84bcea1
Merge pull request #47573 from codergeek121/fix-timewithzone-argumenterror
Fixes TimeWithZone ArgumentError #47572
2023-03-03 18:02:48 -05:00
Rafael Mendonça França
fee2bf3f80
Revert "Remove deprecated children and parent_of? on ActiveSupport::Notifications::Event"
This reverts commit 29679df058e057cdb75e0978204b576d86dfb4fb.

This method was deprecated in 7.1, so shouldn't be removed until 7.2.
2023-03-03 22:38:21 +00:00
Niklas Haeusele
f9868208b5 Fixes #47572
Use triple dots in method_missing to pass on arguments
2023-03-03 20:50:47 +01:00
Rafael Mendonça França
7b4affc78b
Remove deprecated support to generate incorrect RFC 4122 UUIDs 2023-03-03 00:38:36 +00:00
Rafael Mendonça França
29679df058
Remove deprecated children and parent_of? on ActiveSupport::Notifications::Event 2023-03-03 00:38:35 +00:00
Rafael Mendonça França
f02998d2b5
Remove implicit conversion of objects into String by ActiveSupport::SafeBuffer 2023-03-03 00:38:34 +00:00
Rafael Mendonça França
f0ddb7709b
Remove deprecated active_support/core_ext/range/include_time_with_zone file 2023-03-03 00:38:33 +00:00
Rafael Mendonça França
da8e6f6175
Remove deprecated active_support/core_ext/uri file 2023-03-03 00:38:32 +00:00
Rafael Mendonça França
34e296d492
Remove deprecated override of ActiveSupport::TimeWithZone.name 2023-03-03 00:38:31 +00:00
Rafael Mendonça França
e420c3380e
Remove deprecated option to passing a format to #to_s 2023-03-03 00:38:30 +00:00
Rafael Mendonça França
4eb6441dd8
Remove deprecated ActiveSupport::PerThreadRegistry 2023-03-03 00:38:28 +00:00
Rafael Mendonça França
3ec629784c
Remove deprecated override of Enumerable#sum 2023-03-03 00:38:26 +00:00
Alejandro Dustet
78b74e9227
Deprecate initialize memcache store with dalli client
Why:
----

Following up on [#47323](https://github.com/rails/rails/issues/47323).
Many options are not forwarded to the Dalli client when it is
initialized from the `ActiveSupport::Cache::MemCacheStore`. This is to
support a broader set of features powered by the implementation. When an
instance of a client is passed on the initializer, it takes precedence,
and we have no control over which attributes will be overridden or
re-processed on the client side; this is by design and should remain as
such to allow both projects to progress independently. Having this
option introduces several potential bugs that are difficult to pinpoint
and get multiplied by which version of the tool is used and how each
evolves. During the conversation on the issue, the `Dalli` client
maintainer supports [deprecating](https://github.com/rails/rails/issues/47323#issuecomment-1424292456)
this option.

How:
----

Removing this implicit dependency will ensure each library can evolve
separately and cements the usage of `Dalli::Client` as an [implementation
detail](https://github.com/rails/rails/issues/21595#issuecomment-139815433)

We can not remove a supported feature overnight, so I propose we add a
deprecation warning for the next minor release(7.2 at this time).

There was a constant on the `Cache` namespace only used to restrict
options passed to the `Dalli::Client` initializer that now lives on the
`MemCacheStore` class.

Co-authored-by: Eileen M. Uchitelle <eileencodes@users.noreply.github.com>
2023-02-28 14:31:23 -05:00
Hartley McGuire
e02710191e
Suppress output from mail parser warnings
Previously, requiring any of the ragel generated parsers in mail would
output tons of warnings in tests, making output much harder to read
(especially in Railties).

This commit extends the RaiseWarnings patch to suppress output from
these mail parsers.

The suppression can be removed once mikel/mail#1557 or mikel/mail#1551
or any other PR fixes the issue
2023-02-23 17:07:34 -05:00
zzak
d2af670dba
Remove Copyright years (#47467)
* Remove Copyright years

* Basecamp is now 37signals... again

Co-authored-by: David Heinemeier Hansson <dhh@hey.com>

---------

Co-authored-by: David Heinemeier Hansson <dhh@hey.com>
2023-02-23 11:38:16 +01:00
Jean Boussier
a44559679a Make ForkTracker.check! a noop on Ruby 3.1+
Fix: https://github.com/rails/rails/issues/47418

We should trust the `Process._fork` callback, as it can't really
be bypassed except throught `Process.daemonize` but I don't think
it's a problem.

We initially kept the PID check even if the callback was present,
but it turns out that on modern GLIBC the getpid() syscall isn't
cached and it causes a significant overhead.
2023-02-16 17:32:38 +01:00
Guillermo Iguaran
ad714896f6
Merge pull request #47376 from seanpdoyle/with-options-hash-like
Add test coverage for `Object#with_options` with `Hash`-like argument
2023-02-15 13:09:03 -08:00
Étienne Barrié
8db8409825 Fix collect_deprecations documentation and add a test 2023-02-15 15:20:51 +01:00
zzak
3838738163
Update TimeHelpers#travel_to docs after stubbing Time.new
Follow up to #47315
2023-02-15 17:24:28 +09:00
fatkodima
0c4e9d35bb Stub Time.new() in TimeHelpers#travel_to 2023-02-14 11:28:52 +02:00
Sean Doyle
286fa403d6 Add test coverage for Object#with_options with Hash-like
Add test coverage for existing `Object#with_options` support for
`Hash`-like objects.

The current implementation expects "Hash-like" to mean that the argument
implements both `Hash#deep_merge` and `#to_hash` (to be called
explicitly with `#to_hash` and implicitly with `**`).
2023-02-13 22:08:23 -05:00
zzak
a8690805e8
Merge pull request #47225 from runephilosof-karnovgroup/patch-1
Document `namespace` option for read method
2023-02-13 17:32:01 +09:00
Yasuo Honda
f838a74212
Merge pull request #46866 from ghousemohamed/change-year-2022-to-2023 2023-02-13 13:15:43 +09:00
Jonathan Hefner
d3917f5fdd Use throw for message error handling control flow
There are multiple points of failure when processing a message with
`MessageEncryptor` or `MessageVerifier`, and there several ways we might
want to handle those failures.  For example, swallowing a failure with
`MessageVerifier#verified`, or raising a specific exception with
`MessageVerifier#verify`, or conditionally ignoring a failure when
rotations are configured.

Prior to this commit, the _internal_ logic of handling failures was
implemented using a mix of `nil` return values and raised exceptions.
This commit reimplements the internal logic using `throw` and a few
precisely targeted `rescue`s.  This accomplishes several things:

* Allow rotation of serializers for `MessageVerifier`.  Previously,
  errors from a `MessageVerifier`'s initial serializer were never
  rescued.  Thus, the serializer could not be rotated:

    ```ruby
    old_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: Marshal)
    new_verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)
    new_verifier.rotate(serializer: Marshal)

    message = old_verifier.generate("message")

    new_verifier.verify(message)
    # BEFORE:
    # => raises JSON::ParserError
    # AFTER:
    # => "message"
    ```

* Allow rotation of serializers for `MessageEncryptor` when using a
  non-standard initial serializer.  Similar to `MessageVerifier`, the
  serializer could not be rotated when the initial serializer raised an
  error other than `TypeError` or `JSON::ParserError`, such as
  `Psych::SyntaxError` or a custom error.

* Raise `MessageEncryptor::InvalidMessage` from `decrypt_and_verify`
  regardless of cipher.  Previously, when a `MessageEncryptor` was using
  a non-AEAD cipher such as AES-256-CBC, a corrupt or tampered message
  would raise `MessageVerifier::InvalidSignature` due to reliance on
  `MessageVerifier` for verification.  Now, the verification mechanism
  is transparent to the user:

    ```ruby
    encryptor = ActiveSupport::MessageEncryptor.new("x" * 32, cipher: "aes-256-gcm")
    message = encryptor.encrypt_and_sign("message")
    encryptor.decrypt_and_verify(message.next)
    # => raises ActiveSupport::MessageEncryptor::InvalidMessage

    encryptor = ActiveSupport::MessageEncryptor.new("x" * 32, cipher: "aes-256-cbc")
    message = encryptor.encrypt_and_sign("message")
    encryptor.decrypt_and_verify(message.next)
    # BEFORE:
    # => raises ActiveSupport::MessageVerifier::InvalidSignature
    # AFTER:
    # => raises ActiveSupport::MessageEncryptor::InvalidMessage
    ```

* Support `nil` original value when using `MessageVerifier#verify`.
  Previously, `MessageVerifier#verify` did not work with `nil` original
  values, though both `MessageVerifier#verified` and
  `MessageEncryptor#decrypt_and_verify` do:

    ```ruby
    encryptor = ActiveSupport::MessageEncryptor.new("x" * 32)
    message = encryptor.encrypt_and_sign(nil)

    encryptor.decrypt_and_verify(message)
    # => nil

    verifier = ActiveSupport::MessageVerifier.new("secret")
    message = verifier.generate(nil)

    verifier.verified(message)
    # => nil

    verifier.verify(message)
    # BEFORE:
    # => raises ActiveSupport::MessageVerifier::InvalidSignature
    # AFTER:
    # => nil
    ```

* Improve performance of verifying a message when it has expired and one
  or more rotations have been configured:

    ```ruby
    # frozen_string_literal: true
    require "benchmark/ips"
    require "active_support/all"

    verifier = ActiveSupport::MessageVerifier.new("new secret")
    verifier.rotate("old secret")

    message = verifier.generate({ "data" => "x" * 100 }, expires_at: 1.day.ago)

    Benchmark.ips do |x|
      x.report("expired message") do
        verifier.verified(message)
      end
    end
    ```

  __Before__

    ```
    Warming up --------------------------------------
         expired message     1.442k i/100ms
    Calculating -------------------------------------
         expired message     14.403k (± 1.7%) i/s -     72.100k in   5.007382s
    ```

  __After__

    ```
    Warming up --------------------------------------
         expired message     1.995k i/100ms
    Calculating -------------------------------------
         expired message     19.992k (± 2.0%) i/s -    101.745k in   5.091421s
    ```

Fixes #47185.
2023-02-12 15:16:25 -06:00
Michael Go
2e52d0a39d [Fix #47343] maintain html_safe? on sliced HTML safe strings 2023-02-09 16:51:22 -04:00
Jonathan Hefner
91b2a15e2b Refactor MessageEncryptor and MessageVerifier
This factors common methods into a `ActiveSupport::Messages::Codec` base
class.  This also disentangles serialization (and deserialization) from
encryption (and decryption) in `MessageEncryptor`.
2023-02-08 17:22:22 -06:00
Jonathan Hefner
81ded4d48a
Merge pull request #46848 from jonathanhefner/messages-use_message_serializer_for_metadata
Avoid double serialization of message data
2023-02-08 17:03:57 -06:00
Jonathan Hefner
91bb5da5fc Avoid double serialization of message data
Prior to this commit, messages with metadata were always serialized in
the following way:

  ```ruby
  Base64.strict_encode64(
    ActiveSupport::JSON.encode({
      "_rails" => {
        "message" => Base64.strict_encode64(
          serializer.dump(data)
        ),
        "pur" => "the purpose",
        "exp" => "the expiration"
      },
    })
  )
  ```

in which the message data is serialized and URL-encoded twice.

This commit changes message serialization such that, when possible, the
data is serialized and URL-encoded only once:

  ```ruby
  Base64.strict_encode64(
    serializer.dump({
      "_rails" => {
        "data" => data,
        "pur" => "the purpose",
        "exp" => "the expiration"
      },
    })
  )
  ```

This improves performance in proportion to the size of the data:

**Benchmark**

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support/all"

  verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)

  payloads = [
    { "content" => "x" * 100 },
    { "content" => "x" * 2000 },
    { "content" => "x" * 1_000_000 },
  ]

  if ActiveSupport::Messages::Metadata.respond_to?(:use_message_serializer_for_metadata)
    ActiveSupport::Messages::Metadata.use_message_serializer_for_metadata = true
  end

  Benchmark.ips do |x|
    payloads.each do |payload|
      x.report("generate ~#{payload["content"].size}B") do
        $generated_message = verifier.generate(payload, purpose: "x")
      end

      x.report("verify ~#{payload["content"].size}B") do
        verifier.verify($generated_message, purpose: "x")
      end
    end
  end

  puts

  puts "Message size:"
  payloads.each do |payload|
    puts "  ~#{payload["content"].size} bytes of data => " \
      "#{verifier.generate(payload, purpose: "x").size} byte message"
  end
  ```

**Before**

  ```
  Warming up --------------------------------------
        generate ~100B     1.578k i/100ms
          verify ~100B     2.506k i/100ms
       generate ~2000B   447.000  i/100ms
         verify ~2000B     1.409k i/100ms
    generate ~1000000B     1.000  i/100ms
      verify ~1000000B     6.000  i/100ms
  Calculating -------------------------------------
        generate ~100B     15.807k (± 1.8%) i/s -     80.478k in   5.093161s
          verify ~100B     25.240k (± 2.1%) i/s -    127.806k in   5.066096s
       generate ~2000B      4.530k (± 2.4%) i/s -     22.797k in   5.035398s
         verify ~2000B     14.136k (± 2.3%) i/s -     71.859k in   5.086267s
    generate ~1000000B     11.673  (± 0.0%) i/s -     59.000  in   5.060598s
      verify ~1000000B     64.372  (± 6.2%) i/s -    324.000  in   5.053304s

  Message size:
    ~100 bytes of data => 306 byte message
    ~2000 bytes of data => 3690 byte message
    ~1000000 bytes of data => 1777906 byte message
  ```

**After**

  ```
  Warming up --------------------------------------
        generate ~100B     4.689k i/100ms
          verify ~100B     3.183k i/100ms
       generate ~2000B     2.722k i/100ms
         verify ~2000B     2.066k i/100ms
    generate ~1000000B    12.000  i/100ms
      verify ~1000000B    11.000  i/100ms
  Calculating -------------------------------------
        generate ~100B     46.984k (± 1.2%) i/s -    239.139k in   5.090540s
          verify ~100B     32.043k (± 1.2%) i/s -    162.333k in   5.066903s
       generate ~2000B     27.163k (± 1.2%) i/s -    136.100k in   5.011254s
         verify ~2000B     20.726k (± 1.7%) i/s -    105.366k in   5.085442s
    generate ~1000000B    125.600  (± 1.6%) i/s -    636.000  in   5.064607s
      verify ~1000000B    122.039  (± 4.1%) i/s -    616.000  in   5.058386s

  Message size:
    ~100 bytes of data => 234 byte message
    ~2000 bytes of data => 2770 byte message
    ~1000000 bytes of data => 1333434 byte message
  ```

This optimization is only applied for recognized serializers that are
capable of serializing a `Hash`.

Additionally, because the optimization changes the message format, a
`config.active_support.use_message_serializer_for_metadata` option has
been added to disable it.  The optimization is disabled by default, but
enabled with `config.load_defaults 7.1`.

Regardless of whether the optimization is enabled, messages using either
format can still be read.

In the case of a rolling deploy of a Rails upgrade, wherein servers that
have not yet been upgraded must be able to read messages from upgraded
servers, the optimization can be disabled on first deploy, then safely
enabled on a subsequent deploy.
2023-02-08 16:25:31 -06:00
Jonathan Hefner
9e5c7cfd9a Detach Messages::Rotator from SecureCompareRotator
Prior to this commit, `ActiveSupport::SecureCompareRotator` used
`ActiveSupport::Messages::Rotator` for part of its rotation logic, even
though `SecureCompareRotator` is entirely unrelated to messages.  This
made it precarious to alter `Messages::Rotator`, especially because
`Messages::Rotator` was `prepend`ed to `SecureCompareRotator` rather
than `include`d.

This commit reimplements `SecureCompareRotator` without
`Messages::Rotator`, which simplifies the logic and, as a bonus,
improves performance:

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support/all"

  comparer = ActiveSupport::SecureCompareRotator.new("new secret")
  comparer.rotate("old secret")

  Benchmark.ips do |x|
    x.report("compare old") do
      comparer.secure_compare!("old secret")
    end
  end
  ```

__Before__

  ```
  Warming up --------------------------------------
           compare old    72.073k i/100ms
  Calculating -------------------------------------
           compare old    719.844k (± 1.0%) i/s -      3.604M in   5.006682s
  ```

__After__

  ```
  Warming up --------------------------------------
           compare old   147.486k i/100ms
  Calculating -------------------------------------
           compare old      1.473M (± 0.9%) i/s -      7.374M in   5.006655s
  ```
2023-02-08 15:20:01 -06:00
Jonathan Hefner
10fcbeec60 Document MessageVerifier method options [ci-skip]
This documents the options on the methods themselves, so that the reader
does not have to scan the class summary documentation for a specific
option.
2023-02-08 15:14:17 -06:00
Jonathan Hefner
c7792a0eca Document MessageEncryptor method options [ci-skip]
This documents the options on the methods themselves, so that the reader
does not have to scan the class summary documentation for a specific
option.
2023-02-08 15:14:12 -06:00
Ignacio Galindo
47b0aa05c3 Add Object#in? support for open date ranges
This leverages Range#cover? to properly handle beginless and endless
range inclusion checks.
2023-02-08 18:32:27 +01:00
Guillermo Iguaran
a7d4d78479 Use Data#to_h and Struct#to_h for JSON encoding
`Hash[Array#zip]` was used before Struct supported #to_h.

Data#to_h and Struct#to_h are also faster according to benchmarks:
https://github.com/rails/rails/pull/47273/files#r1097994236
2023-02-07 12:57:56 -08:00
Haroon Ahmed
52bc74b9d2 Fixes https://github.com/rails/rails/issues/47267
Add as_json method for the new Data object introduced in Ruby 3.2 so
Data objects can be encoded/decoded.

data = Data.define(:name).new(:test)
data.as_json
2023-02-06 15:13:47 +00:00
runephilosof-karnovgroup
df73d5cd37
Document namespace option for read method 2023-02-02 09:33:07 +01:00
Matthew Draper
c285f36366 Never report negative idle time
We calculate idle time by subtracting two separately-obtained values,
and apparently it's possible for the CPU-time clock to tick a
millisecond ahead of the monotonic clock.

I think it's safe to ignore that potential difference in general, but
even at the extreme of CPU activity, I'm reasonably confident time
doesn't move backwards.
2023-01-29 04:56:50 +10:30
Rafael Mendonça França
4af571c22d
Merge pull request #47105 from ghiculescu/i18n-raise
Make `raise_on_missing_translations` raise on any missing translation
2023-01-23 14:10:50 -05:00
Alex Ghiculescu
e18c23fc2b Make raise_on_missing_translations raise on any missing translation
Fixes https://github.com/rails/rails/issues/47057
Superseeds https://github.com/rails/rails/pull/45361
2023-01-23 10:17:11 -07:00
Jonathan Hefner
6f31335d36 Hard code serializer for AS::EncryptedFile
Follow-up to #42846.

Prior to this commit, `ActiveSupport::EncryptedFile` used the
`ActiveSupport::MessageEncryptor` default serializer.  #42846 changed
the default serializer to `JSON` when using `config.load_defaults 7.1`.
Thus, encrypted files that were written with the previous default
serializer (`Marshal`) could not be read by `Rails.application.encrypted`.
That included files which were written with `bin/rails encrypted:edit`
even when using the new default, because the `bin/rails encrypted:edit`
command does not run the initializer that applies the configuration to
`MessageEncryptor`:

  ```console
  $ bin/rails r 'puts ActiveSupport::MessageEncryptor.default_message_encryptor_serializer'
  json

  $ bin/rails encrypted:edit config/my_encrypted.yml.enc
  ...

  $ bin/rails encrypted:show config/my_encrypted.yml.enc
  foo: bar

  $ bin/rails r 'Rails.application.encrypted("config/my_encrypted.yml.enc").read'
  rails/activesupport/lib/active_support/message_encryptor.rb:241:in `rescue in _decrypt': ActiveSupport::MessageEncryptor::InvalidMessage (ActiveSupport::MessageEncryptor::InvalidMessage)
  ...
  ruby-3.2.0/lib/ruby/3.2.0/json/common.rb:216:in `parse': unexpected token at "foo: bar (JSON::ParserError)
  ```

This commit hard codes the serializer for `EncryptedFile` to `Marshal`.
2023-01-22 16:26:47 -06:00
Jean Boussier
66227e01e7 Improve Rails' Shape friendliness (third pass)
Followup: https://github.com/rails/rails/pull/47023

```
Shape Edges Report
-----------------------------------
snip...
       130  @_config
snip...
        99  @_url_options
```
2023-01-18 15:44:08 +01:00
Rafael Mendonça França
e9bc620f78
Make sure Enumerable#sum works with objects that implement #coerce without deprecation
Closes #46779
2023-01-17 20:51:19 +00:00
John Hawthorn
af346db8e1 Avoid regex backtracking in Inflector.underscore
[CVE-2023-22796]
2023-01-17 11:38:11 -08:00
Jean Boussier
aa7d78d9b1 Improve Rails' Shape friendliness (second pass)
Followup: https://github.com/rails/rails/pull/47023

```
Shape Edges Report
-----------------------------------
snip...
       238  @errors
snip...
       219  @options
snip...
       129  @_request
       128  @type
       125  @virtual_path
       124  @_assigns
       123  @_config
       123  @_controller
       123  @output_buffer
       123  @view_flow
       122  @_default_form_builder
snip...
        89  @_already_called
        75  @validation_context
snip...
        65  @_new_record_before_last_commit
snip...
        58  @_url_options
snip...
```
2023-01-17 13:55:49 +01:00
Rafael Mendonça França
711abd41c0
Extract values to constant 2023-01-16 20:41:15 +00:00
Jonathan Hefner
f64eadc256
Merge pull request #46955 from jonathanhefner/tagged_logging-formatter-perf
Improve `TaggedLogging::Formatter` performance
2023-01-16 11:40:17 -06:00
Jean Boussier
fc950324bd Improve Rails' Shape friendliness
Ruby 3.2 significantly changed how instance variables are store.
It now use shapes, and in short, it's important for performance
to define instance variables in a consistent order to limit the
amount of shapes.

Otherwise, the number of shapes will increase past a point where
MRI won't be able to cache instance variable access. The impact
is even more important when YJIT is enabled.

This PR is data driven. I dump the list of Shapes from Shopify's
monolith production environment, and Rails is very present among
the top offenders:

```
Shape Edges Report
-----------------------------------
       770  @default_graphql_name
       697  @own_fields
       661  @to_non_null_type
       555  @own_interface_type_memberships
       472  @description
       389  @errors
       348  @oseid
       316  @_view_runtime
       310  @_db_runtime
       292  @visibility
       286  @shop
       271  @attribute_method_patterns_cache
       264  @namespace_for_serializer
       254  @locking_column
       254  @primary_key
       253  @validation_context
       244  @quoted_primary_key
       238  @access_controls
       234  @_trigger_destroy_callback
       226  @_trigger_update_callback
       224  @finder_needs_type_condition
       215  @_committed_already_called
       214  @api_type
       203  @mutations_before_last_save
       202  @access_controls_overrides
       201  @options
       198  @mutations_from_database
       190  @_already_called
       183  @name
       179  @_request
       176  @own_arguments
       175  @_assigns
       175  @virtual_path
       174  @context
       173  @_controller
       173  @output_buffer
       173  @view_flow
       172  @_default_form_builder
       169  @cache
       159  @_touch_record
       151  @attribute_names
       151  @default_attributes
       150  @columns_hash
       149  @attribute_types
       148  @columns
       147  @marked_for_same_origin_verification
       146  @schema_loaded
       143  @_config
       143  @type
       141  @column_names
```

All the changes are of similar nature, the goal is to preset the instance
variable to nil when objects are allocated, or when classes are created.

For classes I leverage the `inherited` hook. If the patern becomes common enough
it might make sense to add a helper for this in `ActiveSupport::Concern`.
2023-01-16 12:31:37 +01:00
Jean Boussier
f1b1ad6d7e
Merge pull request #47017 from ghiculescu/current-attributes-restricted-names
`ActiveSupport::CurrentAttributes`: raise if a restricted attribute name is used.
2023-01-16 11:10:35 +01:00
zzak
edd3e694af Unlink Callbacks module name from self 2023-01-16 14:20:14 +09:00
Alex Ghiculescu
1284df5286 ActiveSupport::CurrentAttributes: raise if a restricted attribute name is used.
Attributes such as `set` and `reset` should not be used as they clash with the  `CurrentAttributes` public API. This PR raises an `ArgumentError` if a restricted attribute name is used.
2023-01-15 17:07:11 -07:00
Jonathan Hefner
26e115e58e Improve TaggedLogging::Formatter performance
This commit improves the performance of `TaggedLogging::Formatter` by
factoring out a `TaggedLogging::TagStack` helper class that memoizes
the tag string which is prepended to log messages.  `TagStack` is
implemented as a separate class so that the array of tags and the
memoized tag string can be stored in a single thread-local variable,
minimizing the number of thread-local variable reads / writes.

Additionally, the construction of the tag string has been optimized to
allocate O(1) objects instead of O(N), where N is the number of tags.
So even when the tag string is not yet memoized -- e.g. for a singular
logging call inside a `tagged` block -- performance is improved.

__Benchmark__

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "benchmark/memory"

  io = StringIO.new
  logger = ActiveSupport::TaggedLogging.new(Logger.new(io))
  message = "for your information, this is a message"

  benchmark = -> x do
    x.report("messages: 1, tags: 0") do
      logger.info(message)
      io.rewind
    end

    (1..3).each do |tag_count|
      tags = ["tag"] * tag_count

      x.report("messages: 1, tags: #{tag_count}") do
        logger.tagged(*tags) do
          logger.info(message)
        end
        io.rewind
      end
    end

    (1..3).each do |tag_count|
      tags = ["tag"] * tag_count

      x.report("messages: 3, tags: #{tag_count}") do
        logger.tagged(*tags) do
          logger.info(message)
          logger.info(message)
          logger.info(message)
        end
        io.rewind
      end
    end
  end

  Benchmark.ips(&benchmark)
  Benchmark.memory(&benchmark)
  ```

__Before__

  ```
  Warming up --------------------------------------
  messages: 1, tags: 0    27.525k i/100ms
  messages: 1, tags: 1     8.971k i/100ms
  messages: 1, tags: 2     8.538k i/100ms
  messages: 1, tags: 3     7.911k i/100ms
  messages: 3, tags: 1     4.540k i/100ms
  messages: 3, tags: 2     4.278k i/100ms
  messages: 3, tags: 3     3.948k i/100ms
  Calculating -------------------------------------
  messages: 1, tags: 0    274.500k (± 1.5%) i/s -      1.376M in   5.014770s
  messages: 1, tags: 1     90.649k (± 1.5%) i/s -    457.521k in   5.048251s
  messages: 1, tags: 2     84.970k (± 1.7%) i/s -    426.900k in   5.025548s
  messages: 1, tags: 3     78.819k (± 2.0%) i/s -    395.550k in   5.020476s
  messages: 3, tags: 1     46.517k (± 1.4%) i/s -    236.080k in   5.076084s
  messages: 3, tags: 2     43.062k (± 1.5%) i/s -    218.178k in   5.067764s
  messages: 3, tags: 3     39.822k (± 2.2%) i/s -    201.348k in   5.058528s
  Calculating -------------------------------------
  messages: 1, tags: 0   166.000  memsize (     0.000  retained)
                           2.000  objects (     0.000  retained)
                           1.000  strings (     0.000  retained)
  messages: 1, tags: 1   562.000  memsize (     0.000  retained)
                           8.000  objects (     0.000  retained)
                           2.000  strings (     0.000  retained)
  messages: 1, tags: 2   550.000  memsize (     0.000  retained)
                          10.000  objects (     0.000  retained)
                           3.000  strings (     0.000  retained)
  messages: 1, tags: 3   758.000  memsize (     0.000  retained)
                          11.000  objects (     0.000  retained)
                           3.000  strings (     0.000  retained)
  messages: 3, tags: 1     1.366k memsize (     0.000  retained)
                          16.000  objects (     0.000  retained)
                           2.000  strings (     0.000  retained)
  messages: 3, tags: 2     1.330k memsize (     0.000  retained)
                          22.000  objects (     0.000  retained)
                           3.000  strings (     0.000  retained)
  messages: 3, tags: 3     1.954k memsize (     0.000  retained)
                          25.000  objects (     0.000  retained)
                           3.000  strings (     0.000  retained)
  ```

__After__

  ```
  Warming up --------------------------------------
  messages: 1, tags: 0    29.424k i/100ms
  messages: 1, tags: 1     9.156k i/100ms
  messages: 1, tags: 2     9.073k i/100ms
  messages: 1, tags: 3     8.263k i/100ms
  messages: 3, tags: 1     4.837k i/100ms
  messages: 3, tags: 2     5.331k i/100ms
  messages: 3, tags: 3     4.834k i/100ms
  Calculating -------------------------------------
  messages: 1, tags: 0    295.005k (± 1.1%) i/s -      1.501M in   5.087339s
  messages: 1, tags: 1     91.360k (± 1.4%) i/s -    457.800k in   5.011926s
  messages: 1, tags: 2     91.213k (± 1.1%) i/s -    462.723k in   5.073683s
  messages: 1, tags: 3     82.784k (± 0.9%) i/s -    421.413k in   5.090932s
  messages: 3, tags: 1     47.606k (± 1.7%) i/s -    241.850k in   5.081694s
  messages: 3, tags: 2     53.170k (± 0.8%) i/s -    266.550k in   5.013474s
  messages: 3, tags: 3     48.340k (± 1.4%) i/s -    246.534k in   5.100972s
  Calculating -------------------------------------
  messages: 1, tags: 0   166.000  memsize (     0.000  retained)
                           2.000  objects (     0.000  retained)
                           1.000  strings (     0.000  retained)
  messages: 1, tags: 1   522.000  memsize (     0.000  retained)
                           7.000  objects (     0.000  retained)
                           2.000  strings (     0.000  retained)
  messages: 1, tags: 2   446.000  memsize (     0.000  retained)
                           8.000  objects (     0.000  retained)
                           4.000  strings (     0.000  retained)
  messages: 1, tags: 3   678.000  memsize (     0.000  retained)
                           8.000  objects (     0.000  retained)
                           4.000  strings (     0.000  retained)
  messages: 3, tags: 1     1.326k memsize (     0.000  retained)
                          15.000  objects (     0.000  retained)
                           2.000  strings (     0.000  retained)
  messages: 3, tags: 2   938.000  memsize (     0.000  retained)
                          14.000  objects (     0.000  retained)
                           4.000  strings (     0.000  retained)
  messages: 3, tags: 3     1.490k memsize (     0.000  retained)
                          14.000  objects (     0.000  retained)
                           4.000  strings (     0.000  retained)
  ```

Note that this commit preserves the contract of `Formatter#current_tags`
because it is used by `ActionCable::Connection::TaggedLoggerProxy#tag`
and `ActiveJob::Logging#logger_tagged_by_active_job?`.  Similarly,
`Formatter#tags_text` is kept because it is used by
`ActionDispatch::DebugExceptions#log_array`.
2023-01-14 15:58:05 -06:00
Jonathan Hefner
fb172431f8 Revert "Optimize for the default case where TaggedLogger takes only one tag name"
This reverts commit 3baffd31bea7a4e2bc6bb9892f867d5d7366c468.
2023-01-14 12:00:44 -06:00
Hartley McGuire
4b6e3e13b0
Improve error message for missing listen gem
A user reported seeing the following error when trying to use the
EventedFileUpdateChecker in their app:

```shell
$ rails server
=> Booting Puma
=> Rails 7.0.4 application starting in development
=> Run `bin/rails server --help` for more startup options
Exiting
/usr/local/lib/ruby/gems/3.0.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require': cannot load such file -- listen (LoadError)
	from /usr/local/lib/ruby/gems/3.0.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require'
	from /usr/local/lib/ruby/gems/3.0.0/gems/activesupport-7.0.4/lib/active_support/evented_file_update_checker.rb:6:in `<top (required)>'
	from /usr/local/lib/ruby/gems/3.0.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require'
	from /usr/local/lib/ruby/gems/3.0.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require'
	from /home/ross/Data/EmySystem/newentry/Entry2/config/environments/development.rb:84:in `block in <top (required)>'
	from /usr/local/lib/ruby/gems/3.0.0/gems/railties-7.0.4/lib/rails/railtie.rb:257:in `instance_eval'
	from /usr/local/lib/ruby/gems/3.0.0/gems/railties-7.0.4/lib/rails/railtie.rb:257:in `configure'
	from /home/ross/Data/EmySystem/newentry/Entry2/config/environments/development.rb:3:in `<top (required)>'
        ...
```

While we were able to direct them to the proper fix (add listen to
their app's Gemfile), the error message does not really point them in
that direction on its own.

This commit improves the error message by using Kernel#gem to indicate
that the gem should be in the user's bundle. This is the same approach
used for other not-specified dependencies in Rails, such as the various
database drivers in their Active Record adapters.

New error message:
```shell
$ bin/rails r "ActiveSupport::EventedFileUpdateChecker"
/home/hartley/.cache/asdf/installs/ruby/3.2.0/lib/ruby/site_ruby/3.2.0/bundler/rubygems_integration.rb:276:in `block (2 levels) in replace_gem': listen is not part of the bundle. Add it to your Gemfile. (Gem::LoadError)
        from /home/hartley/src/github.com/skipkayhil/rails/activesupport/lib/active_support/evented_file_update_checker.rb:3:in `<top (required)>'
        from /home/hartley/.cache/asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require'
        from /home/hartley/.cache/asdf/installs/ruby/3.2.0/lib/ruby/gems/3.2.0/gems/zeitwerk-2.6.6/lib/zeitwerk/kernel.rb:38:in `require'
	...
```
2023-01-13 13:19:50 -05:00
Hartley McGuire
978993e880
Enable Style/EvalWithLocation
Ref: ad39d6b

Ensure that all evals include file and line number to identify their
source.

Two of the evals reported by this cop were unneccesary and replaced with
non-eval alternatives: xml is set as a local variable in
Template::Handlers::Builder#call, and instance_eval isn't needed to get
the current binding.

There are additionally 27 offenses in test directories, but since it
seems less important to fix those they are currently ignored.
2023-01-11 18:46:09 -05:00
Akira Matsuda
a81be79d4b
Merge pull request #46846 from amatsuda/transform_keys_by_hash
Let HWIA#transform_keys take a Hash argument like Ruby's Hash#transform_keys
2023-01-12 03:33:47 +09:00
Jean Boussier
ad39d6b2f9 Fix anonymous evals
This makes it very annoying to locate the source of methods
and such when profiling etc.
2023-01-11 11:22:42 +01:00
Akira Matsuda
7a63a7a668
Let HWIA#transform_keys take a Hash argument like Hash#transform_keys
Active Support's original Hash#transform_keys used not to take any argument, and
that version has been transported to Ruby 2.5
https://github.com/ruby/ruby/commit/1405111722

Then since Ruby 3.0, that method in Ruby has been expanded to take a Hash
argument https://github.com/ruby/ruby/commit/b25e27277d

Hence, we'd better update our own in-house Hash-like class
HashWithIndifferentAccess to follow the Hash API change.

Co-authored-by: Hartley McGuire <skipkayhil@gmail.com>
Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2023-01-11 12:32:06 +09:00
Akira Matsuda
8b2f57dc6f
Let delegate define method with proper arity when delegating to a Class / Module
Method delegation with `...` argument is known to be slow because it allocates
redundant Array and Hash objects each time when being called.
see: https://bugs.ruby-lang.org/issues/19165

Current implementation of `delegate` defines all delegation methods in this
slow form regardless of the original method's arity, but in case the delegation
target is a Class or a Module, we can investigate the arity of the original
method in the definition timing, then we can define the delegation in proper
minimal arity.

This makes 3.5 times faster zero arity method delegation as follows:
Warming up --------------------------------------
                 old   811.534k i/100ms
                 new     1.807M i/100ms
Calculating -------------------------------------
                 old      9.809M (± 3.4%) i/s -     49.504M in   5.053355s
                 new     34.360M (± 0.8%) i/s -    173.465M in   5.048692s

Comparison:
                 new: 34360408.4 i/s
                 old:  9809157.4 i/s - 3.50x  (± 0.00) slower
2023-01-11 04:32:08 +09:00
Étienne Barrié
ded85e78b3 TaggedLogging accepts non-String objects
Before 0671acfeeaf33d1a5d5a08deb45917767e22ed61, objects were coerced by
the string interpolation, this brings it back.
2023-01-10 11:55:25 +01:00
Jonathan Hefner
fa11789d49
Merge pull request #46943 from jonathanhefner/hwia-default-match-hash-default-arity
Match arity of `Hash#default` in `HWIA#default`
2023-01-09 17:00:42 -06:00
Jonathan Hefner
a2d179aa0c
Merge pull request #46942 from jonathanhefner/hwia-fetch_values-use-map-bang
Use `map!` in `HashWithIndifferentAccess#fetch_values`
2023-01-09 17:00:27 -06:00
Jonathan Hefner
b3c01961bc
Merge pull request #46941 from jonathanhefner/hwia-values_at-use-map-bang
Use `map!` in `HashWithIndifferentAccess#values_at`
2023-01-09 17:00:13 -06:00
Jonathan Hefner
88269a93f3 Use map! in HashWithIndifferentAccess#fetch_values
This avoids an extra allocation from `map`.

__Benchmark__

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support/all"

  Hash.alias_method(:old_fetch_values, :fetch_values)
  Hash.alias_method(:new_fetch_values, :fetch_values)

  class ActiveSupport::HashWithIndifferentAccess
    def old_fetch_values(*indices, &block)
      super(*indices.map { |key| convert_key(key) }, &block)
    end

    def new_fetch_values(*indices, &block)
      indices.map! { |key| convert_key(key) }
      super
    end
  end

  hwia = { foo: 1, bar: 2, baz: 3, qux: 4 }.with_indifferent_access
  splat_keys = [:bar, :baz]

  Benchmark.ips do |x|
    x.report("old fetch_values 1") do
      hwia.old_fetch_values(:bar)
    end

    x.report("new fetch_values 1") do
      hwia.new_fetch_values(:bar)
    end

    x.compare!
  end

  Benchmark.ips do |x|
    x.report("old fetch_values splat") do
      hwia.old_fetch_values(*splat_keys)
    end

    x.report("new fetch_values splat") do
      hwia.new_fetch_values(*splat_keys)
    end

    x.compare!
  end
  ```

__Results__

  ```
  Warming up --------------------------------------
    old fetch_values 1   150.459k i/100ms
    new fetch_values 1   162.756k i/100ms
  Calculating -------------------------------------
    old fetch_values 1      1.503M (± 1.3%) i/s -      7.523M in   5.006517s
    new fetch_values 1      1.620M (± 1.0%) i/s -      8.138M in   5.022927s

  Comparison:
    new fetch_values 1:  1620310.5 i/s
    old fetch_values 1:  1502873.2 i/s - 1.08x  (± 0.00) slower

  Warming up --------------------------------------
  old fetch_values splat
                         109.967k i/100ms
  new fetch_values splat
                         117.143k i/100ms
  Calculating -------------------------------------
  old fetch_values splat
                            1.100M (± 1.5%) i/s -      5.498M in   5.001587s
  new fetch_values splat
                            1.168M (± 1.0%) i/s -      5.857M in   5.015409s

  Comparison:
  new fetch_values splat:  1167963.4 i/s
  old fetch_values splat:  1099558.2 i/s - 1.06x  (± 0.00) slower
  ```
2023-01-09 15:51:27 -06:00
Jonathan Hefner
d8a6f35805 Use map! in HashWithIndifferentAccess#values_at
This avoids an extra allocation from `map`.

__Benchmark__

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support/all"

  Hash.alias_method(:old_values_at, :values_at)
  Hash.alias_method(:new_values_at, :values_at)

  class ActiveSupport::HashWithIndifferentAccess
    def old_values_at(*keys)
      super(*keys.map { |key| convert_key(key) })
    end

    def new_values_at(*keys)
      keys.map! { |key| convert_key(key) }
      super
    end
  end

  hwia = { foo: 1, bar: 2, baz: 3, qux: 4 }.with_indifferent_access
  splat_keys = [:bar, :baz]

  Benchmark.ips do |x|
    x.report("old values_at 1") do
      hwia.old_values_at(:bar)
    end

    x.report("new values_at 1") do
      hwia.new_values_at(:bar)
    end

    x.compare!
  end

  Benchmark.ips do |x|
    x.report("old values_at splat") do
      hwia.old_values_at(*splat_keys)
    end

    x.report("new values_at splat") do
      hwia.new_values_at(*splat_keys)
    end

    x.compare!
  end
  ```

__Results__

  ```
  Warming up --------------------------------------
       old values_at 1   150.881k i/100ms
       new values_at 1   163.731k i/100ms
  Calculating -------------------------------------
       old values_at 1      1.509M (± 1.3%) i/s -      7.695M in   5.099286s
       new values_at 1      1.646M (± 1.1%) i/s -      8.350M in   5.072959s

  Comparison:
       new values_at 1:  1646260.9 i/s
       old values_at 1:  1509283.6 i/s - 1.09x  (± 0.00) slower

  Warming up --------------------------------------
   old values_at splat   110.815k i/100ms
   new values_at splat   118.871k i/100ms
  Calculating -------------------------------------
   old values_at splat      1.118M (± 1.3%) i/s -      5.652M in   5.057480s
   new values_at splat      1.194M (± 0.9%) i/s -      6.062M in   5.077104s

  Comparison:
   new values_at splat:  1194171.4 i/s
   old values_at splat:  1117670.4 i/s - 1.07x  (± 0.00) slower
  ```
2023-01-09 15:50:35 -06:00
Jonathan Hefner
c618ed8e07 Match arity of Hash#default in HWIA#default
Follow-up to b9beb3eee1ce1e49d69fff3437fde4feb44a0b77.

Ruby's `Hash#default` expects 0..1 args:

  ```ruby
  {}.default(:foo, :bar)
  # => wrong number of arguments (given 2, expected 0..1) (ArgumentError)
  ```

Using a sentinel value instead of `*args` improves performance:

__Benchmark__

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support/all"

  Hash.alias_method(:old_default, :default)
  Hash.alias_method(:new_default, :default)

  class ActiveSupport::HashWithIndifferentAccess
    def old_default(*args)
      if args.length == 1
        super(convert_key(args[0]))
      else
        super(*args.map { |arg| convert_key(arg) })
      end
    end

    def new_default(key = (no_key = true))
      if no_key
        super()
      else
        super(convert_key(key))
      end
    end
  end

  hwia = {}.with_indifferent_access
  hwia.default_proc = -> (h, key) { key }

  Benchmark.ips do |x|
    x.report("old default nullary") do
      hwia.old_default
    end

    x.report("new default nullary") do
      hwia.new_default
    end

    x.compare!
  end

  Benchmark.ips do |x|
    x.report("old default unary") do
      hwia.old_default(:key)
    end

    x.report("new default unary") do
      hwia.new_default(:key)
    end

    x.compare!
  end
  ```

__Results__

  ```
  Warming up --------------------------------------
   old default nullary   266.594k i/100ms
   new default nullary   606.502k i/100ms
  Calculating -------------------------------------
   old default nullary      2.708M (± 1.3%) i/s -     13.596M in   5.022049s
   new default nullary      6.052M (± 0.5%) i/s -     30.325M in   5.011218s

  Comparison:
   new default nullary:  6051595.4 i/s
   old default nullary:  2707813.7 i/s - 2.23x  (± 0.00) slower

  Warming up --------------------------------------
     old default unary   220.776k i/100ms
     new default unary   305.148k i/100ms
  Calculating -------------------------------------
     old default unary      2.195M (± 1.3%) i/s -     11.039M in   5.030518s
     new default unary      3.081M (± 0.8%) i/s -     15.563M in   5.051871s

  Comparison:
     new default unary:  3080770.1 i/s
     old default unary:  2194765.4 i/s - 1.40x  (± 0.00) slower
  ```
2023-01-09 15:33:45 -06:00
Jonathan Hefner
fad1c4c759 Use #except! in HashWithIndifferentAccess#except
This avoids multiple unnecessary allocations.

__Benchmark__

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support/all"

  class ActiveSupport::HashWithIndifferentAccess
    def old_except(*keys)
      slice(*self.keys - keys.map { |key| convert_key(key) })
    end

    def new_except(*keys)
      dup.except!(*keys)
    end
  end

  hwia = { foo: 1, bar: 2, baz: 3, qux: 4 }.with_indifferent_access
  splat_keys = [:bar, :baz]

  Benchmark.ips do |x|
    x.report("old except 1") do
      hwia.old_except(:bar)
    end

    x.report("new except 1") do
      hwia.new_except(:bar)
    end

    x.compare!
  end

  Benchmark.ips do |x|
    x.report("old except splat") do
      hwia.old_except(*splat_keys)
    end

    x.report("new except splat") do
      hwia.new_except(*splat_keys)
    end

    x.compare!
  end
  ```

__Results__

  ```
  Warming up --------------------------------------
          old except 1    18.079k i/100ms
          new except 1    28.205k i/100ms
  Calculating -------------------------------------
          old except 1    180.832k (± 1.7%) i/s -    903.950k in   5.000295s
          new except 1    282.729k (± 1.2%) i/s -      1.438M in   5.088540s

  Comparison:
          new except 1:   282729.2 i/s
          old except 1:   180831.5 i/s - 1.56x  (± 0.00) slower

  Warming up --------------------------------------
      old except splat    19.309k i/100ms
      new except splat    25.932k i/100ms
  Calculating -------------------------------------
      old except splat    194.091k (± 1.6%) i/s -    984.759k in   5.075044s
      new except splat    255.873k (± 1.3%) i/s -      1.297M in   5.068184s

  Comparison:
      new except splat:   255873.0 i/s
      old except splat:   194091.3 i/s - 1.32x  (± 0.00) slower
  ```
2023-01-09 14:28:15 -06:00
Jonathan Hefner
7208d524c8 Document EncryptedConfiguration#config [ci-skip] 2023-01-08 15:47:31 -06:00
Jonathan Hefner
f46ce95c77 Document EncryptedConfiguration access methods [ci-skip] 2023-01-08 15:47:31 -06:00
Jonathan Hefner
17e29786b5 Document EncryptedConfiguration#read [ci-skip] 2023-01-08 15:47:31 -06:00
Jonathan Hefner
13f0ff9dfe Document increment order for {Date,etc}#advance [ci-skip] 2023-01-08 15:47:31 -06:00
Jonathan Hefner
ce23110829 Coordinate FilterParameters and ParameterFilter docs [ci-skip]
This clarifies the `ActiveSupport::ParameterFilter` documentation, and
tweaks the example code to be more friendly to the syntax highlighter
(similar to the tweaks made for `ActionDispatch::Http::FilterParameters`
in 782bed5d450363b302e0e6aa28b7ea0aef306d9f).

This also trims the `ActionDispatch::Http::FilterParameters`
documentation, and links it to `ActiveSupport::ParameterFilter`, since
`ActiveSupport::ParameterFilter` is responsible for filter behavior.
2023-01-08 15:47:20 -06:00
Jonathan Hefner
a4528b58e9 Reference AS::Deprecation instance methods [ci-skip]
This is in line with transitioning away from the global
`ActiveSupport::Deprecation` instance, towards individual
`ActiveSupport::Deprecation` instances.
2023-01-08 15:47:20 -06:00
Jonathan Hefner
cfe300ceb1 Format inline code [ci-skip] 2023-01-08 15:47:20 -06:00
Jonathan Hefner
3eadf057db Fix typos in API docs [ci-skip] 2023-01-08 15:47:20 -06:00
Jonathan Hefner
33557c5dca Indent private methods in code examples [ci-skip]
This matches the indentation used in generated code, such as code from
`railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt`.
2023-01-08 15:47:20 -06:00
Jean Boussier
05acc18d73
Merge pull request #46863 from ghiculescu/assert-diffrence-message
Include amount changed by in `assert_difference` failure message
2023-01-07 19:14:15 +01:00
zzak
13fecbd5a9 Use +symbol+ when there is no spaces or other breaking chars for RDoc 2023-01-07 09:23:11 +09:00
Alex Ghiculescu
293349c959 Include amount changed by in assert_difference failure message
Unless you're very good at math, this test fail message is not the easiest to debug:

```
"User.count" didn't change by 32.
Expected: 1611
  Actual: 1579
```

It's not obvious from the error, but in this case, it actually changed by 0. This is a pretty strong clue as to what went wrong, but the message doesn't tell us that.

This PR improves the message to make debugging easier:

```
"User.count" didn't change by 32 (changed by 0).
Expected: 1611
  Actual: 1579
```
2023-01-06 12:25:34 -07:00
Akira Matsuda
3d00c8b97f
Optimize camelize for single word
"hello"
Warming up --------------------------------------
                 old   101.269k i/100ms
                 new   193.263k i/100ms
Calculating -------------------------------------
                 old      1.073M (± 1.0%) i/s -      5.367M in   5.000337s
                 new      1.970M (± 1.0%) i/s -      9.856M in   5.002860s

Comparison:
                 new:  1970358.7 i/s
                 old:  1073491.6 i/s - 1.84x  (± 0.00) slower

"active_record"
Warming up --------------------------------------
                 old    45.864k i/100ms
                 new    44.306k i/100ms
Calculating -------------------------------------
                 old    468.932k (± 1.6%) i/s -      2.385M in   5.087160s
                 new    453.549k (± 1.4%) i/s -      2.304M in   5.080705s

Comparison:
                 old:   468931.7 i/s
                 new:   453549.3 i/s - 1.03x  (± 0.00) slower

"foo_bar_baz/aa_bb_cc"
Warming up --------------------------------------
                 old    18.536k i/100ms
                 new    18.334k i/100ms
Calculating -------------------------------------
                 old    186.371k (± 1.1%) i/s -    945.336k in   5.072959s
                 new    183.743k (± 0.9%) i/s -    935.034k in   5.089229s

Comparison:
                 old:   186371.2 i/s
                 new:   183742.5 i/s - same-ish: difference falls within error
2023-01-07 03:20:56 +09:00
Akira Matsuda
3ade331e75
This has to be mutable now... 2023-01-07 02:17:20 +09:00
Akira Matsuda
ca0d6521b1
tags_text() can actually be nil... 2023-01-07 01:59:26 +09:00
Akira Matsuda
0671acfeea
Reuse the String object created via tags_text()
tags_text() always creates a new mutable String
2023-01-07 01:56:05 +09:00
Akira Matsuda
c9875d31cc
Make it clear that SB#[]= takes 3 arguments, and reduce Array allocation
this reduces a redundant Array allocation that used to be created by *.
Plus, added some tests for []= with three arguments.
2023-01-06 03:02:02 +09:00
Akira Matsuda
5653d7d56d
String#* takes one argument, and in that case, better not splat the arg
because splatting the argument allocates an extra Array object.

benchmark
```ruby
s = 'a'.html_safe
Benchmark.ips do |x|
  x.report('') { s * 1 }
end
```

result
```
before
Warming up --------------------------------------
                       216.816k i/100ms
Calculating -------------------------------------
                          2.341M (± 2.0%) i/s -     11.708M in   5.002555s

after
Warming up --------------------------------------
                       315.118k i/100ms
Calculating -------------------------------------
                          3.704M (± 1.5%) i/s -     18.592M in   5.020261s
```
2023-01-05 10:15:19 +09:00
Akira Matsuda
aa73d1ad52
Method redefinition is no longer in use here
it's gone to another file via 9f5dee3a916a762a30efc472b52c613eb8979c5d,
then removed via d6c42b3fba456d1882682a5348610309164b1edd
2023-01-05 06:34:27 +09:00
Akira Matsuda
076003d8e6
require erb at the place where ERB is in use
follows up 9f5dee3a916a762a30efc472b52c613eb8979c5d
2023-01-05 06:30:31 +09:00
Eileen M. Uchitelle
2370ad1121
Merge pull request #46887 from zarqman/fix-deprec-msg
LogSubscriber: Fix example syntax for `color` in deprecation message
2023-01-04 15:04:56 -05:00
Akira Matsuda
ffdbf17191
Improve demodulize performance by not allocating a Range object
Warming up --------------------------------------
                 old   580.381k i/100ms
                 new   803.836k i/100ms
Calculating -------------------------------------
                 old      7.347M (± 0.9%) i/s -     37.144M in   5.056194s
                 new     10.640M (± 0.9%) i/s -     53.857M in   5.061897s

Comparison:
                 new: 10640489.8 i/s
                 old:  7346918.8 i/s - 1.45x  (± 0.00) slower
2023-01-05 04:32:04 +09:00