<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm" -->

---
title: The Persistence APIs That Skip the Record Lifecycle
description: Bulk and direct persistence APIs in Active Record such as update_all, delete, delete_all, update_columns, insert_all, and upsert_all bypass parts of the record...
canonical: https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The Persistence APIs That Skip the Record Lifecycle | daily.dev
og:description: Bulk and direct persistence APIs in Active Record such as update_all, delete, delete_all, update_columns, insert_all, and upsert_all bypass parts of the record...
og:url: https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm
og:image: https://api.daily.dev/og/posts/l3eb62qjm.png
og:image:alt: The Persistence APIs That Skip the Record Lifecycle
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Persistence APIs That Skip the Record Lifecycle

**[RUBYLAND](https://daily.dev/sources/rubyla)** · 12 min read · 1 upvotes · 0 comments

## Summary

Bulk and direct persistence APIs in Active Record such as update_all, delete, delete_all, update_columns, insert_all, and upsert_all bypass parts of the record lifecycle: validations, callbacks, timestamps, and in-memory synchronization. The piece walks through concrete examples showing missing audit rows, stale updated_at values, stale in-memory objects, optimistic-locking StaleObjectError surprises, and skipped normalization on bulk inserts. It closes with a repair pattern that wraps update_all and insert_all! in a transaction with explicit timestamps and audit writes, arguing developers should choose a persistence method based on which lifecycle behaviors the operation actually requires, noting that Rails 8.1 added touch: true support to update_column/update_columns (absent in 8.0 and 7.x).

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://railsrevelry.substack.com/p/persistence-apis-that-skip-the-record-lifecycle>

## Questions this post answers

### Does Rails update_all run model callbacks like after_update?

No. ActiveRecord::Relation#update_all compiles a single SQL UPDATE statement directly against the database without instantiating the affected records, so no callbacks, validations, or automatic timestamp updates run. It type-casts the hash values, increments the optimistic-locking column when applicable, executes the statement, and resets the relation, but it never touches updated_at automatically.

_daily.dev surfaces posts like this for developers debugging why Rails callbacks silently didn't fire._

### What is the difference between Rails destroy and delete methods?

destroy runs the full destroy callback chain and honors dependent association options like dependent: :destroy, loading and destroying child records before marking the receiver destroyed and frozen. delete instead issues a direct SQL DELETE for the receiver's row and then marks it destroyed and frozen, without running destroy callbacks or dependent cleanup, which can leave associated child rows orphaned or trigger a foreign key error.

_Rails developers weighing destroy versus delete for cleanup logic can track these nuances on daily.dev._

### When did Rails add the touch option to update_column and update_columns?

Rails 8.1.0 added a touch: keyword option to update_column and update_columns, allowing timestamp columns to be updated as part of that direct write. The option is unavailable in Rails 8.0 and the 7.x series, including 7.1.3.4, where calling update_columns does not update updated_at unless it is explicitly included in the passed attributes.

_Teams upgrading Rails versions can follow API changes like this one through daily.dev._

## Similar posts on daily.dev

- [ActiveRecord: Consistent delete\_all and update\_all](https://daily.dev/posts/activerecord-consistent-delete-all-and-update-all-qta6unmqw) · RUBYLAND · 0 upvotes · 0 comments
- [attribute\` is back, pluck on unsaved records, and more\!](https://daily.dev/posts/attribute-is-back-pluck-on-unsaved-records-and-more--v6axyffpq) · This Week in Rails · 0 upvotes · 0 comments
- [The challenges of soft delete](https://daily.dev/posts/the-challenges-of-soft-delete-gfrwefogi) · Hacker News · 7 upvotes · 1 comments
- [Active Record gets better every week](https://daily.dev/posts/active-record-gets-better-every-week-rturdxueb) · Rails · 4 upvotes · 0 comments
- [The Rails Way in 2026](https://daily.dev/posts/the-rails-way-in-2026-lwaqok4v6) · arkency · 0 upvotes · 0 comments

---

Tags: [#database](https://daily.dev/tags/database), [#ruby](https://daily.dev/tags/ruby), [#rails](https://daily.dev/tags/rails)

[View this post on daily.dev](https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"The Persistence APIs That Skip the Record Lifecycle","url":"https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm"},"datePublished":"2026-08-23T04:58:09.646Z","dateModified":"2026-09-14T07:18:43.439Z","description":"Bulk and direct persistence APIs in Active Record such as update_all, delete, delete_all, update_columns, insert_all, and upsert_all bypass parts of the record...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/a7044316d600af73a4e1bd23805615fc?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/a7044316d600af73a4e1bd23805615fc?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"RUBYLAND","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"RUBYLAND","logo":"https://media.daily.dev/image/upload/t_logo,f_auto/v1/logos/rubyla","url":"https://daily.dev/sources/rubyla"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":1},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"database,ruby,rails","timeRequired":"PT12M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"RUBYLAND","item":"https://daily.dev/sources/rubyla"},{"@type":"ListItem","position":3,"name":"The Persistence APIs That Skip the Record Lifecycle"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/the-persistence-apis-that-skip-the-record-lifecycle-l3eb62qjm#faq","mainEntity":[{"@type":"Question","name":"Does Rails update_all run model callbacks like after_update?","acceptedAnswer":{"@type":"Answer","text":"No. ActiveRecord::Relation#update_all compiles a single SQL UPDATE statement directly against the database without instantiating the affected records, so no callbacks, validations, or automatic timestamp updates run. It type-casts the hash values, increments the optimistic-locking column when applicable, executes the statement, and resets the relation, but it never touches updated_at automatically. daily.dev surfaces posts like this for developers debugging why Rails callbacks silently didn't fire."}},{"@type":"Question","name":"What is the difference between Rails destroy and delete methods?","acceptedAnswer":{"@type":"Answer","text":"destroy runs the full destroy callback chain and honors dependent association options like dependent: :destroy, loading and destroying child records before marking the receiver destroyed and frozen. delete instead issues a direct SQL DELETE for the receiver's row and then marks it destroyed and frozen, without running destroy callbacks or dependent cleanup, which can leave associated child rows orphaned or trigger a foreign key error. Rails developers weighing destroy versus delete for cleanup logic can track these nuances on daily.dev."}},{"@type":"Question","name":"When did Rails add the touch option to update_column and update_columns?","acceptedAnswer":{"@type":"Answer","text":"Rails 8.1.0 added a touch: keyword option to update_column and update_columns, allowing timestamp columns to be updated as part of that direct write. The option is unavailable in Rails 8.0 and the 7.x series, including 7.1.3.4, where calling update_columns does not update updated_at unless it is explicitly included in the passed attributes. Teams upgrading Rails versions can follow API changes like this one through daily.dev."}}]}
```

