counter_culture 0.1.13 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -6,6 +6,7 @@ Turbo-charged counter caches for your Rails app. Huge improvements over the Rail
6
6
  * Supports counter caches through multiple levels of relations
7
7
  * Supports dynamic column names, making it possible to split up the counter cache for different types of objects
8
8
  * Executes counter updates after the commit, avoiding [deadlocks](http://mina.naguib.ca/blog/2010/11/22/postgresql-foreign-key-deadlocks.html)
9
+ * Can keep a running count, or a running total
9
10
 
10
11
  ## Installation
11
12
 
@@ -120,6 +121,28 @@ end
120
121
 
121
122
  Now, the ```Category``` model will keep the counter cache in ```special_count``` up-to-date. Only products where ```special?``` returns true will affect the special_count.
122
123
 
124
+ ### Totaling instead of counting
125
+
126
+ Instead of keeping a running count, you may want to automatically track a running total.
127
+ In that case, the target counter will change by the value in the totaled field instead of changing by exactly 1 each time.
128
+ Use the ```:delta_column``` option to specify that the counter should change by the value of a specific field in the counted object.
129
+ For example, suppose the Product model table has a field named ```weight_ounces```, and you want to keep a running
130
+ total of the weight for all the products in the Category model's ```product_weight_ounces``` field:
131
+
132
+ ```ruby
133
+ class Product < ActiveRecord::Base
134
+ belongs_to :category
135
+ counter_culture :category, :column_name => 'product_weight_ounces', :delta_column => 'weight_ounces'
136
+ end
137
+
138
+ class Category < ActiveRecord::Base
139
+ has_many :products
140
+ end
141
+ ```
142
+
143
+ Now, the ```Category``` model will keep the counter cache in ```product_weight_ounces``` up-to-date.
144
+ The value in the counter cache will be the sum of the ```weight_ounces``` values in each of the associated Product records.
145
+
123
146
  ### Dynamically over-writing affected foreign keys
124
147
 
125
148
  ```ruby
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.13
1
+ 0.1.14
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "counter_culture"
8
- s.version = "0.1.13"
8
+ s.version = "0.1.14"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Magnus von Koeller"]
12
- s.date = "2013-07-22"
12
+ s.date = "2013-08-13"
13
13
  s.description = "counter_culture provides turbo-charged counter caches that are kept up-to-date not just on create and destroy, that support multiple levels of indirection through relationships, allow dynamic column names and that avoid deadlocks by updating in the after_commit callback."
14
14
  s.email = "magnus@vonkoeller.de"
15
15
  s.extra_rdoc_files = [
@@ -30,6 +30,7 @@ module CounterCulture
30
30
  :relation => relation.is_a?(Enumerable) ? relation : [relation],
31
31
  :counter_cache_name => (options[:column_name] || "#{name.tableize}_count"),
32
32
  :column_names => options[:column_names],
33
+ :delta_column => options[:delta_column],
33
34
  :foreign_key_values => options[:foreign_key_values]
34
35
  }
35
36
  end
@@ -74,7 +75,9 @@ module CounterCulture
74
75
  klass = relation_klass(hash[:relation])
75
76
 
76
77
  # we are only interested in the id and the count of related objects (that's this class itself)
77
- query = klass.select("#{klass.table_name}.id, COUNT(#{self.table_name}.id) AS count")
78
+ query = hash[:delta_column] \
79
+ ? klass.select("#{klass.table_name}.id, SUM(COALESCE(#{self.table_name}.#{hash[:delta_column]},0)) AS count") \
80
+ : klass.select("#{klass.table_name}.id, COUNT(#{self.table_name}.id ) AS count")
78
81
  query = query.group("#{klass.table_name}.id")
79
82
  # respect the deleted_at column if it exists
80
83
  query = query.where("#{self.table_name}.deleted_at IS NULL") if self.column_names.include?('deleted_at')
@@ -217,7 +220,10 @@ module CounterCulture
217
220
  counter_cache_name_was = counter_cache_name_for(previous_model, hash[:counter_cache_name])
218
221
  counter_cache_name = counter_cache_name_for(self, hash[:counter_cache_name])
219
222
 
220
- if send("#{first_level_relation_foreign_key(hash[:relation])}_changed?") || counter_cache_name != counter_cache_name_was
223
+ if send("#{first_level_relation_foreign_key(hash[:relation])}_changed?") ||
224
+ (hash[:delta_column] && send("#{hash[:delta_column]}_changed?")) ||
225
+ counter_cache_name != counter_cache_name_was
226
+
221
227
  # increment the counter cache of the new value
222
228
  change_counter_cache(hash.merge(:increment => true, :counter_column => counter_cache_name))
223
229
  # decrement the counter cache of the old value
@@ -234,6 +240,7 @@ module CounterCulture
234
240
  # :relation => which relation to increment the count on,
235
241
  # :counter_cache_name => the column name of the counter cache
236
242
  # :counter_column => overrides :counter_cache_name
243
+ # :delta_column => override the default count delta (1) with the value of this column in the counted record
237
244
  # :was => whether to get the current value or the old value of the
238
245
  # first part of the relation
239
246
  def change_counter_cache(options)
@@ -245,12 +252,18 @@ module CounterCulture
245
252
  id_to_change = options[:foreign_key_values].call(id_to_change) if options[:foreign_key_values]
246
253
 
247
254
  if id_to_change && options[:counter_column]
255
+ delta_magnitude = if options[:delta_column]
256
+ delta_attr_name = options[:was] ? "#{options[:delta_column]}_was" : options[:delta_column]
257
+ self.send(delta_attr_name).to_i
258
+ else
259
+ 1
260
+ end
248
261
  execute_after_commit do
249
262
  # increment or decrement?
250
- method = options[:increment] ? :increment_counter : :decrement_counter
263
+ delta = options[:increment] ? delta_magnitude : -delta_magnitude
251
264
 
252
265
  # do it!
253
- relation_klass(options[:relation]).send(method, options[:counter_column], id_to_change)
266
+ relation_klass(options[:relation]).update_counters(id_to_change, options[:counter_column] => delta)
254
267
  end
255
268
  end
256
269
  end
@@ -15,13 +15,15 @@ describe "CounterCulture" do
15
15
 
16
16
  user.reviews_count.should == 0
17
17
  product.reviews_count.should == 0
18
+ user.review_approvals_count.should == 0
18
19
 
19
- review = Review.create :user_id => user.id, :product_id => product.id
20
+ user.reviews.create :user_id => user.id, :product_id => product.id, :approvals => 13
20
21
 
21
22
  user.reload
22
23
  product.reload
23
24
 
24
25
  user.reviews_count.should == 1
26
+ user.review_approvals_count.should == 13
25
27
  product.reviews_count.should == 1
26
28
  end
27
29
 
@@ -31,14 +33,16 @@ describe "CounterCulture" do
31
33
 
32
34
  user.reviews_count.should == 0
33
35
  product.reviews_count.should == 0
36
+ user.review_approvals_count.should == 0
34
37
 
35
- review = Review.create :user_id => user.id, :product_id => product.id
38
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 69
36
39
 
37
40
  user.reload
38
41
  product.reload
39
42
 
40
43
  user.reviews_count.should == 1
41
44
  product.reviews_count.should == 1
45
+ user.review_approvals_count.should == 69
42
46
 
43
47
  review.destroy
44
48
 
@@ -46,6 +50,7 @@ describe "CounterCulture" do
46
50
  product.reload
47
51
 
48
52
  user.reviews_count.should == 0
53
+ user.review_approvals_count.should == 0
49
54
  product.reviews_count.should == 0
50
55
  end
51
56
 
@@ -57,8 +62,10 @@ describe "CounterCulture" do
57
62
  user1.reviews_count.should == 0
58
63
  user2.reviews_count.should == 0
59
64
  product.reviews_count.should == 0
65
+ user1.review_approvals_count.should == 0
66
+ user2.review_approvals_count.should == 0
60
67
 
61
- review = Review.create :user_id => user1.id, :product_id => product.id
68
+ review = Review.create :user_id => user1.id, :product_id => product.id, :approvals => 42
62
69
 
63
70
  user1.reload
64
71
  user2.reload
@@ -67,6 +74,8 @@ describe "CounterCulture" do
67
74
  user1.reviews_count.should == 1
68
75
  user2.reviews_count.should == 0
69
76
  product.reviews_count.should == 1
77
+ user1.review_approvals_count.should == 42
78
+ user2.review_approvals_count.should == 0
70
79
 
71
80
  review.user = user2
72
81
  review.save!
@@ -78,6 +87,29 @@ describe "CounterCulture" do
78
87
  user1.reviews_count.should == 0
79
88
  user2.reviews_count.should == 1
80
89
  product.reviews_count.should == 1
90
+ user1.review_approvals_count.should == 0
91
+ user2.review_approvals_count.should == 42
92
+
93
+ review.update_attribute(:approvals, 69)
94
+ user2.reload.review_approvals_count.should == 69
95
+ end
96
+
97
+ it "treats null delta column values as 0" do
98
+ user = User.create
99
+ product = Product.create
100
+
101
+ user.reviews_count.should == 0
102
+ product.reviews_count.should == 0
103
+ user.review_approvals_count.should == 0
104
+
105
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => nil
106
+
107
+ user.reload
108
+ product.reload
109
+
110
+ user.reviews_count.should == 1
111
+ user.review_approvals_count.should == 0
112
+ product.reviews_count.should == 1
81
113
  end
82
114
 
83
115
  it "increments second-level counter cache on create" do
@@ -88,14 +120,16 @@ describe "CounterCulture" do
88
120
  company.reviews_count.should == 0
89
121
  user.reviews_count.should == 0
90
122
  product.reviews_count.should == 0
123
+ company.review_approvals_count.should == 0
91
124
 
92
- review = Review.create :user_id => user.id, :product_id => product.id
125
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 314
93
126
 
94
127
  company.reload
95
128
  user.reload
96
129
  product.reload
97
130
 
98
131
  company.reviews_count.should == 1
132
+ company.review_approvals_count.should == 314
99
133
  user.reviews_count.should == 1
100
134
  product.reviews_count.should == 1
101
135
  end
@@ -108,8 +142,9 @@ describe "CounterCulture" do
108
142
  company.reviews_count.should == 0
109
143
  user.reviews_count.should == 0
110
144
  product.reviews_count.should == 0
145
+ company.review_approvals_count.should == 0
111
146
 
112
- review = Review.create :user_id => user.id, :product_id => product.id
147
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 314
113
148
 
114
149
  user.reload
115
150
  product.reload
@@ -118,6 +153,7 @@ describe "CounterCulture" do
118
153
  user.reviews_count.should == 1
119
154
  product.reviews_count.should == 1
120
155
  company.reviews_count.should == 1
156
+ company.review_approvals_count.should == 314
121
157
 
122
158
  review.destroy
123
159
 
@@ -128,6 +164,7 @@ describe "CounterCulture" do
128
164
  user.reviews_count.should == 0
129
165
  product.reviews_count.should == 0
130
166
  company.reviews_count.should == 0
167
+ company.review_approvals_count.should == 0
131
168
  end
132
169
 
133
170
  it "updates second-level counter cache on update" do
@@ -142,8 +179,10 @@ describe "CounterCulture" do
142
179
  company1.reviews_count.should == 0
143
180
  company2.reviews_count.should == 0
144
181
  product.reviews_count.should == 0
182
+ company1.review_approvals_count.should == 0
183
+ company2.review_approvals_count.should == 0
145
184
 
146
- review = Review.create :user_id => user1.id, :product_id => product.id
185
+ review = Review.create :user_id => user1.id, :product_id => product.id, :approvals => 69
147
186
 
148
187
  user1.reload
149
188
  user2.reload
@@ -156,6 +195,8 @@ describe "CounterCulture" do
156
195
  company1.reviews_count.should == 1
157
196
  company2.reviews_count.should == 0
158
197
  product.reviews_count.should == 1
198
+ company1.review_approvals_count.should == 69
199
+ company2.review_approvals_count.should == 0
159
200
 
160
201
  review.user = user2
161
202
  review.save!
@@ -171,6 +212,11 @@ describe "CounterCulture" do
171
212
  company1.reviews_count.should == 0
172
213
  company2.reviews_count.should == 1
173
214
  product.reviews_count.should == 1
215
+ company1.review_approvals_count.should == 0
216
+ company2.review_approvals_count.should == 69
217
+
218
+ review.update_attribute(:approvals, 42)
219
+ company2.reload.review_approvals_count.should == 42
174
220
  end
175
221
 
176
222
  it "increments custom counter cache column on create" do
@@ -369,11 +415,12 @@ describe "CounterCulture" do
369
415
  product = Product.create
370
416
 
371
417
  industry.reviews_count.should == 0
418
+ industry.review_approvals_count.should == 0
372
419
  company.reviews_count.should == 0
373
420
  user.reviews_count.should == 0
374
421
  product.reviews_count.should == 0
375
422
 
376
- review = Review.create :user_id => user.id, :product_id => product.id
423
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 42
377
424
 
378
425
  industry.reload
379
426
  company.reload
@@ -381,6 +428,7 @@ describe "CounterCulture" do
381
428
  product.reload
382
429
 
383
430
  industry.reviews_count.should == 1
431
+ industry.review_approvals_count.should == 42
384
432
  company.reviews_count.should == 1
385
433
  user.reviews_count.should == 1
386
434
  product.reviews_count.should == 1
@@ -393,11 +441,12 @@ describe "CounterCulture" do
393
441
  product = Product.create
394
442
 
395
443
  industry.reviews_count.should == 0
444
+ industry.review_approvals_count.should == 0
396
445
  company.reviews_count.should == 0
397
446
  user.reviews_count.should == 0
398
447
  product.reviews_count.should == 0
399
448
 
400
- review = Review.create :user_id => user.id, :product_id => product.id
449
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 42
401
450
 
402
451
  industry.reload
403
452
  company.reload
@@ -405,6 +454,7 @@ describe "CounterCulture" do
405
454
  product.reload
406
455
 
407
456
  industry.reviews_count.should == 1
457
+ industry.review_approvals_count.should == 42
408
458
  company.reviews_count.should == 1
409
459
  user.reviews_count.should == 1
410
460
  product.reviews_count.should == 1
@@ -417,6 +467,7 @@ describe "CounterCulture" do
417
467
  product.reload
418
468
 
419
469
  industry.reviews_count.should == 0
470
+ industry.review_approvals_count.should == 0
420
471
  company.reviews_count.should == 0
421
472
  user.reviews_count.should == 0
422
473
  product.reviews_count.should == 0
@@ -437,8 +488,10 @@ describe "CounterCulture" do
437
488
  company2.reviews_count.should == 0
438
489
  user1.reviews_count.should == 0
439
490
  user2.reviews_count.should == 0
491
+ industry1.review_approvals_count.should == 0
492
+ industry2.review_approvals_count.should == 0
440
493
 
441
- review = Review.create :user_id => user1.id, :product_id => product.id
494
+ review = Review.create :user_id => user1.id, :product_id => product.id, :approvals => 42
442
495
 
443
496
  industry1.reload
444
497
  industry2.reload
@@ -453,6 +506,8 @@ describe "CounterCulture" do
453
506
  company2.reviews_count.should == 0
454
507
  user1.reviews_count.should == 1
455
508
  user2.reviews_count.should == 0
509
+ industry1.review_approvals_count.should == 42
510
+ industry2.review_approvals_count.should == 0
456
511
 
457
512
  review.user = user2
458
513
  review.save!
@@ -470,6 +525,11 @@ describe "CounterCulture" do
470
525
  company2.reviews_count.should == 1
471
526
  user1.reviews_count.should == 0
472
527
  user2.reviews_count.should == 1
528
+ industry1.review_approvals_count.should == 0
529
+ industry2.review_approvals_count.should == 42
530
+
531
+ review.update_attribute(:approvals, 69)
532
+ industry2.reload.review_approvals_count.should == 69
473
533
  end
474
534
 
475
535
  it "increments third-level custom counter cache on create" do
@@ -749,28 +809,32 @@ describe "CounterCulture" do
749
809
 
750
810
  user.reviews_count.should == 0
751
811
  product.reviews_count.should == 0
812
+ user.review_approvals_count.should == 0
752
813
 
753
- review = Review.create :user_id => user.id, :product_id => product.id
814
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 69
754
815
 
755
816
  user.reload
756
817
  product.reload
757
818
 
758
819
  user.reviews_count.should == 1
759
820
  product.reviews_count.should == 1
821
+ user.review_approvals_count.should == 69
760
822
 
761
823
  user.reviews_count = 0
762
824
  product.reviews_count = 2
825
+ user.review_approvals_count = 7
763
826
  user.save!
764
827
  product.save!
765
828
 
766
829
  fixed = Review.counter_culture_fix_counts :skip_unsupported => true
767
- fixed.length.should == 2
830
+ fixed.length.should == 3
768
831
 
769
832
  user.reload
770
833
  product.reload
771
834
 
772
835
  user.reviews_count.should == 1
773
836
  product.reviews_count.should == 1
837
+ user.review_approvals_count.should == 69
774
838
  end
775
839
 
776
840
  it "should fix where the count should go back to zero correctly" do
@@ -799,8 +863,9 @@ describe "CounterCulture" do
799
863
  company.reviews_count.should == 0
800
864
  user.reviews_count.should == 0
801
865
  product.reviews_count.should == 0
866
+ company.review_approvals_count.should == 0
802
867
 
803
- review = Review.create :user_id => user.id, :product_id => product.id
868
+ review = Review.create :user_id => user.id, :product_id => product.id, :approvals => 42
804
869
 
805
870
  company.reload
806
871
  user.reload
@@ -809,8 +874,10 @@ describe "CounterCulture" do
809
874
  company.reviews_count.should == 1
810
875
  user.reviews_count.should == 1
811
876
  product.reviews_count.should == 1
877
+ company.review_approvals_count.should == 42
812
878
 
813
879
  company.reviews_count = 2
880
+ company.review_approvals_count = 7
814
881
  user.reviews_count = 3
815
882
  product.reviews_count = 4
816
883
  company.save!
@@ -825,6 +892,7 @@ describe "CounterCulture" do
825
892
  company.reviews_count.should == 1
826
893
  user.reviews_count.should == 1
827
894
  product.reviews_count.should == 1
895
+ company.review_approvals_count.should == 42
828
896
  end
829
897
 
830
898
  it "should fix a custom counter cache correctly" do
@@ -6,10 +6,13 @@ class Review < ActiveRecord::Base
6
6
  counter_culture :product, :column_name => 'rexiews_count'
7
7
  counter_culture :user
8
8
  counter_culture :user, :column_name => Proc.new { |model| model.review_type ? "#{model.review_type}_count" : nil }, :column_names => {"reviews.review_type = 'using'" => 'using_count', "reviews.review_type = 'tried'" => 'tried_count'}
9
+ counter_culture :user, :column_name => 'review_approvals_count', :delta_column => 'approvals'
9
10
  counter_culture [:user, :manages_company]
11
+ counter_culture [:user, :manages_company], :column_name => 'review_approvals_count', :delta_column => 'approvals'
10
12
  counter_culture [:user, :manages_company, :industry]
11
13
  counter_culture [:user, :manages_company, :industry], :column_name => 'rexiews_count'
12
14
  counter_culture [:user, :manages_company, :industry], :column_name => Proc.new { |model| model.review_type ? "#{model.review_type}_count" : nil }
15
+ counter_culture [:user, :manages_company, :industry], :column_name => 'review_approvals_count', :delta_column => 'approvals'
13
16
 
14
17
  after_create :update_some_text
15
18
 
@@ -5,4 +5,6 @@ class User < ActiveRecord::Base
5
5
  counter_culture :manages_company, :column_name => "managers_count"
6
6
  belongs_to :has_string_id
7
7
  counter_culture :has_string_id
8
+
9
+ has_many :reviews
8
10
  end
@@ -20,6 +20,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
20
20
  t.integer "using_count", :default => 0, :null => false
21
21
  t.integer "tried_count", :default => 0, :null => false
22
22
  t.integer "managers_count", :default => 0, :null => false
23
+ t.integer "review_approvals_count", :default => 0, :null => false
23
24
  end
24
25
 
25
26
  create_table "industries", :force => true do |t|
@@ -28,6 +29,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
28
29
  t.integer "rexiews_count", :default => 0, :null => false
29
30
  t.integer "using_count", :default => 0, :null => false
30
31
  t.integer "tried_count", :default => 0, :null => false
32
+ t.integer "review_approvals_count", :default => 0, :null => false
31
33
  end
32
34
 
33
35
  create_table "products", :force => true do |t|
@@ -42,6 +44,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
42
44
  t.string "some_text"
43
45
  t.integer "user_id"
44
46
  t.integer "product_id"
47
+ t.integer "approvals"
45
48
  end
46
49
 
47
50
  create_table "users", :force => true do |t|
@@ -51,6 +54,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
51
54
  t.integer "reviews_count", :default => 0, :null => false
52
55
  t.integer "using_count", :default => 0, :null => false
53
56
  t.integer "tried_count", :default => 0, :null => false
57
+ t.integer "review_approvals_count", :default => 0, :null => false
54
58
  t.string "has_string_id_id"
55
59
  end
56
60
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: counter_culture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-22 00:00:00.000000000 Z
12
+ date: 2013-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -219,7 +219,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
219
219
  version: '0'
220
220
  segments:
221
221
  - 0
222
- hash: -3562941340638569663
222
+ hash: -1126156113488497245
223
223
  required_rubygems_version: !ruby/object:Gem::Requirement
224
224
  none: false
225
225
  requirements: