From 975fa15b47e4ef47a3b46f0e946860a076167149 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 28 May 2018 00:06:02 +0900 Subject: Extract `Relation#update_counters` for internal use The target object for counter cache is not always determined by the primary key value on the model. I'd like to extract `update_couters` onto the `Relation` for the internal use. --- .../associations/belongs_to_association.rb | 11 ++++------- .../associations/builder/belongs_to.rb | 10 ++++------ activerecord/lib/active_record/counter_cache.rb | 22 +--------------------- activerecord/lib/active_record/relation.rb | 18 ++++++++++++++++++ 4 files changed, 27 insertions(+), 34 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 08f450278d..3d4ad1dd5b 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -67,7 +67,7 @@ module ActiveRecord if target && !stale_target? target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch]) else - klass.update_counters(target_id, reflection.counter_cache_column => by, touch: reflection.options[:touch]) + counter_cache_target.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch]) end end end @@ -112,12 +112,9 @@ module ActiveRecord inverse && inverse.has_one? end - def target_id - if options[:primary_key] - owner.send(reflection.name).try(:id) - else - owner._read_attribute(reflection.foreign_key) - end + def counter_cache_target + primary_key = reflection.association_primary_key(klass) + klass.unscoped.where!(primary_key => owner._read_attribute(reflection.foreign_key)) end def stale_state diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 4b6cb76081..b5fb0092d7 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -48,14 +48,12 @@ module ActiveRecord::Associations::Builder # :nodoc: foreign_key_was = attribute_before_last_save foreign_key foreign_key = attribute_in_database foreign_key - if foreign_key && model.respond_to?(:increment_counter) - foreign_key = counter_cache_target(reflection, model, foreign_key) - model.increment_counter(cache_column, foreign_key) + if foreign_key && model < ActiveRecord::Base + counter_cache_target(reflection, model, foreign_key).update_counters(cache_column => 1) end - if foreign_key_was && model_was.respond_to?(:decrement_counter) - foreign_key_was = counter_cache_target(reflection, model_was, foreign_key_was) - model_was.decrement_counter(cache_column, foreign_key_was) + if foreign_key_was && model_was < ActiveRecord::Base + counter_cache_target(reflection, model_was, foreign_key_was).update_counters(cache_column => -1) end end end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 0d8748d7e6..c7f0077a76 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -102,27 +102,7 @@ module ActiveRecord # # `updated_at` = '2016-10-13T09:59:23-05:00' # # WHERE id IN (10, 15) def update_counters(id, counters) - touch = counters.delete(:touch) - - updates = counters.map do |counter_name, value| - operator = value < 0 ? "-" : "+" - quoted_column = connection.quote_column_name(counter_name) - "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" - end - - if touch - names = touch if touch != true - touch_updates = touch_attributes_with_time(*names) - updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty? - end - - if id.is_a?(Relation) && self == id.klass - relation = id - else - relation = unscoped.where!(primary_key => id) - end - - relation.update_all updates.join(", ") + unscoped.where!(primary_key => id).update_counters(counters) end # Increment a numeric field by one, via a direct SQL update. diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e7224658c4..e1cd5cbf29 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -369,6 +369,24 @@ module ActiveRecord @klass.connection.update stmt, "#{@klass} Update All" end + def update_counters(counters) # :nodoc: + touch = counters.delete(:touch) + + updates = counters.map do |counter_name, value| + operator = value < 0 ? "-" : "+" + quoted_column = connection.quote_column_name(counter_name) + "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}" + end + + if touch + names = touch if touch != true + touch_updates = klass.touch_attributes_with_time(*names) + updates << klass.sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty? + end + + update_all updates.join(", ") + end + # Touches all records in the current relation without instantiating records first with the updated_at/on attributes # set to the current time or the time specified. # This method can be passed attribute names and an optional time argument. -- cgit v1.2.3