Skip to content

Commit c9beaf1

Browse files
Track parent key as string in ParameterFilter
This avoids allocating an array each time `ParameterFilter#filter` is called, and avoids a few redundant operations when building up the full key. This yields a small but reproducible performance increase: **Benchmark script** ```ruby # frozen_string_literal: true require "benchmark/ips" require "benchmark/memory" require "active_support" require "active_support/parameter_filter" ootb = [:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn] mixed = [:passw, "secret", /token/, :crypt, "salt", /certificate/, "user.otp", /user\.ssn/, proc {}] params = { "user" => { "name" => :name, "email" => :email, "password" => :password, "ssn" => :ssn, "locations" => [ { "city" => :city, "country" => :country }, { "city" => :city, "country" => :country }, ], } } Benchmark.ips do |x| x.report("ootb") do ActiveSupport::ParameterFilter.new(ootb).filter(params) end x.report("mixed") do ActiveSupport::ParameterFilter.new(mixed).filter(params) end end ``` **Before** ``` Warming up -------------------------------------- ootb 2.003k i/100ms mixed 1.362k i/100ms Calculating ------------------------------------- ootb 19.975k (± 0.9%) i/s - 100.150k in 5.014277s mixed 13.627k (± 0.9%) i/s - 69.462k in 5.097741s ``` **After** ``` Warming up -------------------------------------- ootb 2.032k i/100ms mixed 1.521k i/100ms Calculating ------------------------------------- ootb 20.315k (± 1.3%) i/s - 101.600k in 5.001939s mixed 15.142k (± 1.2%) i/s - 76.050k in 5.023077s ```
1 parent 586436d commit c9beaf1

File tree

1 file changed

+11
-13
lines changed

1 file changed

+11
-13
lines changed

activesupport/lib/active_support/parameter_filter.rb

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,37 +100,35 @@ def initialize(regexps, deep_regexps, blocks, mask:)
100100
@mask = mask
101101
end
102102

103-
def call(params, parents = [], original_params = params)
103+
def call(params, full_parent_key = nil, original_params = params)
104104
filtered_params = params.class.new
105105

106106
params.each do |key, value|
107-
filtered_params[key] = value_for_key(key, value, parents, original_params)
107+
filtered_params[key] = value_for_key(key, value, full_parent_key, original_params)
108108
end
109109

110110
filtered_params
111111
end
112112

113-
def value_for_key(key, value, parents = [], original_params = nil)
114-
parents.push(key) if deep_regexps
113+
def value_for_key(key, value, full_parent_key = nil, original_params = nil)
114+
if deep_regexps
115+
full_key = full_parent_key ? "#{full_parent_key}.#{key}" : key.to_s
116+
end
117+
115118
if regexps.any? { |r| r.match?(key.to_s) }
116119
value = @mask
117-
elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
120+
elsif deep_regexps&.any? { |r| r.match?(full_key) }
118121
value = @mask
119122
elsif value.is_a?(Hash)
120-
value = call(value, parents, original_params)
123+
value = call(value, full_key, original_params)
121124
elsif value.is_a?(Array)
122-
# If we don't pop the current parent it will be duplicated as we
123-
# process each array value.
124-
parents.pop if deep_regexps
125-
value = value.map { |v| value_for_key(key, v, parents, original_params) }
126-
# Restore the parent stack after processing the array.
127-
parents.push(key) if deep_regexps
125+
value = value.map { |v| value_for_key(key, v, full_parent_key, original_params) }
128126
elsif blocks.any?
129127
key = key.dup if key.duplicable?
130128
value = value.dup if value.duplicable?
131129
blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
132130
end
133-
parents.pop if deep_regexps
131+
134132
value
135133
end
136134
end

0 commit comments

Comments
 (0)