Sometimes I want to verify the value within a constant is handled in a certain way, independently of what the actual value in the constant is defined as.

So I change it, temporarily.

ApplicationConfig.override_constant(:RECORD_ANALYTICS, false) do
  puts "For the duration of this block, RECORD_ANALYTICS is #{ApplicationConfig::RECORD_ANALYTICS}"
end

# Override the value of a Module Constant for the duration of a block.
class Module
  def override_constant constant_name, new_value
    raise ArgumentError, 'No block provided to establish a context in which the constant is overridden' unless block_given?
    # Cannot override unless constant is defined in Module
    raise NameError, "#{self} does not define #{constant_name}" unless const_defined?(constant_name)
 
    # Store original value to replace it at the end
    original_value = const_get(constant_name)
 
    # Remove original definition
    remove_const constant_name
 
    # Insert alternative definition
    const_set constant_name, new_value
 
    begin
      yield
 
    ensure
      # Remove alternative definition
      remove_const constant_name
 
      # Restore original definition
      const_set constant_name, original_value
    end
  end
end

class OverrideConstantTest < Test::Unit::TestCase
  module TestModule
    DEFINED_CONSTANT = true
  end
 
  def test_should_raise_argument_error_when_block_is_not_provided
    assert_raise_with_error_message ArgumentError, :error_message => 'No block provided to establish a context in which the constant is overridden' do
      TestModule.override_constant :DEFINED_CONSTANT, true
    end
  end
 
  def test_should_raise_error_when_module_does_not_define_constant
    assert_raise_with_error_message NameError, :error_message => 'OverrideConstantTest::TestModule does not define NO_SUCH_CONSTANT' do
      TestModule.override_constant :NO_SUCH_CONSTANT, true do
      end
    end
  end
 
  def test_should_alter_value_of_constant_for_duration_of_block
    assert_equal true, TestModule::DEFINED_CONSTANT
 
    TestModule.override_constant :DEFINED_CONSTANT, false do
      assert_equal false, TestModule::DEFINED_CONSTANT
    end
 
    assert_equal true, TestModule::DEFINED_CONSTANT
  end
 
end

One Comment

  1. duncanbeevers

    Updated to ensure constants are returned to their original state, even if the yielded block raises an error.

Leave a Comment

Enclose code in <code lang="ruby"></code> if you care.
Preview your comment using the button below.