engineers_blogこんにちは。Railsエンジニアの横山です。私がRailsで開発を行うようになって2年弱になりますが、日々、知らない事・分からない事・理解の不十分な事があり勉強の毎日です。今回は、そのなかでも、理解が不十分であった故にActiveRecordのコールバックでハマってしまったお話をしようかと思います。)を行う際に、自動で呼び出されるメソッドがあります。それがコールバックと呼ばれる処理で、以下のような種類があります。after_findafter_initializebefore_validationafter_validationbefore_saveafter_savebefore_createafter_createbefore_updateafter_updateafter_commitafter_rollbackafter_touch今回はこの中でもafter_saveのお話になります。新規追加時と更新時で挙動が違う?やりたかった処理はオブジェクトの更新後、自身と子モデルの状態をチェックして、条件に一致したら自身の情報を一部変更する、というものです。子モデルの更新をafter_saveで行っていたためbefore_validation、after_validation、before_saveなどを使えなかった、という事情もありました。(今回の説明では簡略化のため、子モデルの処理は省略)以下のコマンドでarticleモデルを作成します。text:textarticleのtextに”hogehoge”という文字が含まれていたら”xxxxx”に置き換えて保存するという処理を行う事にします。当初は単純に「after_saveの中に混ぜ込めばいいだろ」と思っていたので、以下のように記述しました。endend賢明な皆様なら一発かと思いますが、上記の記載方法だと、新規作成の処理ではうまくいきますが、更新処理ではうまく機能しない時があります。手順1.新規にtext:ああhogehogeを登録=>これは普通に成功し、「ああxxxxx」になるはず2.追加したarticleのtext:ああxxxxxに対して「ああhogehoge」と再登録=>これは失敗。「ああhogehoge」で登録されてしまいます。これ、なんで駄目なのか良く分からないのでRailsの中まで追ってみました。コールバックのタイミングの問題かと思いきや、どうもupdate_attributes!とafter_saveの相性?の問題のようでした。changed_attributes.include?(attr)endwrite_attributeの中で実際に差し替える処理を行い、attribute_changed?の中で、その差し替え必要性の判定を行っています。上記のケースだと、最初のsave処理で@changed_attributes内に修正するデータが格納されます。その後、after_saveでupdate_attributes!が呼ばれ、再度、ここの判定処理を行いますが一回目のsaveで登録した@changed_attributesの情報がそのままであるため、2回目のsaveでは「さっき更新しただろ?」と判定されてしまい、after_saveで行ったupdate_attributes!が無効化されてしまう、というオチでした。@changed_attributesの情報が初期化されるので、こっちを使うようにしています。changed_attributes.include?(attr)endで、Hashのkeyの有無だけで更新の必要性を判定するのではなく、中身もちゃんと見るようにしても良いかもしれませんね。requestも検討してみます。