Commit Graph

9252 Commits

Author SHA1 Message Date
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
thomas morgan
2017c272f7 LogSubscriber: Fix example syntax for color in deprecation message 2023-01-04 12:19:02 -07:00
Akira Matsuda
a1c4aa87b3
Reduce two Array allocations 2023-01-05 02:15:10 +09:00
fatkodima
767ecc4131 Add ability to match exception messages to assert_raises assertion 2023-01-03 20:57:08 +02:00
Akira Matsuda
f6dbed5de4
HWIA#transform_values may take no argument
because Hash#transform_values! takes no argument and so raises when delegating
with the given arguments.

{}.with_indifferent_access.transform_values!(1) { p :hello }
 => wrong number of arguments (given 1, expected 0) (ArgumentError)
2023-01-01 21:57:41 +09:00
Jonathan Hefner
fa33ba3959
Merge pull request #46830 from skipkayhil/optimize-env-local
Use precompute optimization for Rails.env.local?
2022-12-29 15:32:17 -06:00
Akira Matsuda
663e8e1ccd
Revert "Don't extend the return value of DelegateClass"
This reverts commit 4f61d46348447e5cc54f53225a36a002274587bd.
2022-12-29 21:54:59 +09:00
Akira Matsuda
9e39c8a721
Revert "rubocop --only Layout/IndentationConsistency -a"
This reverts commit 7d0a788167d8c34d84dd331842836f159cdb978e.
2022-12-29 21:47:49 +09:00
Akira Matsuda
7d0a788167
rubocop --only Layout/IndentationConsistency -a 2022-12-29 20:29:26 +09:00
Akira Matsuda
4f61d46348
Don't extend the return value of DelegateClass
This will create an unneccesary intermidiate class without a name
2022-12-29 20:15:08 +09:00
Jonathan Hefner
c103a4328a Refactor message metadata tests
Prior to this commit, there were several places to potentially add a new
message metadata test:

* `SharedMessageMetadataTests` module
* `MessageEncryptorMetadataTest` class
  (includes `SharedMessageMetadataTests`)
* `MessageEncryptorMetadataMarshalTest` class
  (subclasses `MessageEncryptorMetadataTest`)
* `MessageEncryptorMetadataJSONTest` class
  (subclasses `MessageEncryptorMetadataTest`)
* `MessageEncryptorMetadataJsonWithMarshalFallbackTest` class
  (subclasses `MessageEncryptorMetadataTest`)
* `MessageVerifierMetadataTest` class
  (includes `SharedMessageMetadataTests`)
* `MessageVerifierMetadataMarshalTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageVerifierMetadataJsonWithMarshalFallbackTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageVerifierMetadataJsonTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageVerifierMetadataCustomJSONTest` class
  (subclasses `MessageVerifierMetadataTest`)
* `MessageEncryptorMetadataNullSerializerTest` class
  (subclasses `MessageVerifierMetadataTest`)

This commit refactors the message metadata tests, reducing the list to:

* `MessageMetadataTests` module
* `MessageEncryptorMetadataTest` class (includes `MessageMetadataTests`)
* `MessageVerifierMetadataTest` class (includes `MessageMetadataTests`)

This makes it easier to add new tests, as well as new testing scenarios
(e.g. new encryptor / verifier configurations).

Additionally, this commit fixes two tests that were ineffective:

* The `test_passing_expires_in_less_than_a_second_is_not_expired` test
  (which is now simply a part of the "message expires with :expires_in"
  test) did not use the `with_usec: true` option.  Therefore, the time
  resulting from `travel 0.5.seconds` was truncated, effectively
  traveling 0 seconds.
* The `test_verify_with_use_standard_json_time_format_as_false` test
  (which is now named "expiration works with
  ActiveSupport.use_standard_json_time_format = false") did not specify
  an expiration time.  Therefore, the conditional branch that it was
  supposed to test was never exercised.
2022-12-28 14:22:48 -06:00
Matthew Draper
0f0ec9908e
Merge pull request #46792 from codeminator/fix-flaky-tests
Increase `socket_timeout` for Memcached inside memcached tests
2022-12-27 17:45:33 +10:30
Hartley McGuire
ba2e06aee9
Use precompute optimization for Rails.env.local?
Ref: 09e0372

This makes #local? behave the same as the predefined environment
predicates (they immediately return a precomputed instance variable).

Benchmark:

```ruby
dev = ActiveSupport::EnvironmentInquirer.new("development")
test = ActiveSupport::EnvironmentInquirer.new("test")
prod = ActiveSupport::EnvironmentInquirer.new("production")

Benchmark.ips do |x|
  x.report("dev local?") { dev.local? }
  x.report("test local?") { test.local? }
  x.report("prod local?") { prod.local? }
  x.compare!
end
```

Before:
```
Warming up --------------------------------------
          dev local?   676.645k i/100ms
         test local?   627.687k i/100ms
         prod local?   671.078k i/100ms
Calculating -------------------------------------
          dev local?      6.785M (± 1.8%) i/s -     34.509M in   5.087679s
         test local?      6.284M (± 2.0%) i/s -     32.012M in   5.096338s
         prod local?      6.759M (± 1.8%) i/s -     34.225M in   5.065240s

Comparison:
          dev local?:  6785134.0 i/s
         prod local?:  6759023.6 i/s - same-ish: difference falls within error
         test local?:  6283910.1 i/s - 1.08x  (± 0.00) slower
```

After:
```
Warming up --------------------------------------
          dev local?     1.076M i/100ms
         test local?     1.049M i/100ms
         prod local?     1.028M i/100ms
Calculating -------------------------------------
          dev local?     10.586M (± 2.3%) i/s -     53.799M in   5.084729s
         test local?     10.350M (± 2.5%) i/s -     52.457M in   5.071399s
         prod local?     10.396M (± 2.2%) i/s -     52.422M in   5.045193s

Comparison:
          dev local?: 10586400.1 i/s
         prod local?: 10395758.6 i/s - same-ish: difference falls within error
         test local?: 10350328.8 i/s - same-ish: difference falls within error
```
2022-12-26 18:26:59 -05:00
Ahmed Shahin
adaea233d0 Apply the new socket_timeout at mocked expectations 2022-12-22 14:57:14 +02:00
Ahmed Shahin
385626fbe7 Increase socket_timeout for memcached inside tests
It sounds like a default timeout of 1 second can sometimes not be enough.
In normal operations, this should be fine (will result in a cache miss),
but in these tests, we always expect the cache to return the value, hence doing this change for these tests only.

Co-authored-by: Matthew Draper <matthew@trebex.net>
2022-12-22 14:16:59 +02:00
Jonathan Hefner
4c4f4c385a Avoid double JSON parse for metadata-less messages
In #46612, a check was added to only attempt metadata extraction if the
message looks like a JSON object (i.e. starts with "{"), thus avoiding
an unnecessary JSON parse and possible exception.

This commit extends the check to only attempt metadata extraction if the
message looks like a metadata wrapper object (i.e. has the "_rails"
key).  This avoids an unnecessary JSON parse of JSON object messages
that don't have metadata.

**Benchmark**

  ```ruby
  require "benchmark/ips"
  require "active_support/all"

  verifier = ActiveSupport::MessageVerifier.new("secret", serializer: JSON)
  message_100 = verifier.generate({ content: "x" * 100 })
  message_1m = verifier.generate({ content: "x" * 1_000_000 })

  Benchmark.ips do |x|
    x.report("100 chars") do
      verifier.verify(message_100)
    end

    x.report("1m chars") do
      verifier.verify(message_1m)
    end
  end
  ```

**Before**

  ```
  Warming up --------------------------------------
             100 chars     2.803k i/100ms
              1m chars     6.000  i/100ms
  Calculating -------------------------------------
             100 chars     27.762k (± 1.6%) i/s -    140.150k in   5.049649s
              1m chars     83.516  (±16.8%) i/s -    402.000  in   5.037269s
  ```

**After**

  ```
  Warming up --------------------------------------
             100 chars     3.360k i/100ms
              1m chars     9.000  i/100ms
  Calculating -------------------------------------
             100 chars     33.480k (± 1.7%) i/s -    168.000k in   5.019311s
              1m chars    113.373  (±15.0%) i/s -    549.000  in   5.023443s
  ```
2022-12-21 14:12:15 -06:00
David Heinemeier Hansson
47eaf8881c
Add Rails.env.local? (#46786)
* Add Rails.env.local?

So many checks against Rails.env is whether we're running test or development, so combine into just one.

* Add CHANGELOG

* Prevent 'local' from being used as an environment name

Now that we have it as a combined predicate for dev + test.
2022-12-21 15:38:33 +01:00
David Heinemeier Hansson
2e142d9ae0 Explain the optimization setup 2022-12-21 14:51:02 +01:00
Hartley McGuire
daff36c157
Eager autoload ActiveSupport::ExecutionContext
ExecutionContext was added as a regular autoload when it was introduced
in 6bad959. However, the class is not currently referenced anywhere on
the boot path. This means that the file will currently be required
during the first request/job/query (currently its loaded when the to_run
callback defined in active_support.reset_execution_context is executed).

To maximize CoW and ensure that the first request/job/query doesn't have
any extra latency, ExecutionContext should be eager autoloaded instead.
2022-12-19 19:51:52 -05:00
Jonathan Hefner
61f711a1ff Support Message{Encryptors,Verifiers}#rotate block
This commit adds a block form of `ActiveSupport::MessageEncryptors#rotate`
and `ActiveSupport::MessageVerifiers#rotate`, which supports
fine-grained per-salt rotation options.  The block will receive a salt,
and should return an appropriate options Hash.  The block may also
return `nil` to indicate that the rotation does not apply to the given
salt.  For example:

  ```ruby
  verifiers = ActiveSupport::MessageVerifiers.new { ... }
  verifiers.rotate(serializer: JSON, url_safe: true)
  verifiers.rotate do |salt|
   case salt
   when :foo
     { serializer: Marshal, url_safe: true }
   when :bar
     { serializer: Marshal, url_safe: false }
   end
  end

  # Uses `serializer: JSON, url_safe: true`.
  # Falls back to `serializer: Marshal, url_safe: true`.
  verifiers[:foo]

  # Uses `serializer: JSON, url_safe: true`.
  # Falls back to `serializer: Marshal, url_safe: false`.
  verifiers[:bar]

  # Uses `serializer: JSON, url_safe: true`.
  verifiers[:baz]
  ```

This can be particularly useful when migrating older configurations to a
unified configuration.
2022-12-19 16:35:20 -06:00