How to use PaperTrail for soft-deletionFebruary 28, 2014 ActiveRecord PaperTrail Ruby Ruby on Rails
A major benefit of using PaperTrail instead of a “flag-as-deleted” approach used by acts_as_paranoid and the like, is that deleted objects don’t pollute the main table. It’s not the best solution if soft deletion is all you need, but if you already use PaperTrail for versioning, you also get undeletion for free.
How undeletion with PaperTrail works
PaperTrail stores a version when a versioned object is deleted. It also provides a method,
Version#reify, that creates a model object from data stored in the version object. Add a
save to it, and you get undeletion:
Note that the object is restored with the same ID as before, which may be unwanted, in which case you can zero out the ID before saving and get an autogenerated one. But in most cases, preserving IDs is actually beneficial.
If you have more than one model that need to be undeleted, all of them have to be versioned and restored one by one. The associations would be restored automatically because IDs of the restored models are preserved.
Listing deleted objects using PaperTrail
To undelete anything, you need to get a list of deleted objects first. This can be done with a somewhat complicated Arel query:
PaperTrail::Version. where(event: 'destroy', item_type: 'MyModel'). # find all records of deletion joins("LEFT JOIN my_models ON item_id=my_models.id"). where("my_models.id IS NULL"). # avoid showing deleted objects select("distinct item_id, *").order('versions.created_at DESC') # avoid duplicates
The third line of the query is used to hide versions of objects that are already restored. The fourth line hides duplicates of objects that were deleted multiple times. Both of these can be skipped if you delete the version when restoring the object, so it makes practical sense, but I didn’t try it myself.
As this is a regular Arel scope, you can paginate it when showing to the end user.
However, if you want to filter versions, say, by owner, the query becomes more complicated because model attributes are stored in a serialized hash inside the version object. You can either filter with an
object LIKE '\n%owner_id=?%\n', or add an
owner_id attribute to the version itself.