Forum: Ruby on Rails Overriding Date.today for a Rails app

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
63d809dbf4d44297f73caad307972ec5?d=identicon&s=25 John Trupiano (Guest)
on 2007-07-04 16:10
(Received via mailing list)
Hey guys,

I have a time-sensitive application.....not in the sense that its
completion is urgent (although like all projects, it is), but rather
in the sense that much of the functionality is determined by the
actual time/date.

As a result, I've found that I need to freeze the concept of today
both in development and testing (more-so in testing so that I can
write static test sets).

I've started down the road of using an environment variable to store
our date (Date.today if production, else a date that all of my tests
can be based off of).  I'm unhappy with this solution for a few
reasons.

First, the ENV hash requires my values to be strings (try putting this
in your environment.rb file: ENV['today'] = Date.new(2007, 5, 15) ---
I was shocked when I got a TypeError).  As such, for this to be
useful, I need to write a function that "hides" this
implementation---- explicitly a reader/writer pair that converts
between strings and dates.  Since this reader/writer pair needs to be
accessible from all parts of my application (testing, controllers,
views), I find myself having to put this pair in multiple
places....not very DRY.

Furthermore (and perhaps worst of all), I actually want my models to
use this redefinition of Date.today.  Now, my models _should_ be
completely unaware that they're being used in a Rails app, so I have a
big problem with them knowing about this.  However, I'm not sure of
another way around it.

My last problem just stems from the fact that my whole app now needs
to know about this "other" way to get Date.today (some global function
defined somewhere).....just doesn't feel right.

Another route that I considered was to reopen the Date class and
redefine the class method "today" conditionally (based on which
environment was loaded).  However, I ran into some errors with the
following snippet of code in my environment.rb file:

class Date
  def self.today(sg=ITALY)
    new(2007, 5, 15)
  end
end

It's possible the environment.rb file is the incorrect place to put
this.

I'm open to any thoughts/suggestions/whatever.  How has anyone else
handled this problem in the past?  What issues did you run into?

-John
63d809dbf4d44297f73caad307972ec5?d=identicon&s=25 John Trupiano (Guest)
on 2007-07-04 16:12
(Received via mailing list)
I forgot to mention.....the error I received when I tried to override
the Date.today function as coded below was....

C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/rubygems/
source_index.rb:92:i
n `load_specification': undefined method `_parse' for
Rails::Initializer::Date:C
lass (NoMethodError)
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/specifi
cation.rb:13:in `today'
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/specifi
cation.rb:314:in `date='
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/specifi
cation.rb:416:in `send'
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/specifi
cation.rb:416:in `initialize'
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/specifi
cation.rb:412:in `each'
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/specifi
cation.rb:412:in `initialize'
        from (eval):1:in `new'
        from (eval):1:in `load_specification'
         ... 29 levels...
        from C:/Program Files (x86)/Ruby/lib/ruby/gems/1.8/gems/
rails-1.2.3/lib/
commands/server.rb:39
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/custom_
require.rb:27:in `gem_original_require'
        from C:/Program Files (x86)/Ruby/lib/ruby/site_ruby/1.8/
rubygems/custom_
require.rb:27:in `require'
        from script/server:3
5190330ad8f1b06d35e2c2da73dc623c?d=identicon&s=25 Eric Anderson (Guest)
on 2007-07-04 19:43
(Received via mailing list)
John Trupiano wrote:
> As a result, I've found that I need to freeze the concept of today
> both in development and testing (more-so in testing so that I can
> write static test sets).

I had an app a while back with a similar issue. Depending on the current
day would determine the results so I needed to be able to define "today"
to have reliable test. Here is the result I came up with:

class Time
   class << self
     # Time we might be behaving as
     attr_reader :mock_time

     # Set new time to pretend we are.
     def mock_time=(new_now)
       @mock_time = new_now
     end

     # now() with possible augmentation
     def now_with_mock_time
       mock_time || now_without_mock_time
     end
     alias_method_chain :now, :mock_time
   end
end

Just put this in your test_helper.rb.

This would redefine "now" to use the value I wanted for today.
Everything seems to flow well from that so 3.days.ago returns the right
value. It even works with the various time zone plugins to get time
zones working right in Rails.

So to set "today" to be Feb 1, 2006 simply do:

   Time.mock_time = Time.gm 2006, 2, 1

To restore the normal space-time continuum just:

   Time.mock_time = nil

Let me know if you need any more pointers on this.

Eric
63d809dbf4d44297f73caad307972ec5?d=identicon&s=25 John Trupiano (Guest)
on 2007-07-04 20:32
(Received via mailing list)
This is fantastic.  Thanks Eric.  I'll let you know if anything else
comes up.

-John
5f23b06d22b84ec6c902bf390071a79c?d=identicon&s=25 _Kevin (Guest)
on 2007-07-06 15:16
(Received via mailing list)
I use a variant of the time / date mocking for testing.  Also includes
this nifty block helper, which iterates over the passed values setting
the time to each one before executing the block..

class Date
  @@forced_today = nil
  class << self
    alias :unforced_today :today
    def forced_today
      return @@forced_today ? @@forced_today : unforced_today
    end
    alias :today :forced_today

    def forced_today=(now)
      @@forced_today = now
    end
  end
end

class DateTime
  @@forced_now = nil
  class << self
    alias :unforced_now :now
    def forced_now
      return @@forced_now ? @@forced_now : unforced_now
    end
    alias :now :forced_now

    def forced_now=(now)
      @@forced_now = now
    end
  end
end

  def with_dates(*dates, &block)
    dates.flatten.each do |date|
      begin
        DateTime.forced_now = case date
          when String: DateTime.parse(date)
          when Time: DateTime.parse(date.to_s)
          else
            date
        end
        Date.forced_today = Date.new(DateTime.forced_now.year,
DateTime.forced_now.month, DateTime.forced_now.day)
        yield
      rescue Exception => e
        raise e
      ensure
        DateTime.forced_now = nil
        Date.forced_today = nil
      end
    end
  end

put this all in your test_helper.rb file.  You can do something
similar with Time as well.

now you can do this in your tests..

with_dates(Date.today + 1, Date.today >> 1) do
  some_code....
end

I like this method because you can't forget to set the clock back to
normal.

_Kevin
5190330ad8f1b06d35e2c2da73dc623c?d=identicon&s=25 Eric Anderson (Guest)
on 2007-07-06 17:03
(Received via mailing list)
_Kevin wrote:
> I like this method because you can't forget to set the clock back to
> normal.

Looks like a nice robust implemention. I just put:

Time.mock_time = nil

in my teardown method to make sure any time changes are reset after a
test.

Eric
This topic is locked and can not be replied to.