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

匆匆过客 提交于 2019-12-10 21:54:32

问题


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.


回答1:


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.




回答2:


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.




回答3:


[UPDATED]

I found a better way. You need to update associations inside transaction to make this code work.

class Place < ActiveRecord::Base
  has_paper_trail
  before_update :check_update

  def check_update
    return if changed_notably?

    tracking_has_many_associations = [ ... ]
    tracking_has_has_one_associations = [ ... ]

    tracking_has_many_associations.each do |a|
      send(a).each do |r|
        if r.send(:changed_notably?) || r.marked_for_destruction?
          self.updated_at = DateTime.now
          return
        end
      end
    end
    tracking_has_one_associations.each do |a|
      r = send(a)
      if r.send(:changed_notably?) || r.marked_for_destruction?
        self.updated_at = DateTime.now
        return
      end
    end
  end
end

class Version < PaperTrail::Version
  def associated_versions
    Version.where(transaction_id: transaction_id) if transaction_id
  end
end

[Original Answer]

This is the best way I've found so far. (@JohnSchaum's answer helps me a lot, thanks!)

Before starting, I've added polymorphic_type column to the versions table.

class AddPolymorphicTypeToVersions < ActiveRecord::Migration
  def change
    add_column :versions, :polymorphic_type, :string
  end
end

And setup models like this:

# app/models/version.rb
class Version < PaperTrail::Version
  has_many :associations, class_name: 'PaperTrail::VersionAssociation'
end

# app/models/link.rb
class Link < ActiveRecord::Base
  has_paper_trail meta: { polymorphic_type: :linkable_type }
  belongs_to :linkable, polymorphic: true
end

# app/models/place.rb
class Place < ActiveRecord::Base
  has_paper_trail
  has_many :links, as: :linkable

  def all_versions
    f = '(item_type = "Place" AND item_id = ?) OR ' +
        '(foreign_key_name = "place_id" AND foreign_key_id = ?) OR ' +
        '(polymorphic_type = "Place" AND foreign_key_id = ?)'
    Version.includes(:associations).references(:associations).where(f, id, id, id)
  end
end

And we can now get versions including associations like following:

@place.all_versions.order('created_at DESC')


来源:https://stackoverflow.com/questions/34305935/whats-the-right-way-to-list-all-paper-trail-versions-including-associations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!