Testando datas no Ruby

03/02/08

Estou reescrevendo o Spesa e, obviamente, ele exige uma série de cálculos com datas devido aos agendamentos. Na versão anterior, a maioria destes cálculos era feita em consultas SQL, trazendo uma complexidade um tanto quanto desnecessária, já que muito da lógica fica no banco de dados, incluindo algumas Stored Procedures.

Na nova versão toda esta lógica foi trazida para o Ruby, o que facilitou o desenvolvimento. Mas nem tudo foi fácil. Os testes exigiram um pouco de pesquisa — não mais que alguns minutos — já que testar datas é sempre um pouco mais complicado. A dificuldade está em manter a consistência, já que o resultado irá variar dependendo do dia que você executar os testes.

Devido a possibilidade de sobrecarga de métodos que o Ruby oferece, é simples e ao mesmo tempo elegante. Utilizando o código abaixo é possível forçar o método Time.now para sempre retornar uma data específica.

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

Adicione o código acima ao seu helper de testes, que no meu caso é "spec/spec_helper.rb", pois estou usando RSpec. Para usá-lo, basta definir uma data como sendo a atual. No exemplo abaixo estou definindo a data atual como sendo "28/02/2007".

it "should mock today method" do
  mocked_today = Time.gm(2007, 2, 28)
  Time.mock_time = mocked_today
 
  mydate = Date.today
  mydate.should eql(mocked_today.to_date)
end

Vi essa dica no Ruby Forum e com certeza vale o bookmark.

Update: Seguindo a dica do Tapajós, você pode usar o Mocka, mas de uma maneira diferente da explicada por ele:

Time.stubs(:now).returns(Time.gm(2008, 2, 1))

Update 2: Mais simples ainda e usando apenas o próprio RSpec:

Time.stub!(:now).and_return(Time.gm(2008, 2, 1))

Formatando datas no Ruby on Rails

31/08/07

Imagine que você tenha um campo datetime e que você queira exibir logo abaixo de um post de blog. Você poderia criar um helper para fazer isso:

class PostsHelper
  def post_date(date)
    # formatting date: Aug, 31 2007 - 9:55PM
    date.strftime("posted on %b, %m %Y - %H:%M")
  end
end

E em seu template poderia chamar algo como:

<%= post_date @post.created_at %>

Você também poderia fazer esta formatação no modelo, utilizando o método after_find:

class Post < ActiveRecord::Base
  private
    def after_find
      self.created_at = created_at.strftime("posted on %b, %m %Y - %H:%M")
    end
end

Porém, a maneira mais interessante de se formatar datas no Rails, é utilizando o método to_s; tudo o que você precisa fazer é definir um novo formato. Para fazer isso, adicione a seguinte linha ao seu arquivo "environment.rb":

Time::DATE_FORMATS[:post] = "posted on %b, %m %Y - %I:%M%p"

Para exibir a data devidamente formatada, basta chamar o método to_s passando o formato que você quer utilizar.

<%= @post.created_at.to_s(:post) %>

Lembre-se que você pode definir quantos formatos quiser:

Time::DATE_FORMATS[:archive] = "%B/%Y"
Time::DATE_FORMATS[:updated] = "updated on %b, %m %Y - %I:%M%p"