DEV Community

Cover image for Rails Soft Delete & Audit Logging Guide
Sulman Baig
Sulman Baig

Posted on • Originally published at sulmanweb.com

Rails Soft Delete & Audit Logging Guide

As financial applications grow in complexity, data integrity becomes paramount. Today, I'll share insights from implementing a robust soft deletion system with comprehensive audit logging in a Rails financial application. Let's explore how we can maintain data traceability while ensuring nothing is permanently lost.

The Challenge

In financial applications, simply deleting records isn't an option. We need to:

  • Track all changes to financial records
  • Maintain data relationships
  • Enable record restoration
  • Ensure audit compliance

Implementing Soft Delete with acts_as_paranoid

The acts_as_paranoid gem provides a solid foundation for soft deletion. Here's how we've implemented it in our Account model:

class Account < ApplicationRecord
  acts_as_paranoid

  belongs_to :currency, optional: true
  belongs_to :user
  has_many :transactions, dependent: :destroy

  validates :name, presence: true
  validates :balance, presence: true
end
Enter fullscreen mode Exit fullscreen mode

We added a deleted_at timestamp column which, when set, effectively "hides" the record from normal queries while preserving it in the database.

Advanced Audit Logging System

We've built a comprehensive audit logging system that tracks every change to our financial records:

class AuditLog < ApplicationRecord
  acts_as_paranoid

  belongs_to :user, optional: true

  validates :class_name, presence: true
end
Enter fullscreen mode Exit fullscreen mode

The audit logging is integrated into our service layer using a base service class:

class ApplicationService
  private

  def log_event(user:, data: {})
    event_data = { 
      user: user, 
      data: data, 
      class_name: self.class.to_s 
    }.compact
    AuditLog.create(event_data)
  end
end
Enter fullscreen mode Exit fullscreen mode

Transaction Tracking Across Deletions

For financial transactions, we maintain a complete audit trail even after deletion. Here's how we handle transaction deletion:

class Transactions::DestroyService < ApplicationService
  def call
    return failure([TRANSACTION_NOT_FOUND_MESSAGE]) unless transaction
    return failure([USER_NOT_FOUND_MESSAGE]) unless user

    ActiveRecord::Base.transaction do
      transaction.destroy
      update_account_balance
      log_event(user: user, data: { transaction: transaction })
      success(TRANSACTION_DELETED_MESSAGE)
    rescue ActiveRecord::RecordInvalid => e
      failure(e.record.errors.full_messages)
    end
  end

  private

  def update_account_balance
    factor = transaction.transaction_type == 'expense' ? 1 : -1
    transaction.account.update!(
      balance: transaction.account.balance + (transaction.amount * factor)
    )
  end
end
Enter fullscreen mode Exit fullscreen mode

Maintaining Data Integrity

To ensure data integrity, we've implemented several key features:

  1. Atomic Transactions: All related operations are wrapped in database transactions:
ActiveRecord::Base.transaction do
  transaction.destroy
  update_account_balance
  log_event(user: user, data: { transaction: transaction })
end
Enter fullscreen mode Exit fullscreen mode
  1. Relationship Preservation: We maintain relationships between records even after deletion:
class User < ApplicationRecord
  acts_as_paranoid
  has_many :audit_logs, dependent: :nullify
  has_many :accounts, dependent: :destroy
  has_many :transactions, dependent: :destroy
end
Enter fullscreen mode Exit fullscreen mode
  1. Automated Cleanup: We handle old soft-deleted records with a background job:
class RemoveSoftDeletedUsersJob < ApplicationJob
  def perform
    return unless Settings.jobs.remove_soft_deleted_users.enabled

    User.deleted_before_time(eval(Settings.jobs.remove_soft_deleted_users.time).ago)
        .each(&:destroy_fully!)
  end
end
Enter fullscreen mode Exit fullscreen mode

Real-world Benefits

This implementation has provided several advantages:

  1. Regulatory Compliance: Complete audit trails for financial transactions
  2. Data Recovery: Easy restoration of accidentally deleted records
  3. Performance: Minimal impact on database performance while maintaining data integrity
  4. Maintenance: Automated cleanup of old soft-deleted records

Learning Outcomes

Building this system taught me several valuable lessons:

  1. Always consider the broader implications of data deletion in financial systems
  2. Design for auditability from the ground up
  3. Balance between data retention and system performance
  4. Importance of atomic operations in maintaining data consistency

Best Practices

When implementing a similar system, consider these recommendations:

  1. Always use database transactions for related operations
  2. Log both the action and the state of the data
  3. Implement cleanup strategies for old soft-deleted records
  4. Maintain relationships between related records even after deletion
  5. Design the audit system to be queryable and maintainable

This implementation has proven robust in production, handling millions of financial transactions while maintaining complete traceability and data integrity. The combination of soft deletion and comprehensive audit logging provides the security and transparency essential for financial applications.

Remember, in financial applications, it's not just about storing data—it's about maintaining a verifiable history of every change while ensuring data integrity at every step.


Happy Coding!


Originally published at https://sulmanweb.com.

Top comments (0)