counter_culture 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +32 -5
- data/VERSION +1 -1
- data/counter_culture.gemspec +3 -3
- data/lib/counter_culture/counter.rb +15 -3
- data/lib/counter_culture/reconciler.rb +10 -4
- data/spec/counter_culture_spec.rb +81 -0
- data/spec/models/conditional_dependent.rb +1 -1
- data/spec/models/product.rb +2 -1
- data/spec/models/review.rb +12 -2
- data/spec/models/twitter_review.rb +2 -0
- data/spec/schema.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31d3e78549aeff7e69173ac1c5723b00be56ef8f
|
4
|
+
data.tar.gz: 997a2d03fb2b208894c80c1282ef7094241b12a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bac32314498b675600b72742d7cb5c317666de5d6bec07b377f8ed63b295f20ff919639136373527f725882ed03b369efb37639e0a55dce30c728614c33cd0b
|
7
|
+
data.tar.gz: 556109999f9a2f28dc04250e15e89e11420d3194f0b45bf580fe46fed2fbd9fe7e174a1e7705f27ad3dd9b678e07632bf0b003690794584a11cbf8e86210c179
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -101,7 +101,7 @@ Now, the ```Category``` model will keep an up-to-date counter-cache in the ```pr
|
|
101
101
|
```ruby
|
102
102
|
class Product < ActiveRecord::Base
|
103
103
|
belongs_to :category
|
104
|
-
counter_culture :category, :column_name =>
|
104
|
+
counter_culture :category, :column_name => proc {|model| "#{model.product_type}_count" }
|
105
105
|
# attribute product_type may be one of ['awesome', 'sucky']
|
106
106
|
end
|
107
107
|
|
@@ -110,14 +110,41 @@ class Category < ActiveRecord::Base
|
|
110
110
|
end
|
111
111
|
```
|
112
112
|
|
113
|
-
|
113
|
+
### Delta Magnitude
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
class Product < ActiveRecord::Base
|
117
|
+
belongs_to :category
|
118
|
+
counter_culture :category, column_name: :weight, delta_magnitude: proc { model.product_type == 'awesome' ? 2 : 1 }
|
119
|
+
end
|
120
|
+
|
121
|
+
class Category < ActiveRecord::Base
|
122
|
+
has_many :products
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
Now the `Category` model will keep the `weight` column up to date: `awesome` products will affect it by a magnitude of 2, others by a magnitude of 1.
|
127
|
+
|
128
|
+
You can also use a static multiplier as the `delta_magnitude`:
|
129
|
+
```ruby
|
130
|
+
class Product < ActiveRecord::Base
|
131
|
+
belongs_to :category
|
132
|
+
counter_culture :category, column_name: :weight, delta_magnitude: 3
|
133
|
+
end
|
134
|
+
|
135
|
+
class Category < ActiveRecord::Base
|
136
|
+
has_many :products
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
Now adding a `Product` will increase the `weight` column in its `Category` by 3; deleting it will decrease it by 3.
|
114
141
|
|
115
142
|
### Conditional counter cache
|
116
143
|
|
117
144
|
```ruby
|
118
145
|
class Product < ActiveRecord::Base
|
119
146
|
belongs_to :category
|
120
|
-
counter_culture :category, :column_name =>
|
147
|
+
counter_culture :category, :column_name => proc {|model| model.special? ? 'special_count' : nil }
|
121
148
|
end
|
122
149
|
|
123
150
|
class Category < ActiveRecord::Base
|
@@ -157,7 +184,7 @@ The ```:delta_column``` option supports all numeric column types, not just ```:i
|
|
157
184
|
class Product < ActiveRecord::Base
|
158
185
|
belongs_to :category
|
159
186
|
counter_culture :category, :foreign_key_values =>
|
160
|
-
|
187
|
+
proc {|category_id| [category_id, Category.find_by_id(category_id).try(:parent_category).try(:id)] }
|
161
188
|
end
|
162
189
|
|
163
190
|
class Category < ActiveRecord::Base
|
@@ -233,7 +260,7 @@ Manually populating counter caches with dynamic column names requires additional
|
|
233
260
|
class Product < ActiveRecord::Base
|
234
261
|
belongs_to :category
|
235
262
|
counter_culture :category,
|
236
|
-
:column_name =>
|
263
|
+
:column_name => proc {|model| "#{model.product_type}_count" },
|
237
264
|
:column_names => {
|
238
265
|
["products.product_type = ?", 'awesome'] => 'awesome_count',
|
239
266
|
["products.product_type = ?", 'sucky'] => 'sucky_count'
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/counter_culture.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: counter_culture 0.2.
|
5
|
+
# stub: counter_culture 0.2.1 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "counter_culture"
|
9
|
-
s.version = "0.2.
|
9
|
+
s.version = "0.2.1"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Magnus von Koeller"]
|
14
|
-
s.date = "2016-
|
14
|
+
s.date = "2016-06-15"
|
15
15
|
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."
|
16
16
|
s.email = "magnus@vonkoeller.de"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module CounterCulture
|
2
2
|
class Counter
|
3
|
-
CONFIG_OPTIONS = [ :column_names, :counter_cache_name, :delta_column, :foreign_key_values, :touch ]
|
3
|
+
CONFIG_OPTIONS = [ :column_names, :counter_cache_name, :delta_column, :foreign_key_values, :touch, :delta_magnitude ]
|
4
4
|
|
5
5
|
attr_reader :model, :relation, *CONFIG_OPTIONS
|
6
6
|
|
@@ -13,6 +13,7 @@ module CounterCulture
|
|
13
13
|
@delta_column = options[:delta_column]
|
14
14
|
@foreign_key_values = options[:foreign_key_values]
|
15
15
|
@touch = options.fetch(:touch, false)
|
16
|
+
@delta_magnitude = options[:delta_magnitude] || 1
|
16
17
|
end
|
17
18
|
|
18
19
|
# increments or decrements a counter cache
|
@@ -38,7 +39,7 @@ module CounterCulture
|
|
38
39
|
delta_attr_name = options[:was] ? "#{delta_column}_was" : delta_column
|
39
40
|
obj.send(delta_attr_name) || 0
|
40
41
|
else
|
41
|
-
|
42
|
+
counter_delta_magnitude_for(obj)
|
42
43
|
end
|
43
44
|
obj.execute_after_commit do
|
44
45
|
# increment or decrement?
|
@@ -64,6 +65,17 @@ module CounterCulture
|
|
64
65
|
end
|
65
66
|
end
|
66
67
|
|
68
|
+
# Gets the delta magnitude of the counter cache for a specific object
|
69
|
+
#
|
70
|
+
# obj: object to calculate the counter cache name for
|
71
|
+
def counter_delta_magnitude_for(obj)
|
72
|
+
if delta_magnitude.is_a?(Proc)
|
73
|
+
delta_magnitude.call(obj)
|
74
|
+
else
|
75
|
+
delta_magnitude
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
67
79
|
# Gets the name of the counter cache for a specific object
|
68
80
|
#
|
69
81
|
# obj: object to calculate the counter cache name for
|
@@ -172,4 +184,4 @@ module CounterCulture
|
|
172
184
|
prev
|
173
185
|
end
|
174
186
|
end
|
175
|
-
end
|
187
|
+
end
|
@@ -16,10 +16,11 @@ module CounterCulture
|
|
16
16
|
return false if @reconciled
|
17
17
|
|
18
18
|
if options[:skip_unsupported]
|
19
|
-
return false if (foreign_key_values || (counter_cache_name.is_a?(Proc) && !column_names))
|
19
|
+
return false if (foreign_key_values || (counter_cache_name.is_a?(Proc) && !column_names) || delta_magnitude.is_a?(Proc))
|
20
20
|
else
|
21
21
|
raise "Fixing counter caches is not supported when using :foreign_key_values; you may skip this relation with :skip_unsupported => true" if foreign_key_values
|
22
22
|
raise "Must provide :column_names option for relation #{relation.inspect} when :column_name is a Proc; you may skip this relation with :skip_unsupported => true" if counter_cache_name.is_a?(Proc) && !column_names
|
23
|
+
raise "Fixing counter caches is not supported when :delta_magnitude is a Proc; you may skip this relation with :skip_unsupported => true" if delta_magnitude.is_a?(Proc)
|
23
24
|
end
|
24
25
|
|
25
26
|
# if we're provided a custom set of column names with conditions, use them; just use the
|
@@ -42,7 +43,7 @@ module CounterCulture
|
|
42
43
|
# conditions must also be applied to the join on which we are counting
|
43
44
|
join_clauses.each_with_index do |join,index|
|
44
45
|
if index == join_clauses.size - 1 && where
|
45
|
-
join += " AND (#{model.send(:sanitize_sql_for_conditions, where)})"
|
46
|
+
join += " AND (#{model.send(:sanitize_sql_for_conditions, where)})"
|
46
47
|
end
|
47
48
|
counts_query = counts_query.joins(join)
|
48
49
|
end
|
@@ -83,7 +84,12 @@ module CounterCulture
|
|
83
84
|
|
84
85
|
def count_select
|
85
86
|
# if a delta column is provided use SUM, otherwise use COUNT
|
86
|
-
@count_select
|
87
|
+
return @count_select if @count_select
|
88
|
+
if delta_column
|
89
|
+
@count_select = "SUM(COALESCE(#{self_table_name}.#{delta_column},0))"
|
90
|
+
else
|
91
|
+
@count_select = "COUNT(#{self_table_name}.#{model.primary_key})*#{delta_magnitude}"
|
92
|
+
end
|
87
93
|
end
|
88
94
|
|
89
95
|
def relation_class
|
@@ -122,4 +128,4 @@ module CounterCulture
|
|
122
128
|
end
|
123
129
|
|
124
130
|
end
|
125
|
-
end
|
131
|
+
end
|
@@ -663,6 +663,66 @@ describe "CounterCulture" do
|
|
663
663
|
industry2.rexiews_count.should == 1
|
664
664
|
end
|
665
665
|
|
666
|
+
it "correctly handles dynamic delta magnitude" do
|
667
|
+
user = User.create
|
668
|
+
product = Product.create
|
669
|
+
|
670
|
+
review_heavy = Review.create(
|
671
|
+
:user_id => user.id,
|
672
|
+
:review_type => 'using',
|
673
|
+
:product_id => product.id,
|
674
|
+
:heavy => true,
|
675
|
+
)
|
676
|
+
user.reload
|
677
|
+
user.dynamic_delta_count.should == 2
|
678
|
+
|
679
|
+
review_light = Review.create(
|
680
|
+
:user_id => user.id,
|
681
|
+
:product_id => product.id,
|
682
|
+
:review_type => 'using',
|
683
|
+
:heavy => false,
|
684
|
+
)
|
685
|
+
user.reload
|
686
|
+
user.dynamic_delta_count.should == 3
|
687
|
+
|
688
|
+
review_heavy.destroy
|
689
|
+
user.reload
|
690
|
+
user.dynamic_delta_count.should == 1
|
691
|
+
|
692
|
+
review_light.destroy
|
693
|
+
user.reload
|
694
|
+
user.dynamic_delta_count.should == 0
|
695
|
+
end
|
696
|
+
|
697
|
+
it "correctly handles non-dynamic custom delta magnitude" do
|
698
|
+
user = User.create
|
699
|
+
product = Product.create
|
700
|
+
|
701
|
+
review1 = Review.create(
|
702
|
+
:user_id => user.id,
|
703
|
+
:review_type => 'using',
|
704
|
+
:product_id => product.id
|
705
|
+
)
|
706
|
+
user.reload
|
707
|
+
user.custom_delta_count.should == 3
|
708
|
+
|
709
|
+
review2 = Review.create(
|
710
|
+
:user_id => user.id,
|
711
|
+
:review_type => 'using',
|
712
|
+
:product_id => product.id
|
713
|
+
)
|
714
|
+
user.reload
|
715
|
+
user.custom_delta_count.should == 6
|
716
|
+
|
717
|
+
review1.destroy
|
718
|
+
user.reload
|
719
|
+
user.custom_delta_count.should == 3
|
720
|
+
|
721
|
+
review2.destroy
|
722
|
+
user.reload
|
723
|
+
user.custom_delta_count.should == 0
|
724
|
+
end
|
725
|
+
|
666
726
|
it "increments dynamic counter cache on create" do
|
667
727
|
user = User.create
|
668
728
|
product = Product.create
|
@@ -1101,6 +1161,27 @@ describe "CounterCulture" do
|
|
1101
1161
|
string_id.users_count.should == 2
|
1102
1162
|
end
|
1103
1163
|
|
1164
|
+
it "should fix a static delta magnitude column correctly" do
|
1165
|
+
user = User.create
|
1166
|
+
product = Product.create
|
1167
|
+
|
1168
|
+
Review.create(
|
1169
|
+
:user_id => user.id,
|
1170
|
+
:review_type => 'using',
|
1171
|
+
:product_id => product.id
|
1172
|
+
)
|
1173
|
+
|
1174
|
+
user.reload
|
1175
|
+
user.custom_delta_count.should == 3
|
1176
|
+
|
1177
|
+
user.update_attributes(:custom_delta_count => 5)
|
1178
|
+
|
1179
|
+
Review.counter_culture_fix_counts(:skip_unsupported => true)
|
1180
|
+
|
1181
|
+
user.reload
|
1182
|
+
user.custom_delta_count.should == 3
|
1183
|
+
end
|
1184
|
+
|
1104
1185
|
it "should work correctly for relationships with custom names" do
|
1105
1186
|
company = Company.create
|
1106
1187
|
user1 = User.create :manages_company_id => company.id
|
@@ -2,6 +2,6 @@ class ConditionalDependent < ActiveRecord::Base
|
|
2
2
|
belongs_to :conditional_main
|
3
3
|
|
4
4
|
counter_culture :conditional_main,
|
5
|
-
column_name:
|
5
|
+
column_name: proc {|m| m.condition? ? 'conditional_dependents_count' : nil },
|
6
6
|
column_names: { ['conditional_dependents.condition = ?', true] => 'conditional_dependents_count' }
|
7
7
|
end
|
data/spec/models/product.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
class Product < ActiveRecord::Base
|
2
2
|
belongs_to :category
|
3
3
|
|
4
|
-
counter_culture :category, :foreign_key_values =>
|
4
|
+
counter_culture :category, :foreign_key_values => proc {|foreign_key_value| Category.pluck(:id) }
|
5
|
+
|
5
6
|
end
|
data/spec/models/review.rb
CHANGED
@@ -5,14 +5,16 @@ class Review < ActiveRecord::Base
|
|
5
5
|
counter_culture :product, :touch => true
|
6
6
|
counter_culture :product, :column_name => 'rexiews_count'
|
7
7
|
counter_culture :user
|
8
|
-
counter_culture :user, :column_name =>
|
8
|
+
counter_culture :user, :column_name => proc { |model| model.review_type ? "#{model.review_type}_count" : nil }, :column_names => {"reviews.review_type = 'using'" => 'using_count', "reviews.review_type = 'tried'" => 'tried_count'}
|
9
9
|
counter_culture :user, :column_name => 'review_approvals_count', :delta_column => 'approvals'
|
10
10
|
counter_culture :user, :column_name => 'review_value_sum', :delta_column => 'value'
|
11
|
+
counter_culture :user, :column_name => 'dynamic_delta_count', delta_magnitude: proc {|model| model.weight }
|
12
|
+
counter_culture :user, :column_name => 'custom_delta_count', delta_magnitude: 3
|
11
13
|
counter_culture [:user, :manages_company]
|
12
14
|
counter_culture [:user, :manages_company], :column_name => 'review_approvals_count', :delta_column => 'approvals'
|
13
15
|
counter_culture [:user, :manages_company, :industry]
|
14
16
|
counter_culture [:user, :manages_company, :industry], :column_name => 'rexiews_count'
|
15
|
-
counter_culture [:user, :manages_company, :industry], :column_name =>
|
17
|
+
counter_culture [:user, :manages_company, :industry], :column_name => proc { |model| model.review_type ? "#{model.review_type}_count" : nil }
|
16
18
|
counter_culture [:user, :manages_company, :industry], :column_name => 'review_approvals_count', :delta_column => 'approvals'
|
17
19
|
|
18
20
|
after_create :update_some_text
|
@@ -20,4 +22,12 @@ class Review < ActiveRecord::Base
|
|
20
22
|
def update_some_text
|
21
23
|
update_attribute(:some_text, rand(36**12).to_s(36))
|
22
24
|
end
|
25
|
+
|
26
|
+
def weight
|
27
|
+
if heavy?
|
28
|
+
2
|
29
|
+
else
|
30
|
+
1
|
31
|
+
end
|
32
|
+
end
|
23
33
|
end
|
data/spec/schema.rb
CHANGED
@@ -57,6 +57,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
|
|
57
57
|
t.integer "product_id"
|
58
58
|
t.integer "approvals"
|
59
59
|
t.float "value"
|
60
|
+
t.boolean "heavy", :default => false, :null => false
|
60
61
|
t.string "type"
|
61
62
|
t.datetime "created_at"
|
62
63
|
t.datetime "updated_at"
|
@@ -69,6 +70,8 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
|
|
69
70
|
t.integer "reviews_count", :default => 0, :null => false
|
70
71
|
t.integer "using_count", :default => 0, :null => false
|
71
72
|
t.integer "tried_count", :default => 0, :null => false
|
73
|
+
t.integer "dynamic_delta_count", :default => 0, :null => false
|
74
|
+
t.integer "custom_delta_count", :default => 0, :null => false
|
72
75
|
t.integer "review_approvals_count", :default => 0, :null => false
|
73
76
|
t.string "has_string_id_id"
|
74
77
|
t.float "review_value_sum", :default => 0.0, :null => false
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: counter_culture
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Magnus von Koeller
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-06-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: after_commit_action
|