Reason #150 •
May 30th, 2026
Using blocks for temporary state changes
Another very common use case for blocks is to temporarily change some state, execute a block of code with that state and then restore the original state.
Some examples include:
Ruby
# Temporarily change working directory
Dir.chdir("/tmp") do
puts Dir.pwd # => "/tmp"
end
# ActiveSupport's Time.use_zone temporarily changes the time zone
Time.use_zone("Helsinki") do
puts Time.zone.name # => "Helsinki"
end
# I18n's .with_locale temporarily changes the locale
I18n.with_locale(:fr) do
puts I18n.locale # => :fr
end
# ClimateControl's .modify temporarily changes environment variables
ClimateControl.modify PATH: "/tmp" do
puts ENV["PATH"] # => "/tmp"
end
# minitest's .stub temporarily replaces a method with a stub implementation
Time.stub(:now, Time.new(2020, 1, 1)) do
puts Time.now # => 2020-01-01 00:00:00
end
# Timecop's .freeze temporarily freezes time to a specific moment
Timecop.freeze(Time.new(2020, 1, 1)) do
puts Date.today # => 2020-01-01
end
As you can see, there is no end to use cases for this pattern.
Let's implement our own ENV modifier to see how this works under the hood:
Ruby
module EnvModifier
def self.modify(temp_env)
original_env = ENV.to_hash
temp_env.each { |key, value| ENV[key] = value }
yield
ensure
ENV.replace(original_env)
end
end
puts ENV["TEST"]
# => nil
EnvModifier.modify("TEST" => "temporary value") do
puts ENV["TEST"]
# => "temporary value"
end
puts ENV["TEST"]
# => nil
Note that this implementation isn't thread-safe. You'd need to add a locking mechanism for that, but it serves well to illustrate the pattern.
In general you might want to consider this pattern whenever you find yourself storing some "original value", changing it and then restoring it later. By using a block together with ensure, you can do so in a robust and expressive way!