counter_cacheにおける外部キーの不要な型変換を削除
Reflection#foreign_key が常に frozen/intern済みの String または Array を返すことを前提に、counter_cache 処理内の冗長な型変換コードが削除されました。これにより、_foreign_keys_equal? ヘルパーメソッドも不要となり、コードがシンプルになります。
背景
Reflection#foreign_key の戻り値は、Rails内部で常に frozen/interned な String、またはそれらの frozen な Array として保証されています。この保証があるため、counter_cache の処理内で行われていた .to_s や .to_sym による型強制、および _foreign_keys_equal? のような比較ヘルパーは本来不要でした。
PR本文では「Reflection#foreign_key は常に frozen/interned な String または frozen な Array of frozen/interned String を返すため、to_s や to_sym の呼び出しは必要ない」と明示されています。こうした不要な変換は、コードの読み手に「なぜ変換が必要なのか?」という疑問を生じさせ、実際の型保証を曖昧にしていました。
技術的な変更
変更は activerecord/lib/active_record/counter_cache.rb に集中しており、3箇所の冗長な型変換・比較ロジックが除去されています。
reset_counters 内の変更(.to_s の削除):
# 変更前
foreign_key = has_many_association.foreign_key.to_s
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }
# 変更後
foreign_key = has_many_association.foreign_key
reflection = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key == foreign_key && e.options[:counter_cache].present? }
foreign_key の取得時に .to_s を呼ぶ必要がなくなり、比較側の .to_s も同様に不要となっています。
destroy_row 内の変更(_foreign_keys_equal? の削除):
# 変更前
unless destroyed_by_association && _foreign_keys_equal?(destroyed_by_association.foreign_key, association.reflection.foreign_key)
association.decrement_counters
end
# 変更後
unless destroyed_by_association && destroyed_by_association.foreign_key == association.reflection.foreign_key
association.decrement_counters
end
これに伴い、以下の _foreign_keys_equal? ヘルパーメソッド全体が削除されました。
# 削除されたメソッド
def _foreign_keys_equal?(fkey1, fkey2)
fkey1 == fkey2 || Array(fkey1).map(&:to_sym) == Array(fkey2).map(&:to_sym)
end
このヘルパーは String と Symbol の両方に対応するために Array(...).map(&:to_sym) で正規化していましたが、Reflection#foreign_key の型が保証されている以上、この複雑な比較ロジックは不要です。
設計判断
型の保証をコードで表現するというアプローチが採用されました。
_foreign_keys_equal? は String と Symbol の混在を吸収する防衛的なコードでしたが、その複雑さは Reflection#foreign_key の型保証が明文化・徹底されていないことから生まれていました。今回の変更は「型が保証されているならば、その保証に依拠してシンプルに書くべき」という判断を体現しています。
defensive programmingとして型変換を残す選択肢もありますが、内部APIの型保証に依拠することでコードの意図が明確になり、将来の読み手が型の性質を把握しやすくなります。
まとめ
この変更は機能的な振る舞いを変えるものではなく、Reflection#foreign_key の型保証という内部的な事実をコードに反映させたクリーンアップです。不要な型変換と防衛的なヘルパーメソッドを取り除くことで、外部キー比較のロジックが一貫して == による直接比較に統一され、コードベースの見通しが改善されています。