mongoid/mongoid

View on GitHub
lib/mongoid/relations/counter_cache.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# encoding: utf-8
module Mongoid
  module Relations
    module CounterCache
      extend ActiveSupport::Concern

      # Reset the given counter using the .count() query from the
      # db. This method is usuful in case that a counter got
      # corrupted, or a new counter was added to the collection.
      #
      # @example Reset the given counter cache
      #   post.reset_counters(:comments)
      #
      # @param [ Symbol, Array ] One or more counter caches to reset
      #
      # @since 4.0.0
      def reset_counters(*counters)
        self.class.reset_counters(self, *counters)
      end

      module ClassMethods

        # Reset the given counter using the .count() query from the
        # db. This method is usuful in case that a counter got
        # corrupted, or a new counter was added to the collection.
        #
        # @example Reset the given counter cache
        #   Post.reset_counters('50e0edd97c71c17ea9000001', :comments)
        #
        # @param [ String ] The id of the object that will be reset.
        # @param [ Symbol, Array ] One or more counter caches to reset
        #
        # @since 3.1.0
        def reset_counters(id, *counters)
          document = id.is_a?(Document) ? id : find(id)
          counters.each do |name|
            meta = reflect_on_association(name)
            inverse = meta.klass.reflect_on_association(meta.inverse)
            counter_name = inverse.counter_cache_column_name
            document.update_attribute(counter_name, document.send(name).count)
          end
        end

        # Update the given counters by the value factor. It uses the
        # atomic $inc command.
        #
        # @example Add 5 to comments counter and remove 2 from likes
        #   counter.
        #   Post.update_counters('50e0edd97c71c17ea9000001',
        #              :comments_count => 5, :likes_count => -2)
        #
        # @param [ String ] The id of the object to update.
        # @param [ Hash ] Key = counter_cache and Value = factor.
        #
        # @since 3.1.0
        def update_counters(id, counters)
          where(:_id => id).inc(counters)
        end

        # Increment the counter name from the entries that match the
        # id by one. This method is used on associations callbacks
        # when counter_cache is enabled
        #
        # @example Increment comments counter
        #   Post.increment_counter(:comments_count, '50e0edd97c71c17ea9000001')
        #
        # @param [ Symbol ] Counter cache name
        # @param [ String ] The id of the object that will have its counter incremented.
        #
        # @since 3.1.0
        def increment_counter(counter_name, id)
          update_counters(id, counter_name.to_sym => 1)
        end

        # Decrement the counter name from the entries that match the
        # id by one. This method is used on associations callbacks
        # when counter_cache is enabled
        #
        # @example Decrement comments counter
        #   Post.decrement_counter(:comments_count, '50e0edd97c71c17ea9000001')
        #
        # @param [ Symbol ] Counter cache name
        # @param [ String ] The id of the object that will have its counter decremented.
        #
        # @since 3.1.0
        def decrement_counter(counter_name, id)
          update_counters(id, counter_name.to_sym => -1)
        end

        private

        # Add the callbacks responsible for update the counter cache field
        #
        # @api private
        #
        # @example Add the touchable.
        #   Person.add_counter_cache_callbacks(meta)
        #
        # @param [ Metadata ] metadata The metadata for the relation.
        #
        # @since 3.1.0
        def add_counter_cache_callbacks(meta)
          name = meta.name
          cache_column = meta.counter_cache_column_name.to_sym

          after_create do
            if record = __send__(name)
              record[cache_column] = (record[cache_column] || 0) + 1

              if record.persisted?
                record.class.increment_counter(cache_column, record._id)
                record.remove_change(cache_column)
              end
            end
          end

          before_destroy do
            if record = __send__(name)
              record[cache_column] = (record[cache_column] || 0) - 1 unless record.frozen?

              if record.persisted?
                record.class.decrement_counter(cache_column, record._id)
                record.remove_change(cache_column)
              end
            end
          end
        end
      end
    end
  end
end