ホワイトヘルスケア - テックブログ

ホワイトヘルスケアは、日本のヘルスケア領域における社会課題に正面から向き合い、現実世界を生きる人々の不安や痛みといった見過ごされるべきでない問題に対して本質的な解決に取り組むことで、持続的な医療システム・社会の実現を目指します。

ActiveRecordのerrorsにaddしてもsaveに成功するとaddした内容がなくなる

概要

  • ActiveRecordを継承したmodelには、errorsで任意にエラーを追加できる
  • 追加したエラーは、savevalidateに成功すると、errorsから削除される

はじめに

ホワイトヘルスケア エンジニアの林です。

本記事は、ActiveRecord を使っていた際に、errorsに追加したエラーが意図せず消えてしまった時の挙動を調べた際の話になります。

発生した現象と調査について

事の始まり

1 対多の関係のテーブルに対し、ある機能を作成していた時の話です。

テーブルをざっくり例えると以下のようなイメージのものでした。

  • userが複数のarticlesを所有している
  • articlesは、公開/非公開のstatus、記事のtext、記事の文字数text_countをフィールドとして持つ
  • userは、公開記事の全文字列の合計数であるpublished_text_countをフィールドとして持つ

図として表現するとこのような感じ。

erDiagram
users ||--|{ articles: ""

users {
  integer published_text_count
}

articles {
  enum status
  string text
  integer text_count
}

コードで model を表現するとこのような感じ。

class User < ApplicationRecord
  has_many :articles
end

class Article < ApplicationRecord
  belongs_to :store_invoice
  enum :status, {
        DRAFT: 'draft',             # 下書き
        PUBLISH: 'publish',         # 公開
  }
end

発生した問題

作成していた機能は、以下のようなものでした。

  1. userを指定し、articlesを更新する
    • articleDRAFTだった場合、PUBLISHに変更する
    • articlePUBLISHだった場合、変更できなかったArticle.idのリストをエラーとして表示する
  2. articlePUBLISHであるtextの合計値であるpublished_text_countを更新する

上記をdraft_to_publishとして以下のように実装しました。

class User < ApplicationRecord
  has_many :articles

  def draft_to_publish
    # 元々PUBLISHだったものは、idをエラーとして保持
    errors.add(:base, articles.PUBLISH.ids)

    # DRAFTはPUBLISHに変更
    articles.DRAFT.update(status: :PUBLISH)

    # `published_text_count`の更新
    published_text_count = articles.PUBLISH.sum(:text_count)

    # フィールド値の更新
    save
  end
end


# 想定していた使い方

# 対象のユーザでmethodをcall
user = User.first
user.draft_to_publish

# PUBLISHのarticlesがあれば、このraiseでcall側に通知することを期待してた
raise StandardError(user.errors.full_messages) if user.errors.empty?

しかし、PUBLISHarticlesが存在するuserであっても、エラーがraiseされることがなかったため、調査することにしました。

原因の調査

結論から言うと、save(正確にはvalid?)でerrorsがクリアされている、ということがわかりました。

該当箇所:https://github.com/rails/rails/blob/v7.1.2/activemodel/lib/active_model/validations.rb#L363-L369

明示された文章を探し出せなかったのですが、rails の思想としては、「あるタイミングでエラーが発生しても、その後の処理で保存できる(検証 OK になった)なら、値の修正などでエラーが解消されたとみなす」ということなのだろうと理解しました。

まとめ / 感想

一般的な(?)使い方を考えると、「新規に validation に成功したら errors がクリアされる、は確かにそうだな〜」と思いました。が、反面、自分の認識していないところでデータが書き換わるのは想定しておらずだいぶ困惑しました。

Framework を使う場合、どういった使い方を想定されて作られているのか、という点を意識して使う必要があることを再認識させられる出来事でした。

採用情報

Ruby on Railsアプリ開発をしているホワイトヘルスケアが気になった方は、ぜひお気軽にお問い合わせください!

採用情報詳細

hrmos.co