sql ruby 54 lines · 4 tabs

Use `touch_all` for Efficient “Bump Updated At”

Shared by codesnips Jan 2026
4 tabs
-- Product.active.touch_all(:cache_synced_at) for catalog 42
UPDATE "products"
SET "updated_at" = '2024-05-01 12:00:00.123456',
    "cache_synced_at" = '2024-05-01 12:00:00.123456'
WHERE "products"."catalog_id" = 42
  AND "products"."status" = 'active';
-- one statement, no per-row callbacks, cache_version bumps for every row
4 files · sql, ruby Explain with highlit

The touch_all method on an ActiveRecord relation issues a single UPDATE that sets updated_at (and optionally other timestamp columns) across every matching row, instead of loading records and calling touch one by one. This matters because per-record touching triggers N callbacks, N queries, and N cache-key regenerations, which is disastrous when a parent change should invalidate thousands of children. The trade-off is deliberate: touch_all skips callbacks, validations, and updated_at on associated records, so it is only appropriate when the goal is purely to bump timestamps for cache busting or staleness tracking, not to run domain logic.

In Catalog model, the bump_products_cache! method scopes to products.active and calls touch_all, passing an extra :cache_synced_at column so both the standard updated_at and a domain-specific timestamp move together in one statement. Because Rails cache keys derive from updated_at via cache_version, a single touch invalidates every fragment cache and Russian-doll cache tied to those products.

The RepriceProductsJob shows the realistic trigger: after a bulk price recalculation with update_all (which does not touch timestamps), the job explicitly calls bump_products_cache! so views and API ETags recompute. Using update_all plus touch_all keeps the whole operation to two round-trips regardless of catalog size, and find_each is intentionally avoided here since no per-row Ruby work is needed.

In CatalogsController, enqueue_reprice fires the job and responds with 202 Accepted, keeping the request fast while the heavy write happens off the request cycle. The emitted UPDATE tab shows the actual SQL Rails generates: one UPDATE ... SET updated_at = ?, cache_synced_at = ? WHERE ..., confirming there is no per-row overhead.

A key pitfall is that touch_all will not cascade belongs_to ... touch: true associations, so a parent whose own caches depend on children still needs its own touch. It also bypasses optimistic locking, so records under heavy concurrent edits may lose a lock_version bump. When the intent is fast, callback-free timestamp invalidation across a set, touch_all is the right tool.


Related snips

Share this code

Here's the card — post it anywhere.

Use `touch_all` for Efficient “Bump Updated At” — share card
Link copied