Auditing with Paper Trail

Episode #96 by Teacher's Avatar David Kimura

Summary

Using the paper_trail gem, track changes to model records for auditing purposes and rollback changes when required.
rails database audit 6:30

Resources

Summary

# Gemfile
gem 'paper_trail'

# Terminal
rails g papertrail:install
rails db:migrate

# config/initializers/paper_trail.rb
PaperTrail.config.track_associations = false
PaperTrail.config.version_limit = 3
PaperTrail.serializer = PaperTrail::Serializers::JSON

PaperTrail::Version.class_eval do
  def changed_object
    @changed_object ||= JSON.parse(self.object, object_class: OpenStruct)
  end
end

# user.rb
class User < ApplicationRecord
  has_paper_trail on: [:update, :destroy], only: [:first_name, :last_name]
end

# show.html.erb
<h2>Versions</h2>
<table class='table'>
  <thead>
    <tr>
      <th>ID</th>
      <th>Modifier</th>
      <th>Action</th>
      <th>Changes</th>
    </tr>
  </thead>
  <tbody>
    <% @user.versions.each do |version| %>
      <%= tag.tr do %>
        <%= tag.td version.id %>
        <%= tag.td version.whodunnit %>
        <%= tag.td link_to 'rollback', user_rollback_path(@user, version: version) %>
        <%= tag.td do %>
          <%= tag.ul class: 'list-group' do %>
            <li class='list-group-item'>
              <strong>First Name:</strong> 
              <%= version.changed_object.first_name %>
            </li>
            <li class='list-group-item'>
              <strong>Last Name:</strong> 
              <%= version.changed_object.last_name %>
            </li>
            <li class='list-group-item'>
              <strong>Email:</strong> 
              <%= version.changed_object.email %>
            </li>
          <% end %>
        <% end %>
      <% end %>
    <% end %>
</table>

# application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :set_paper_trail_whodunnit

  def user_for_paper_trail
    # user_signed_in? ? current_user.id : 'Public user'
    'Public user'
  end
end

# routes.rb
Rails.application.routes.draw do
  resources :users do
    get :rollback
  end
  root to: 'users#index'
end

# users_controller.rb
  def rollback
    @user = User.find(params[:user_id])
    version = @user.versions.find(params[:version])
    if version.reify.save
      redirect_to @user, notice: 'User was successfully rollbacked.'
    else
      render :show
    end
  end