Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the right way to list all paper_trail versions including associations?

Question regarding the paper_trail gem.

When only associations change, a version record won't be created for the main model. So what's the right way to list all versions for a certain record including its associations?

Should I query something like this? (The bad point is this SQL query might be long and low performance.)

f = "(item_type = 'Place' AND item_id = ?) OR (item_type = 'PlaceName' AND item_id IN (?))"
PaperTrail::Version.where(f, @place.id, @place.names.map { |n| n.id }) 

Or should I create a version record when only associations changed? I think @DavidHam tried the same thing and asked a similar question but nobody has answered it yet.

like image 869
Tom Avatar asked Sep 25 '22 22:09

Tom


2 Answers

So, I sort of found a way to do this, but it's not exactly pretty and it doesn't create a new version everytime an association is changed. It does, however, give you an efficient way to retrieve the versions chronologically so you can see what the version looked like before/after association changes.

First, I retrieve all the ids for for the asscoiation versions given the id of that model:

def associations_version_ids(item_id=nil)
  if !item_id.nil?
    ids = PaperTrail::VersionAssociation.where(foreign_key_id: item_id, foreign_key_name: 'item_id').select(:version_id)

    return ids
  else
    ids = PaperTrail::VersionAssociation.where(foreign_key_name: 'item_id').select(:version_id)

    return ids
  end
end

Then I get all versions together using the VersionAssociation ids from this function. It will return a chronological array of PaperTrail::Version's. So the information is useful for an activity log, etc. And it's pretty simple to piece back together a version and its associations this way:

def all_versions
  if !@item_id.nil?
    association_version_ids = associations_version_ids(@item_id)
    all_versions = PaperTrail::Version
                      .where("(item_type = ? AND item_id = ?) OR id IN (?)", 'Item', @item_id, association_version_ids)
                      .where("object_changes IS NOT NULL")
                      .order(created_at: :desc)

    return all_versions
  else
    assocation_ids = associations_version_ids
    all_versions = PaperTrail::Version
                      .where("item_type = ? OR id IN (?)", 'Item', association_ids)
                      .where("object_changes IS NOT NULL")
                      .order(created_at: :desc)

    return all_versions
  end
end

Again, not a perfect answer since there isn't a new version everytime there's a change, but it's manageable.

like image 81
JohnSchaum Avatar answered Oct 11 '22 06:10

JohnSchaum


This is more of an approach than a specific answer, but here goes.

In my case, I needed a version history such that any time anyone changed a Child, they also changed a flag on the `Parent'. But I needed a way to show an audit trail that would show the initial values for all the children, and an audit line for the parent whenever anyone changed a child.

So, much simplified, it's like this:

class Parent < ActiveRecord::Base
  has_paper_trail
  has_many :children
end

class Child < ActiveRecord::Base
  has_paper_trail
  belongs_to :parent
end

So, whenever there's a change on a Child we need to create a version on the Parent.

First, try changing Child as follows:

class Child < ActiveRecord::Base
  has_paper_trail
  belongs_to :parent, touch: true
end

This should (should! have not tested) create a timestamp on the Parent whenever the Child changes.

Then, to get the state of the :children at each version of Parent, you search the Child's versions for the one where the transaction_id matches the Parent.

# loop through the parent versions
@parent.versions.each do |version|

  @parent.children.versions.each do |child|
    # Then loop through the children and get the version of the Child where the transaction_id matches the given Parent version
    child_version = child.versions.find_by transaction_id: version.transaction_id

    if child_version # it will only exist if this Child changed in this Parent's version
      # do stuff with the child's version
    end

This worked in my situation, hope something in here is useful for you.

like image 40
David Ham Avatar answered Oct 11 '22 04:10

David Ham