Rails Soft Delete & Audit Logging Guide

Senior Software Engineer with 12 years of expertise in Ruby on Rails and Vue.js, specializing in health, e-commerce, staffing, and transport. Experienced in software development and version analysis.
Search for a command to run...

Senior Software Engineer with 12 years of expertise in Ruby on Rails and Vue.js, specializing in health, e-commerce, staffing, and transport. Experienced in software development and version analysis.
No comments yet. Be the first to comment.
Motivation After building several Shopify embedded apps, I've learned valuable lessons about what works (and what definitely doesn't) in the embedded app environment. Today, I'm sharing these insights to help you avoid common pitfalls and build bette...

As a developer working with Shopify's ecosystem, I recently built a multi-tenant SaaS application that synchronizes customer data between Shopify stores and external services. In this article, I'll share my experience and technical insights into crea...

As developers, we're always looking for ways to streamline our deployment process while maintaining security and reliability. Today, I'm excited to share my experience setting up an automated deployment pipeline for a Rails application using Dokku an...

The Challenge: Modernizing Rails Deployment When I recently needed to deploy my Rails 8 application with multiple databases, I faced a common dilemma: choosing between expensive managed solutions and complex self-managed servers. My requirements were...

The Cost-Benefit Realization As a Rails developer who recently migrated from Heroku to Dokku, I want to share my journey and the surprising benefits I discovered. This transition wasn't just about cost savings—it opened up new possibilities for my de...

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.
In financial applications, simply deleting records isn't an option. We need to:
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
We added a deleted_at timestamp column which, when set, effectively "hides" the record from normal queries while preserving it in the database.
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
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
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
To ensure data integrity, we've implemented several key features:
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
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
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
This implementation has provided several advantages:
Building this system taught me several valuable lessons:
When implementing a similar system, consider these recommendations:
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!