RailsRecipe: Preview a Site on a Different Day

Recently we had a requirement to build a preview mode for a Ruby on Rails application. An article might be scheduled to appear on the front page in the future and the editorial staff needed a way to see how the site (front page, calendars, etc.) would look then. Our application works largely off Date.today to determine what content to display. We extended the Date, DateTime, and Time objects to transparently provide a user chosen date for the application as opposed to the system clock.

Ingredient One: PreviewController
This preview mode was activated through a preview controller. The preview action accepts a date parameter and placed that onto the user’s session. Following that the action redirected to the url being previewed.

def preview
  session[:preview] = {}
  if params[:date]
    begin
      date = Date.parse(params[:date])
      date_time = DateTime.parse(params[:date])
      session[:preview][:date] = date
      session[:preview][:datetime] = date_time
      session[:preview][:time] = Time.mktime(date.year, date.month, date.day)
    rescue
    end
  end
  redirect_to(params[:url] || "/")
end

Ingredient Two: Around Filter on ApplicationController
An Around Filter was added to ApplicationController that looked for a session[:date] attribute on all page requests. If the session object was present the filter placed the user’s preview date onto Thread.current.

around_filter :preview_date
def preview_date
    begin
    Thread.current[:preview] = session[:preview]
      yield
    ensure
    Thread.current[:preview] = nil
    end
  end

Ingredient Three: Overriding System Date
The final ingredient was extending Date.today, DateTime.now, and Time.now with an alias_method_chain to use the Thread.current objects when present instead of using the CPU clock.

DateExtension

module DateExtension
  def self.extended(object)
    class << object
      alias_method_chain :today, :preview
    end
  end

  def today_with_preview
    (Thread.current[:preview] and Thread.current[:preview][:date]) ?
        Thread.current[:preview][:date] : today_without_preview
  end
end

Similar extensions were done for DateTime and Time. I would have liked to have only extended Time.now but that was not sufficient when testing Date and DateTime.

This solution transparently made our application behave in a user preview mode. As far as the application was concerned the date was the preview date with little change beyond the above code which plugs in transparently to the rest of the application. Our ActiveRecord queries all keyed off Date.today and our code was not polluted by introducing an ApplicationDate class that we might have been forced to use in Java or .NET.

Leave a Reply