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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With