Correction, I should probably wrap the user code in an ensure block to
ensure that the constant gets reset:

class Module
  def inject_constant(constant, value)
    old_value = const_get(constant)
    const_set(constant, value)
    if block_given?
      begin
        yield
      ensure
        const_set(constant, old_value)
      end
    end
  end
end

Steve

-----Original Message-----
From: Molitor, Stephen L 
Sent: Wednesday, May 24, 2006 4:21 PM
To: ruby-talk ML
Subject: Re: Overriding Time.now

Very nice!  If I understand correctly, I could do something like this:

class Module
  def inject_constant(constant, value)
    old_value = const_get(constant)
    const_set(constant, value)
    if block_given?
      yield
      const_set(constant, old_value)
    end
  end
end

Then in a test, I could do this:

def test_my_app
  MyApp::inject_constant(:Time, MockTime) end

If all of my code is in the module MyApp, then all of my code uses
MockTime whenever it refers to Time.  Forever in this case, which might
be fine in a test.  Or I could pass a block to limit the duration.  Or
use setup / teardown.  If I want to be more fine grained I can just
inject into one class. 

In any case, I can mock out the current time without messing up external
code, and without changing my original code.  All without a fancy DI or
AOP framework.  Wow!

Is this the cleanest implementation of this technique?

I went through all your DI presentation slides.  Looks like it was a
fantastic presentation.

Steve
 

-----Original Message-----
From: list-bounce / example.com [mailto:list-bounce / example.com] On Behalf
Of Jim Weirich
Sent: Wednesday, May 24, 2006 12:01 PM
To: ruby-talk ML
Subject: Re: Overriding Time.now

Molitor, Stephen L wrote:
>> I believe that you can redefine Time.now in the scope where you need 
>> it without interfering with Test::Unit
> 
> There's only one instance of the Time class object, and any changes 
> you make to it are in effect 'global'.

You can use a technique called "Constant Injection" where you
temporarily change the definition of a constant within the scope of a
single class.

See http://onestepback.org/articles/depinj/classesarejustobjects.html
for an example.

This allows you to say something like:

   def test_my_class
     MyClass.use_class(:Time, MockTime) do
       mc = MyClass.new
       # Any calls to Time.now within MyClass will be routed to
MockTime.now
     end
     # Calls to Time.now are now back to normal.
   end

-- Jim Weirich

--
Posted via http://www.ruby-forum.com/.