Skip to content

Instantly share code, notes, and snippets.

@jeffreyiacono
Created February 4, 2012 22:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jeffreyiacono/1740848 to your computer and use it in GitHub Desktop.
Save jeffreyiacono/1740848 to your computer and use it in GitHub Desktop.
counter cache extension for mongoid orm
module Mongoid
module CounterCache
extend ActiveSupport::Concern
module ClassMethods
# Use Case:
#
# keep a cached count of embedded collections in the parent collection(s)
#
# for example, if a Post embeds many Comments, and there have been 2
# comments embedded in the the first post, we can do the following:
#
# >> Post.first.comments_count # 2
#
# which just queries for the post's attribute.
#
# The parent counter field should follow the format:
#
# pluralized embedded collection name + "_count"
#
# Again, using our example from above, Post would have a field of
# "comments_count" (type Integer, default of 0). If no
# configuration is provided to the counter_cache class method, this
# format will be derivated and assumed.
#
# All parents of the embedded document will be checked for a counter
# cache to incremement or decrement.
#
# Usage:
#
# class Playlist
# include Mongoid::Document
# include Mongoid::Timestamps
# embeds_many :tracks
# field :comments_count, type: Integer, default: 0
# end
#
# class Track
# include Mongoid::Document
# include Mongoid::Timestamps
# embedded_in :posts
# embeds_many :comments, cascade_callbacks: true
#
# field :comments_count, type: Integer, default: 0
# end
#
# class Comment
# include Mongoid::Document
# include Mongoid::Timestamps
# include Mongoid::CounterCache # where all the magic happens!
# embedded_in :tracks
# counter_cache # (optional) :field => "some_field_count", must match parent(s) field definition(s)
# end
#
# Then ...
# >> # some setup is assumed
# >> p = Playlist.first # comments_count = 0
# >> t = p.tracks.first # comments_count = 0
# >> t.comments << Comment.new
# >> p.save # true
# >> t.comments_count # 1
# >> p.comments_count # 1
def counter_cache(options = {})
counter_field = (options[:field] || "#{self.name.downcase.pluralize}_count").to_s
after_create do |document|
current_document = document
while current_document.embedded?
parent = current_document._parent
parent.inc(counter_field, 1) if parent.respond_to? counter_field
current_document = current_document._parent
end
end
after_destroy do |document|
current_document = document
while current_document.embedded?
parent = current_document._parent
parent.inc(counter_field, -1) if parent.respond_to? counter_field
current_document = current_document._parent
end
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment