diff options
Diffstat (limited to 'activerecord/lib/active_record/counter_cache.rb')
-rw-r--r-- | activerecord/lib/active_record/counter_cache.rb | 200 |
1 files changed, 102 insertions, 98 deletions
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index b163ef3c12..b27a19f89a 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -1,111 +1,115 @@ module ActiveRecord # = Active Record Counter Cache module CounterCache - # Resets one or more counter caches to their correct value using an SQL - # count query. This is useful when adding new counter caches, or if the - # counter has been corrupted or modified directly by SQL. - # - # ==== Parameters - # - # * +id+ - The id of the object you wish to reset a counter on. - # * +counters+ - One or more counter names to reset - # - # ==== Examples - # - # # For Post with id #1 records reset the comments_count - # Post.reset_counters(1, :comments) - def reset_counters(id, *counters) - object = find(id) - counters.each do |association| - has_many_association = reflect_on_association(association.to_sym) + extend ActiveSupport::Concern - foreign_key = has_many_association.foreign_key.to_s - child_class = has_many_association.klass - belongs_to = child_class.reflect_on_all_associations(:belongs_to) - reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key } - counter_name = reflection.counter_cache_column + module ClassMethods + # Resets one or more counter caches to their correct value using an SQL + # count query. This is useful when adding new counter caches, or if the + # counter has been corrupted or modified directly by SQL. + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to reset a counter on. + # * +counters+ - One or more counter names to reset + # + # ==== Examples + # + # # For Post with id #1 records reset the comments_count + # Post.reset_counters(1, :comments) + def reset_counters(id, *counters) + object = find(id) + counters.each do |association| + has_many_association = reflect_on_association(association.to_sym) - stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ - arel_table[counter_name] => object.send(association).count - }) - connection.update stmt - end - return true - end + foreign_key = has_many_association.foreign_key.to_s + child_class = has_many_association.klass + belongs_to = child_class.reflect_on_all_associations(:belongs_to) + reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key } + counter_name = reflection.counter_cache_column - # A generic "counter updater" implementation, intended primarily to be - # used by increment_counter and decrement_counter, but which may also - # be useful on its own. It simply does a direct SQL update for the record - # with the given ID, altering the given hash of counters by the amount - # given by the corresponding value: - # - # ==== Parameters - # - # * +id+ - The id of the object you wish to update a counter on or an Array of ids. - # * +counters+ - An Array of Hashes containing the names of the fields - # to update as keys and the amount to update the field by as values. - # - # ==== Examples - # - # # For the Post with id of 5, decrement the comment_count by 1, and - # # increment the action_count by 1 - # Post.update_counters 5, :comment_count => -1, :action_count => 1 - # # Executes the following SQL: - # # UPDATE posts - # # SET comment_count = COALESCE(comment_count, 0) - 1, - # # action_count = COALESCE(action_count, 0) + 1 - # # WHERE id = 5 - # - # # For the Posts with id of 10 and 15, increment the comment_count by 1 - # Post.update_counters [10, 15], :comment_count => 1 - # # Executes the following SQL: - # # UPDATE posts - # # SET comment_count = COALESCE(comment_count, 0) + 1 - # # WHERE id IN (10, 15) - def update_counters(id, counters) - 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}" + stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ + arel_table[counter_name] => object.send(association).count + }) + connection.update stmt + end + return true end - where(primary_key => id).update_all updates.join(', ') - end + # A generic "counter updater" implementation, intended primarily to be + # used by increment_counter and decrement_counter, but which may also + # be useful on its own. It simply does a direct SQL update for the record + # with the given ID, altering the given hash of counters by the amount + # given by the corresponding value: + # + # ==== Parameters + # + # * +id+ - The id of the object you wish to update a counter on or an Array of ids. + # * +counters+ - An Array of Hashes containing the names of the fields + # to update as keys and the amount to update the field by as values. + # + # ==== Examples + # + # # For the Post with id of 5, decrement the comment_count by 1, and + # # increment the action_count by 1 + # Post.update_counters 5, :comment_count => -1, :action_count => 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) - 1, + # # action_count = COALESCE(action_count, 0) + 1 + # # WHERE id = 5 + # + # # For the Posts with id of 10 and 15, increment the comment_count by 1 + # Post.update_counters [10, 15], :comment_count => 1 + # # Executes the following SQL: + # # UPDATE posts + # # SET comment_count = COALESCE(comment_count, 0) + 1 + # # WHERE id IN (10, 15) + def update_counters(id, counters) + 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 - # Increment a number field by one, usually representing a count. - # - # This is used for caching aggregate values, so that they don't need to be computed every time. - # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is - # shown it would have to run an SQL query to find how many posts and comments there are. - # - # ==== Parameters - # - # * +counter_name+ - The name of the field that should be incremented. - # * +id+ - The id of the object that should be incremented. - # - # ==== Examples - # - # # Increment the post_count column for the record with an id of 5 - # DiscussionBoard.increment_counter(:post_count, 5) - def increment_counter(counter_name, id) - update_counters(id, counter_name => 1) - end + where(primary_key => id).update_all updates.join(', ') + end + + # Increment a number field by one, usually representing a count. + # + # This is used for caching aggregate values, so that they don't need to be computed every time. + # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is + # shown it would have to run an SQL query to find how many posts and comments there are. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be incremented. + # * +id+ - The id of the object that should be incremented. + # + # ==== Examples + # + # # Increment the post_count column for the record with an id of 5 + # DiscussionBoard.increment_counter(:post_count, 5) + def increment_counter(counter_name, id) + update_counters(id, counter_name => 1) + end - # Decrement a number field by one, usually representing a count. - # - # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. - # - # ==== Parameters - # - # * +counter_name+ - The name of the field that should be decremented. - # * +id+ - The id of the object that should be decremented. - # - # ==== Examples - # - # # Decrement the post_count column for the record with an id of 5 - # DiscussionBoard.decrement_counter(:post_count, 5) - def decrement_counter(counter_name, id) - update_counters(id, counter_name => -1) + # Decrement a number field by one, usually representing a count. + # + # This works the same as increment_counter but reduces the column value by 1 instead of increasing it. + # + # ==== Parameters + # + # * +counter_name+ - The name of the field that should be decremented. + # * +id+ - The id of the object that should be decremented. + # + # ==== Examples + # + # # Decrement the post_count column for the record with an id of 5 + # DiscussionBoard.decrement_counter(:post_count, 5) + def decrement_counter(counter_name, id) + update_counters(id, counter_name => -1) + end end end end |