Memoize config hash on finalize (#151)
Freezing the hash is beneficial at this point because it saves repeated expensive computation if that hash is to be used later in performance-sensitive situations, such as when serving as a cache key or similar.
This commit is contained in:
parent
4f3039de71
commit
592fb1ca9f
1
Gemfile
1
Gemfile
|
@ -19,4 +19,5 @@ end
|
|||
group :tools do
|
||||
gem "hotch", platform: :mri
|
||||
gem "pry-byebug", platform: :mri
|
||||
gem "rspec-benchmark"
|
||||
end
|
||||
|
|
|
@ -146,6 +146,16 @@ module Dry
|
|||
values.to_h { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
|
||||
end
|
||||
|
||||
# @api private
|
||||
alias_method :_dry_equalizer_hash, :hash
|
||||
|
||||
# @api public
|
||||
def hash
|
||||
return @__hash__ if instance_variable_defined?(:@__hash__)
|
||||
|
||||
_dry_equalizer_hash
|
||||
end
|
||||
|
||||
# @api private
|
||||
def finalize!(freeze_values: false)
|
||||
values.each_value do |value|
|
||||
|
@ -156,6 +166,13 @@ module Dry
|
|||
end
|
||||
end
|
||||
|
||||
# Memoize the hash for the object when finalizing (regardless of whether values themselves
|
||||
# are to be frozen; the intention of finalization is that no further changes should be
|
||||
# made). The benefit of freezing the hash at this point is that it saves repeated expensive
|
||||
# computation (through Dry::Equalizer's hash implementation) if that hash is to be used
|
||||
# later in performance-sensitive situations, such as when serving as a cache key or similar.
|
||||
@__hash__ = _dry_equalizer_hash unless frozen?
|
||||
|
||||
freeze
|
||||
end
|
||||
|
||||
|
|
|
@ -239,6 +239,28 @@ RSpec.describe Dry::Configurable::Config do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#hash" do
|
||||
it "returns the integer hash value for the convig based on its values" do
|
||||
klass.setting :db
|
||||
|
||||
expect(klass.config.hash).to be_an_instance_of(Integer)
|
||||
expect { klass.config.db = "sqlite" }.to change { klass.config.hash }
|
||||
end
|
||||
|
||||
it "is memoized when the config is finalized", :performance do
|
||||
klass.setting :a
|
||||
klass.setting :b
|
||||
klass.setting :c
|
||||
klass.setting :d
|
||||
klass.setting :e
|
||||
|
||||
finalized_config = klass.config.dup.finalize!
|
||||
|
||||
expect(finalized_config.hash).to eq klass.config.hash
|
||||
expect { finalized_config.hash }.to perform_faster_than { klass.config.hash }.at_least(50).times
|
||||
end
|
||||
end
|
||||
|
||||
describe "#method_missing" do
|
||||
it "provides access to reader methods" do
|
||||
klass.setting :hello
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require_relative "support/coverage"
|
||||
require "pathname"
|
||||
require "rspec-benchmark"
|
||||
|
||||
SPEC_ROOT = Pathname(__FILE__).dirname
|
||||
|
||||
|
@ -22,6 +23,8 @@ RSpec.configure do |config|
|
|||
config.disable_monkey_patching!
|
||||
config.filter_run_when_matching :focus
|
||||
|
||||
config.include RSpec::Benchmark::Matchers
|
||||
|
||||
config.around do |example|
|
||||
module Test
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue