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.