0

I'm encountering an issue with PaperTrail in my Rails application where I'm trying to store both controller-level and model-level metadata in the versions table, but only the controller-level metadata (app_id and org_id) is being stored. Here's how I've configured it:

class Table < ApplicationRecord
  self.table_name = 'tablename '

  has_paper_trail meta: {
    metadata: lambda { |record|
      user = record.admin_apps_orgs_user.user
      acted_user = {
        id: user.id,
        name: "#{user.given_name} #{user.family_name}",
        email: user.email
      }
      controller_metadata = PaperTrail.request.controller_info.try(:[], :metadata) || {}

      {
        app_id: controller_metadata[:app_id],
        org_id: controller_metadata[:org_id],
        acted_user: acted_user
      }
    }
  }
end`
# frozen_string_literal: true

module PapertrailInfoHelper
  extend ActiveSupport::Concern

  def info_for_paper_trail
    {
      metadata: {
        app_id: params[:app_id],
        org_id: params[:org_id]
      }
    }
  end
end
module Api
  module V1
    class MyController < ApplicationController
      include PapertrailInfoHelper

      # Controller actions

Issue:

When I update records in AdminAppsOrgsUsersRole, only the app_id and org_id from info_for_paper_trail are stored in the versions table.

The acted_user details from the lambda block in has_paper_trail metadata are not being stored. I've ensured that PaperTrail.request.controller_info contains the correct :metadata values in the Rails console.

Goal:

I want to store both controller-level (app_id, org_id) and model-level (acted_user) metadata in the versions table using PaperTrail. How can I ensure that the acted_user details are correctly captured and stored alongside app_id and org_id?

1
  • My Papertrail schema is => PaperTrail::Version(id: uuid, item_type: string, item_id: string, event: string, whodunnit: string, object: json, object_changes: json, created_at: datetime, metadata: jsonb) you can see here metadata column is added and I want to store this {app_id: 1,org_id: 1, acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}} data into the metadata column. I am getting app_id and org_id from controller and acted_user data from model level.
    – Mohan Mali
    Commented Jul 9 at 4:59

1 Answer 1

1

Issue

Your current issue is that based on how the metadata is merged Here, your PapertrailInfoHelper#info_for_paper_trail overwrites the model implementation because both have the top level key metadata.

meta = {} 
controller_meta = {metadata: {app_id: 1,org_id: 1}}
model_meta = {metadata: {app_id: 1,org_id: 1, acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}}

meta.merge(model_meta)
#=> {metadata: {app_id: 1,org_id: 1, acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}}
meta.merge(controller_meta) 
#=> {metadata: {app_id: 1,org_id: 1}}
meta
#=> {metadata: {app_id: 1,org_id: 1}}

Suggested resolution

For PapertrailInfoHelper#info_for_paper_trail you should remove the metadata key resulting in:

def info_for_paper_trail
  {app_id: params[:app_id],org_id: params[:org_id]}
end

And in the Model:

has_paper_trail meta: {
    acted_user: lambda { |record|
      user = record.admin_apps_orgs_user.user
      {
        id: user.id,
        name: "#{user.given_name} #{user.family_name}",
        email: user.email
      }
    }
  }

acted_user will be the key and the result of calling the block will be the value:

New implementation is:

meta = {} 
controller_meta = {app_id: 1,org_id: 1}
model_meta = {acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}}

meta.merge(model_meta)
#=> {acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}}
meta.merge(controller_meta) 
#=> {app_id: 1,org_id: 1, acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}}
meta
#=> {app_id: 1,org_id: 1, acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}}

UPDATE based on the comment the OP needs to store all of this data in the metadata attribute. To do so requires patching PaperTrail. Something like the following should work:

In controller:

 def info_for_paper_trail
    {
      metadata: {
        app_id: params[:app_id],
        org_id: params[:org_id]
      }
    }
  end

In model:

  has_paper_trail meta: {
    metadata: lambda { |record|
      user = record.admin_apps_orgs_user.user
      {
        acted_user: {
          id: user.id,
          name: "#{user.given_name} #{user.family_name}",
          email: user.email
        }
      }
    }
  }

In initializer:

module PaperTrailMetadataExtension
  def merge_metadata_from_controller_into(data)
    data.deep_dup.deep_merge(super)
  end
end

PaperTrail::Events::Base.prepend(PaperTrailMetadataExtension)
2
  • My Papertrail schema is => PaperTrail::Version(id: uuid, item_type: string, item_id: string, event: string, whodunnit: string, object: json, object_changes: json, created_at: datetime, metadata: jsonb) you can see here metadata column is added and I want to store this {app_id: 1,org_id: 1, acted_user: {id: 1, name: 'Mohan Mali', email: '[email protected]'}} data into the metadata column. I am getting app_id and org_id from controller and acted_user data from model level.
    – Mohan Mali
    Commented Jul 9 at 4:56
  • @MohanMali now I understand the issue. Unfortunately this is not how paper_trail works out of the box so your options are: 1) change your schema; or 2) monkey patch PaperTrail::Events::Base#merge_metadata_into to use something like deep_merge. I will provide a naive example of the second option. Commented Jul 9 at 12:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.