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!

Reason #151 ?