activerecord

Safer Deletion with dependent: :restrict_with_error

Sometimes cascading deletes are the wrong UX and the wrong ops story. Restrict deletion when children exist and provide a user-facing error. This prevents data loss accidents.

Query objects for complex database queries

Query objects encapsulate complex database queries in reusable, testable classes. I use query objects when scopes become too complex or require parameters. Query objects compose smaller scopes, handle conditionals, and apply filtering logic. They're i

Counter Cache Repair Job (Consistency Tooling)

Counter caches drift (deleted records, backfills, manual SQL). A repair job that recomputes counts safely is invaluable. It’s the kind of operational code you’re glad you wrote the first time a dashboard is wrong.

Database transactions for data consistency

Transactions ensure that multiple database operations either all succeed or all fail together, preventing partial updates that leave data in inconsistent states. Rails provides ActiveRecord::Base.transaction which wraps a block of code in a database t

Read Replica Routing for GET-Heavy Endpoints

For apps with replicas, route read-only code paths to reading role and enforce prevent_writes. This is a strong reliability move: accidental writes on replicas become exceptions instead of silent data loss.

Keep DB Connections Healthy in Long Jobs

Long-running jobs can hit stale connections. Wrap work in with_connection and consider verify! before heavy DB usage. This reduces “PG::ConnectionBad” noise during long maintenance tasks.

Transactionally Create Parent + Children with accepts_nested_attributes_for

Nested writes should be transactional: either everything is created or nothing is. Rails does this well when you keep validations coherent and avoid side effects in callbacks.

Targeted Query Caching for Expensive Endpoints

I’ve had endpoints where the same lookups get repeated across helpers and partials, and I didn’t want to pay for query caching on every request. In Scoped query cache, I wrap only the expensive report build in ActiveRecord::Base.cache, so the cache li

ActiveRecord::Relation as a Boundary (No Arrays)

Return relations from query objects, not arrays. It keeps composition possible (additional filters, pagination, eager loading) and avoids loading huge result sets accidentally.

Transaction-Safe After-Commit Hook (Avoid Ghost Jobs)

Enqueueing jobs inside a transaction can create “ghost jobs” when the transaction rolls back. Use after_commit or after_create_commit to enqueue work only after the DB commit succeeds.

Audit Trail with JSON Diff (Minimal, Useful)

Auditing isn’t just “save everything”. Capture who did it, what changed, and why. Rails gives you dirty tracking; store diffs in a JSON column. Keep it minimal to avoid ballooning storage.

Counter cache for association counts

Computing counts on associations can be expensive when done repeatedly—post.comments.count executes a SELECT COUNT(*) query every time. Counter caches solve this by maintaining a denormalized count column on the parent model that increments/decrements