5

I've set papertrail to only record changes containing a whodunnit value/when an admin makes a change by using the below condition in my model:

has_paper_trail if: proc { |model| PaperTrail.request.whodunnit.present? }

However I've noticed there are still a decent amount of records being stored with empty whodunnit values. From having a look at the records, these seem to be mostly 'update' actions all having empty object changes for some reason. I am unsure as to why the value is empty, or how it would get saved at all considering the above condition.

I am getting whodunnit values from warden in my application controller using:

def user_for_paper_trail
  request.env['warden']&.user(:admin)&.id
end

Has anyone come across similar behaviour before?

L457
  • 1,002
  • 1
  • 13
  • 33
  • What kind of object are you storing in `whodunnit`? – taylorthurlow Jan 07 '21 at 01:48
  • Also, can you confirm that you aren't also using an `unless` option on the same model? – taylorthurlow Jan 07 '21 at 01:53
  • Also I'd like you know if you're using `info_for_paper_trail` in any of your controllers, or manually setting the value `PaperTrail.request.controller_info`. If you are including a `whodunnit` key in either of those it could potentially be overriding the value. – taylorthurlow Jan 07 '21 at 02:04
  • @taylorthurlow I'm storing string ids in whodunnit, and I have a separate `whodunnit_type` column which stores string model names to differentiate between user types. I'm using `info_for_paper_trail` to populate this column via `{ whodunnit_type: request.env['warden']&.user(:admin).class&.name }`). Both of these columns and changesets seem to be blank in about 50% of my collected logs. – L457 Jan 07 '21 at 14:08
  • Hmm, OK. I don't have any other theories, sorry. – taylorthurlow Jan 07 '21 at 18:20
  • Could this [closed issue](https://github.com/paper-trail-gem/paper_trail/issues/1181) never have been fixed? – lacostenycoder Jan 10 '21 at 15:41
  • 1
    Could it be, by any chance, that the updates with an empty `whodunnit` value have been performed by something other than a controller, e.g. in a background job or via the console? There you'd have to set the `whodunnit` value manually (see here: https://github.com/paper-trail-gem/paper_trail/wiki/Setting-whodunnit-in-the-rails-console). – Clemens Kofler Jan 11 '21 at 12:17
  • @ClemensKofler don't think so considering there are a few model records saved among the list of invalid records that can only be updated via a controller. – L457 Jan 11 '21 at 21:01
  • Are you 100% sure `save_with_version` is not used somewhere in your codebase? https://github.com/paper-trail-gem/paper_trail/blob/646d7e2c5269ca0a487c8abbc1787db1875bd490/lib/paper_trail/record_trail.rb#L203 – Greg Jan 11 '21 at 22:08
  • 1
    It sounds like you have records being updated in a background job or manually in console, outside of a controller request. You can update `Papertrail.whodunnit` to save where the command is coming from if you're really stuck. – Kelsey Hannan Jan 12 '21 at 05:19

1 Answers1

1

Adding a source_location and command field to your papertrail versions table will help you:

  1. track what command is causing the nil whodunnit changes.
  2. Ensure you record correctly changes initiated by a rake task or rails console.

To do this, create a migration to add the source_location and command fields to your Versions table:


class AddSourceLocationAndCommandToVersions < ActiveRecord::Migration
  def change
    add_column :versions, :source_location, :text
    add_column :versions, :command, :text
  end
end

I have the following set up in my papertrail.rb. I am using the JSON serializer but these changes may work with the normal serializer. The code after the serializer declaration is what is of interest to you:

require "paper_trail/frameworks/rails"
require "paper_trail"

# the following line is required for PaperTrail >= 4.0.0 with Rails
PaperTrail::Rails::Engine.eager_load!

PaperTrail.config.enabled = true
PaperTrail.serializer = PaperTrail::Serializers::JSON

# Store some metadata about where the change came from, even for rake tasks, etc.
# See: https://github.com/paper-trail-gem/paper_trail/wiki/Setting-whodunnit-in-the-rails-console
def PaperTrail.set_global_metadata
  request.controller_info ||= {}
  request.controller_info[:command] ||= "#{File.basename($PROGRAM_NAME)} #{ARGV.join ' '} (#{$PID})"
  request.controller_info[:source_location] = caller.find { |line|
    line.starts_with? Rails.root.to_s and
   !line.starts_with? __FILE__
  }
end

# Defer evaluation in case we're using spring loader (otherwise it would be something like "spring app    | app | started 13 secs ago | development")
# There's no way to set up deferred evaluation of PaperTrail.request.controller_info from here like
# we can with whodunnit, so abuse that property of PaperTrail.request.whodunnit to set other
# metadata. Reserve the whodunnit field for storing the actual name or id of the human who made the
# change.
PaperTrail.request.whodunnit = ->() {
  PaperTrail.set_global_metadata
  if Rails.const_defined?("Console") || $rails_rake_task
    "#{`whoami`.strip}: console"
  else
    "#{`whoami`.strip}: #{File.basename($PROGRAM_NAME)} #{ARGV.join ' '}"
  end
}

Rails.application.configure do
  console do
    PaperTrail.request.controller_info = { command: "rails console" }
    PaperTrail.request.whodunnit = ->() {
      PaperTrail.set_global_metadata

      @paper_trail_whodunnit ||= (
        if Rails.const_defined?("Console") || $rails_rake_task
          "#{`whoami`.strip}: console"
        else
          "#{`whoami`.strip}: #{File.basename($PROGRAM_NAME)} #{ARGV.join ' '}"
        end
      )
    }
  end
end

Note that in any place where record creation occurs outside of a request, you can manually set the whodunnit value to something specific if you don't want it blank. E.g. in my seed file I do the following:


class SeedCreator
  def initialize
    PaperTrail.request.whodunnit = ->() {
      PaperTrail.set_global_metadata # save source_location & command
      "seed" # set whodunnit value to "seed"
    }
    create_campaign
    create_users
    # more seeding of models....
  end
end

In addition to improving the quality of your Papertrail table (knowing which command triggered a record update), this config should help you identify where phantom whodunnits are being saved.

Kelsey Hannan
  • 2,857
  • 2
  • 30
  • 46