module ActiveRecord 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| child_class = reflect_on_association(association).klass counter_name = child_class.reflect_on_association(self.name.downcase.to_sym).counter_cache_column connection.update("UPDATE #{quoted_table_name} SET #{connection.quote_column_name(counter_name)} = #{object.send(association).count} WHERE #{connection.quote_column_name(primary_key)} = #{quote_value(object.id)}", "#{name} UPDATE") end 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 = comment_count - 1, # # action_count = action_count + 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 = comment_count + 1, # # WHERE id IN (10, 15) def update_counters(id, counters) updates = counters.inject([]) { |list, (counter_name, increment)| sign = increment < 0 ? "-" : "+" list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}" }.join(", ") if id.is_a?(Array) ids_list = id.map {|i| quote_value(i)}.join(', ') condition = "IN (#{ids_list})" else condition = "= #{quote_value(id)}" end update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}") 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) end end end