子レコードにbelongs_to :parent, touch: true
などと宣言した場合、親レコードにaccepts_nested_attributes_for :children
と宣言した上で親レコードを更新すると、子レコードの数だけUPDATE文が実行される動きになる。本来1回で済むはずのUPDATEの実行がN-1回分余計に実行されるわけで、これをひとまとめにしてくれるGemがactiverecord-delay_touchingなのです。
内部の動き
DelayTouchingという名前の通り、ActiveRecord::Base#touchをオーバーライドしてActiveRecord::DelayTouching::Stateというクラスの@record
というインスタンス変数にtouch対象のレコードを保存しておいて、
# https://github.com/godaddy/activerecord-delay_touching/blob/master/lib/activerecord/delay_touching.rb#L9 # Override ActiveRecord::Base#touch. def touch(name = nil) if self.class.delay_touching? && !try(:no_touching?) DelayTouching.add_record(self, name) true else super end end
レコードの保存時に一気にUPDATEする、という動きをしています。
# https://github.com/godaddy/activerecord-delay_touching/blob/master/lib/activerecord/delay_touching.rb#L96 klass.unscoped.where(klass.primary_key => records).update_all(changes)
この複数レコードを保持している部分はスレッドセーフなのか?という点については、
# https://github.com/godaddy/activerecord-delay_touching/blob/master/lib/activerecord/delay_touching.rb#L40 def self.state Thread.current[:delay_touching_state] ||= State.new end
という形で現在のスレッドにインスタンスを保持するようにしているので、大丈夫そうです。このコードが良い例ですが、クラス変数で情報を保持するというのはグローバル変数を利用しているのに等しいので、注意が必要ですね(参考:Jose Valim,Rubyにおける並行プログラミングのためのいくつかのアイデアを提案。~ RubyKaigi 2013 基調講演 2日目)。
Rails Best PracticesのFetch current user in modelsでThread.currentに情報を保持しているのも、これと同じ理由です。
ただ、いろんなクラスでThread.currentに情報を突っ込んでいると、キーが被った時の動作が怖いなーと思うわけですが。。そのあたり、ご利用は計画的に。