Commit Graph

7562 Commits

Author SHA1 Message Date
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