aboutsummaryrefslogblamecommitdiffstats
path: root/activerecord/lib/active_record/counter_cache.rb
blob: cbebded9955d80c645e5336aa500b290cbefb6d0 (plain) (tree)










































































































                                                                                                                                                                                                                                               
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