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
endView original code on GitHub