Playback Papertrail Audit Logs

We use paper_trail quite often, it’s a wonderful gem and generally part of our standard suite for business apps, we primarily use it for audit logging, today we needed to get data out of this history

There are quite a few ways to do this, from having daily server backups or parsing logs. They all have there quirks, so does this solution.  In this example we want to playback and take a snapshot of our orders table, let’s say value grouped by status.

paperclip_rollback.rb

# This utterly destroys your data, and does not attempt to put things back. 
# BE WARNED
def roll_back_by_day(klass, start_at, end_at, &block)
  # Turn off papertrail, we don't want our playback creating new versions.
  klass.constantize.paper_trail_off!

  # Play back on changes per day, at the last record on a day take snapshot
  # using the block that was passed.
  (end_at.to_date..start_at.to_date).reverse_each do |date|
    query = PaperTrail::Version
    .where(item_type: klass)
    .where('created_at >= ? AND created_at <= ?', date.beginning_of_day, date.end_of_day)
    .order('created_at DESC')

    query.each do |version|
      # Rewalk DB
      item = version.reify

      # Check for ID. Some papertrail versions are marked as update but don't
      # have an ID
      item.save!(validate: false) if item && item.id?
      version.item.delete if !item && version.item
    end

    block.call(date) if block

    puts "Total #{klass}: #{klass.constantize.count} for #{date}"
  end
  klass.constantize.paper_trail_on!
  puts 'Done'
end


# Real use case
start_at = PaperTrail::Version.last.created_at
end_at   = PaperTrail::Version.first.created_at
klass    = 'Order'

ActiveRecord::Base.connection.execute("TRUNCATE summary_order_statuses RESTART IDENTITY")

roll_back_by_day(klass, start_at, end_at) do |date|
  Order.distinct.pluck(:status).each do |status|
    order_sql = Order.where(status: status)
    Summary::OrderStatus.create(
      date: date,
      order_status: status,
      count: order_sql.count,
    dollar_amount_in_cents: order_sql.sum(:cached_total) * 100)
  end
end
View original code on GitHub