counter_culture 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|