railsのtouchオプションの流れを追ってみた！

# はじめに
業務で、子レコードの作成、更新時に、親の更新日時だけを更新したいことがあってActiveRecordの関連付けで、touchオプションを使えば簡単にできること知ったのでtouchの処理をChatGPTに処理解説してもらった！
どうやって使えばいいかは、今回省略しますのでRailsガイドを見てください。

----------------



```Ruby
def before_committed!
 touch_deferred_attributes if has_defer_touch_attrs? && persisted?
 super
end

```

1. `def before_committed!`:
   - `before_committed!`メソッドを定義します。
   - これは、データベースへのコミットが行われる前に呼び出されるコールバックメソッドです。

2. `touch_deferred_attributes if has_defer_touch_attrs? && persisted?`:
   - もし遅延タッチの属性があり（`has_defer_touch_attrs?`がtrue）、かレコードがデータベースに既に存在している場合（`persisted?`がtrue）に、`touch_deferred_attributes`メソッドを呼び出します。

3. `super`:
   - 親クラス（この場合は同じメソッド名の親モジュール内のメソッド）を呼び出します。

:::note info 
補足
:::
遅延タッチの属性とは？
>遅延タッチ（Deferred Touch）は、ActiveRecordモデルで親レコードの更新日時を更新する際に、実際に更新を行うのではなく、更新が必要な属性を一時的に記憶しておく機能を指します。
具体的には、親レコードの更新が行われる際に、関連する子レコードの更新も必要である場合に、子レコードの更新を遅延させておき、親レコードがコミット（保存）されるタイミングで子レコードを実際に更新するための属性のリストを保持する仕組みです。




```Ruby
    def touch_later(*names) # :nodoc:
      _raise_record_not_touched_error unless persisted?

      @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
      @_defer_touch_attrs |= names.map! do |name|
        name = name.to_s
        self.class.attribute_aliases[name] || name
      end unless names.empty?

      @_touch_time = current_time_from_proper_timezone

      surreptitiously_touch @_defer_touch_attrs
      add_to_transaction
      @_new_record_before_last_commit ||= false

      # touch the parents as we are not calling the after_save callbacks
      self.class.reflect_on_all_associations.each do |r|
        if touch = r.options[:touch]
          if r.macro == :belongs_to
            ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch)
          elsif r.macro == :has_one
            ActiveRecord::Associations::Builder::HasOne.touch_record(self, r.name, touch)
          end
        end
      end
    end
```


6. `def touch_later(*names)`:
   - `touch_later`メソッドを定義します。
   - このメソッドは、遅延してタイムスタンプを更新するためのものです。

7. `_raise_record_not_touched_error unless persisted?`:
   - もしレコードが存在していない場合は、エラーを発生させます。

8. `@_defer_touch_attrs ||= timestamp_attributes_for_update_in_model`:
   - `@_defer_touch_attrs`が未定義の場合、`timestamp_attributes_for_update_in_model`の結果を代入します。

9. `@_defer_touch_attrs |= names.map! do |name| ... end unless names.empty?`:
   - `names`が空でない場合、`names`配列内の属性名を処理して、`@_defer_touch_attrs`に追加します。

10. `@_touch_time = current_time_from_proper_timezone`:
    - 現在の時刻を適切なタイムゾーンで取得し、`@_touch_time`に代入します。

11. `surreptitiously_touch @_defer_touch_attrs`:
    - `surreptitiously_touch`メソッドを呼び出し、遅延タッチの属性を更新します。

12. `add_to_transaction`:
    - トランザクションにレコードを追加します。

13. `@_new_record_before_last_commit ||= false`:
    - インスタンス変数`@_new_record_before_last_commit`が未定義の場合、`false`を代入します。

14. `self.class.reflect_on_all_associations.each do |r| ... end`:
    - モデルの関連（アソシエーション）を反映し、それぞれに対して処理を行います。

15. `if touch = r.options[:touch]`:
    - 関連に`:touch`オプションが指定されていれば、その内容を取得します。

16. `elsif r.macro == :belongs_to`:
    - 関連がbelongs_toの場合の処理を行います。

17. `elsif r.macro == :has_one`:
    - 関連がhas_oneの場合の処理を行います。


```Ruby
    def touch(*names, time: nil) # :nodoc:
      if has_defer_touch_attrs?
        names |= @_defer_touch_attrs
        super(*names, time: time)
        @_defer_touch_attrs, @_touch_time = nil, nil
      else
        super
      end
    end
```

1. `def touch(*names, time: nil) # :nodoc:`
   - `touch`メソッドの定義を開始します。このメソッドは、親レコードの更新日時を更新するためのものであり、引数として任意の属性名（`names`）および`time`を受け取ります。

2. `if has_defer_touch_attrs?`
   - `has_defer_touch_attrs?`メソッドがtrueを返すかどうかをチェックします。これは、親レコードの更新日時を遅延させる属性があるかどうかを確認するメソッドです。

3. `names |= @_defer_touch_attrs`
   - 遅延させる属性のリスト（`@_defer_touch_attrs`）を引数の`names`に追加します。`names`に`@_defer_touch_attrs`を追加しています。

4. `super(*names, time: time)`
   - 親クラスの同名メソッドを呼び出します。`super`キーワードは、親クラスの同名のメソッドを呼び出すために使用されます。ここでは、親クラスの`touch`メソッドを呼び出しています。`*names`は可変長引数であり、`names`の要素をメソッドの引数として渡します。

5. `@_defer_touch_attrs, @_touch_time = nil, nil`
   - `@_defer_touch_attrs`および`@_touch_time`を`nil`に設定します。これにより、遅延させる属性のリストとタイムスタンプがリセットされます。

6. `else`
   - もし`has_defer_touch_attrs?`がfalseの場合、つまり遅延させる属性がない場合に実行される処理を示します。

7. `super`
   - 親クラスの同名メソッドを呼び出します。`super`キーワードを使用することで、親クラスの同名のメソッドが呼び出されます。ここでは、親クラスの`touch`メソッドが呼び出されます。




参考

https://railsguides.jp/association_basics.html#belongs-to%E3%81%AE%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3-touch

https://github.com/rails/rails/blob/main/activerecord/lib/active_record/touch_later.rb