module TransactionTimestamps mattr_accessor :enabled module Timestamp def self.included(base) base.class_eval do # override the original timestamp with one our own that returns transaction-based timestamps alias_method :original_current_time_from_proper_timezone, :current_time_from_proper_timezone def current_time_from_proper_timezone if use_transaction_timestamps? current_transaction_time else original_current_time_from_proper_timezone end end end end def current_transaction_time if new_transaction? @@cached_timestamp = adjust_time_to_timezone(db_transaction_time) else @@cached_timestamp end end private def use_transaction_timestamps? # Timecop doesn't play well transaction timestamps, so don't enable them when using Timecop # and the time is frozen TransactionTimestamps.enabled && !(defined?(Timecop) && Timecop.frozen?) end def new_transaction? # check whether the transaction object id has changed since the last time we checked current_transaction_id = transaction_id is_new = !defined?(@@prev_transaction_id) || (current_transaction_id != @@prev_transaction_id) @@prev_transaction_id = current_transaction_id is_new end def transaction_id # use the transaction object id as a unique identifier for whether the transaction has changed manager = ActiveRecord::Base.connection.instance_variable_get(:@transaction_manager) manager.current_transaction.object_id end def db_transaction_time if postgresql? time_str = ActiveRecord::Base.connection.select_one("SELECT transaction_timestamp();")['transaction_timestamp'] Time.parse(time_str) else # other databases (MySQL, sqlite) don't support retrieval of the actual transaction time, so the best we can # do is to use the current system time (and cache this until the transaction changes) Time.now end end def postgresql? defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) && ActiveRecord::Base.connection.instance_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) end def adjust_time_to_timezone(time) # do exactly the same time adjustment as performed in ActiveRecord::Timestamp self.class.default_timezone == :utc ? time.utc : time.getlocal end end end