Search code examples
ruby-on-railsrubygemspaper-trail-gem

Paper_trail gem abilities


I was wondering whether the following use case can be achieved using the papertrail gem? A Wikipedia-type of application with wiki pages that logged in users can change/edit and where:

  1. Moderators can undo specific changes:
    I understand papertrail allows to roll back to a previous version but what I’m asking here is somewhat different. That is, the ability to undo a specific edit/change. Suppose there have been three edits/versions to a record/wiki-page. Then if you want to undo edit 2, then changes from edit 1 and 3 should still remain. But if you would roll back to the version before edit 2, then also edit 3 would be undone, which is not what I want here.

  2. Changes made (contributions) by a user feed back into the user’s profile, which would then have an overview of the changes/contributions made by that user:
    I believe this is possible using the --with-changes option (which registers the change that was made in addition to the full dump of the changed resource) in combination with the fact that papertrail registers the user who has made a change. Am I correct in my understanding?
    In the tutorial http://samurails.com/gems/papertrail/ I read about using papertrail in combination with the gem diffy to establish what was changed exactly, but what I don’t understand is why the tutorial uses diffy when papertrail itself already offers a “diffing” functionality?

  3. To have moderators first accept a change by some users, before that change is actually implemented (i.e., before the change is actually applied):
    Can papertrail also help to achieve this functionality?


Solution

  • 1. Moderators can undo specific changes

    You can achieve this functionality using the following module:

    module Revertible
       SKIP_FIELDS = [ 'updated_at' ]
    
       def revert_to(version)
         raise 'not version of this model' unless self == version.item
         changes = version.changeset.select{ |k, v| not SKIP_FIELDS.include?(k) }.map{ |k,v| [k.to_sym, v[0]] }.to_h
         self.update_attributes(changes)
      end
    end
    

    It adds revert_to method to the model which allows moderator to undo only changes in specific edit. Pay attention to SKIP_FIELDS array, which excludes several system fields, which should not be reverted.

    We can easily test this module. Let's create a table:

    create_table :articles do |t|
      t.string :title
      t.string :body
    
      t.timestamps null: false
    end
    

    and associated model:

    class Article < ActiveRecord::Base
      include Revertible
      has_paper_trail
    end
    

    The following test case shows, that only version specific edits were reverted:

    class ArticleTest < ActiveSupport::TestCase
      test "rollback specific edit" do
        article = Article.create(title: 'My Article 1', body: 'first version')
        article.update_attributes(title: 'My Article 1', body: 'second version')
        article.update_attributes(title: 'My Article 3', body: 'third version')
    
        assert_equal 3, article.versions.count
        assert_equal 'My Article 3', article.title
        assert_equal 'third version', article.body
    
        article.revert_to article.versions[1]
    
        assert_equal 4, article.versions.count
        assert_equal 'My Article 3', article.title # title haven't changed
        assert_equal 'first version', article.body # body did change
      end
    end
    

    2.Changes made (contributions) by a user

    To turn on changes tracking add the following method to your application controller:

    class ApplicationController
      def user_for_paper_trail
        user = current_user
        return 'public' if user.blank?
        user.username
      end
    end
    

    Changes made by a specific user can be easily tracked now:

    versions = PaperTrail::Version.where(whodunnit: 'dimakura')
    version = versions.first
    version.item # => #<Article id: 1, title: "...", body: "...">
    version.event # => "create"
    version.changeset
    

    Diffy

    As to your question about diffy. You don't actually need it if the only thing you need is to get difference between two adjacent versions. But if you need to compare changes between version separated by several edits, then you do need diffy or any similar library.

    Moderator accepts changes

    I don't think it's easy to implement in a single field. You probably need to have two columns for "accepted" and "raw" data, maybe even two different models.

    I think I covered all you questions and it was helpful for you.