/
counter_cache.rb
97 lines (83 loc) · 2.84 KB
/
counter_cache.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
module MongoMapper
module Plugins
# Counter Caching for MongoMapper::Document
#
# Examples:
#
# class Post
# belongs_to :user
# counter_cache :user
# end
#
# or:
#
# class Post
# belongs_to :user
# counter_cache :user, :custom_posts_count
# end
#
# Field names follow rails conventions, so counter_cache :user will increment the Integer field `posts_count' on User
#
# Alternatively, you can also use the more common ActiveRecord syntax:
#
# class Post
# belongs_to :user, :counter_cache => true
# end
#
# Or with an alternative field name:
#
# class Post
# belongs_to :user, :counter_cache => :custom_posts_count
# end
#
module CounterCache
class InvalidCounterCacheError < StandardError; end
extend ActiveSupport::Concern
module ClassMethods
def counter_cache(association_name, options = {})
options.symbolize_keys!
field = options[:field] ?
options[:field] :
"#{self.collection_name.gsub(/.*\./, '')}_count"
association = associations[association_name]
if !association
raise InvalidCounterCacheError, "You must define an association with name `#{association_name}' on model #{self}"
end
# make a define-time check to make sure the counter cache field is defined.
# note: this can only be done in non-polymorphic classes
# (since we may not know the class on the other side of the association)
if !association.polymorphic?
association_class = association.klass
key_names = association_class.keys.keys
if !key_names.include?(field.to_s)
_raise_when_missing_counter_cache_key(association_class, field)
end
end
after_create do
if obj = self.send(association_name)
if !obj.respond_to?(field)
self.class._raise_when_missing_counter_cache_key(obj.class, field)
end
obj.increment(field => 1)
obj.write_attribute(field, obj.read_attribute(field) + 1)
end
true
end
after_destroy do
if obj = self.send(association_name)
if !obj.respond_to?(field)
self.class._raise_when_missing_counter_cache_key(obj.class, field)
end
obj.decrement(field => 1)
obj.write_attribute(field, obj.read_attribute(field) - 1)
end
true
end
end
def _raise_when_missing_counter_cache_key(klass, field)
raise InvalidCounterCacheError, "Missing `key #{field.to_sym.inspect}, Integer, :default => 0' on model #{klass}"
end
end
end
end
end