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.
This is in line with transitioning away from the global
`ActiveSupport::Deprecation` instance, towards individual
`ActiveSupport::Deprecation` instances.
This matches the indentation used in generated code, such as code from
`railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt`.
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
```
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
```
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)
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
```
* 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.
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.
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.
Rescuing an exception here can be wildly expensive here. We noticed 22ms overhead
on each request when attempting to decode metadata that was serialized using Marshal.
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
This commit adds a `transitional` attribute to
`ActiveSupport::MessageEncryptors` and `ActiveSupport::MessageVerifiers`.
Setting `transitional = true` will swap the first two rotations when
building a message encryptor / verifier. For example, with the
following configuration, message verifiers will generate messages using
`serializer: Marshal, url_safe: true`, and will able to verify messages
that were generated using any of the three option sets:
```ruby
verifiers = ActiveSupport::MessageVerifiers.new { ... }
verifiers.rotate(serializer: JSON, url_safe: true)
verifiers.rotate(serializer: Marshal, url_safe: true)
verifiers.rotate(serializer: Marshal, url_safe: false)
verifiers.transitional = true
```
This can be useful when performing a rolling deploy of an application,
wherein servers that have not yet been updated must still be able to
verify messages from updated servers. In particular, as it can be
applied to the default rotations of `Rails.application.message_verifiers`.
This fixes some issues I found while working on https://github.com/rails/rails/pull/46718
- Some of the tests in `test_runner_test.rb` generate a new test file, then run it. In some cases, the generated tests were failing. The calling test didn't expect this, but didn't fail despite that. So in this PR I have fixed the generated tests to always pass, and added `allow_failure: false` to enforce this.
- Added a missing test for when `PARALLEL_WORKERS=1`.
- Fixed a typo and removed some unnecessary wording in the docs.
Co-authored-by: Adam Hess <hparker@github.com>
Fixes an issue where parameterizing strings was mutating the source, eg:
```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 "sqlite3"
end
require "active_record"
require "minitest/autorun"
require "logger"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :posts, force: true do |t|
t.text :name
t.text :slug
end
end
class Post < ActiveRecord::Base
validate :generate_slug
def generate_slug
self.slug = name.parameterize
end
end
class BugTest < Minitest::Test
def test_name_gets_corrupted
post = Post.create!(name: "hi there")
assert_equal "hi there", post.name # This test fails, "hi-there"
end
end
```
Since engine initializers run later in the process, we need to run this
initializer earlier than the default.
This ensures they're all registered before the environments are loaded.
Although disallowed warnings are likely to be deprecator-specific, it
can be useful to set this value once and have it applied to any
deprecators that are added later. For example, setting the value in an
initializer via `config.active_support.disallowed_deprecation_warnings`
and applying it to any third-party deprecators that are added later in
the boot process. Also, when using `:all`, it is more convenient to
write `deprecators.disallowed_warnings = :all` instead of
`deprecators.each { |deprecator| deprecator.disallowed_warnings = :all }`.