counter_culture 0.1.14 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +11 -0
- data/Gemfile +4 -2
- data/Gemfile.lock +97 -67
- data/README.md +19 -5
- data/VERSION +1 -1
- data/counter_culture.gemspec +17 -8
- data/lib/counter_culture.rb +40 -25
- data/lib/generators/counter_culture_generator.rb +31 -0
- data/lib/generators/templates/counter_culture_migration.rb.erb +15 -0
- data/spec/counter_culture_spec.rb +64 -3
- data/spec/models/has_string_id.rb +1 -1
- data/spec/models/review.rb +1 -0
- data/spec/models/simple_dependent.rb +5 -0
- data/spec/models/simple_main.rb +3 -0
- data/spec/schema.rb +10 -0
- metadata +36 -35
- data/.rvmrc +0 -1
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35e696be357c398d2c9bcb23bde8ea01203ddbf8
|
4
|
+
data.tar.gz: 9d11a323ccdcd5e09f18e086e5cd948b0fd7bd36
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d87125c4933ac012ac93e5084079c86b85d1ad2420e04245ecb414f3cd4bb56d9e37b38404270b77061028b067545c0ec15cbf5dbb05ea9ed45942890eabdafe
|
7
|
+
data.tar.gz: a72a586a7ddd60204413200b3897b64bd9ec263dcfe5a0ddd4be525aa68404775e59a3a747724757682f29caf00e3bc304233b9c143b9fc5073f4e288a8e650d
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
counter_culture
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p247
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
## 0.1.15 (October 5, 2013)
|
2
|
+
|
3
|
+
Features:
|
4
|
+
- Added a simple migration generator to simplify adding counter cache columns
|
5
|
+
|
6
|
+
Improvements:
|
7
|
+
- delta_column now supports float values
|
8
|
+
|
9
|
+
Bugfixes:
|
10
|
+
|
11
|
+
- Prevent running out of memory when running counter_culture_fix_counts in large tables
|
data/Gemfile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
source "
|
1
|
+
source "https://rubygems.org"
|
2
2
|
# Add dependencies required to use your gem here.
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
@@ -9,15 +9,17 @@ group :development, :test do
|
|
9
9
|
gem "rails", '>= 3.1.0'
|
10
10
|
gem "rspec", "~> 2.10.0"
|
11
11
|
gem "after_commit_action"
|
12
|
+
gem "awesome_print"
|
12
13
|
end
|
13
14
|
|
14
15
|
group :development do
|
15
16
|
gem "rdoc", "~> 3.12"
|
16
|
-
gem "bundler", "
|
17
|
+
gem "bundler", ">= 1.2.0"
|
17
18
|
gem "jeweler", "~> 1.8.3"
|
18
19
|
end
|
19
20
|
|
20
21
|
group :test do
|
21
22
|
gem "sqlite3"
|
22
23
|
gem "rspec-extra-formatters"
|
24
|
+
gem "database_cleaner", ">= 1.1.1"
|
23
25
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,80 +1,102 @@
|
|
1
1
|
GEM
|
2
|
-
remote:
|
2
|
+
remote: https://rubygems.org/
|
3
3
|
specs:
|
4
|
-
actionmailer (
|
5
|
-
actionpack (=
|
6
|
-
mail (~> 2.
|
7
|
-
actionpack (
|
8
|
-
|
9
|
-
|
10
|
-
builder (~> 3.0.0)
|
4
|
+
actionmailer (4.0.0)
|
5
|
+
actionpack (= 4.0.0)
|
6
|
+
mail (~> 2.5.3)
|
7
|
+
actionpack (4.0.0)
|
8
|
+
activesupport (= 4.0.0)
|
9
|
+
builder (~> 3.1.0)
|
11
10
|
erubis (~> 2.7.0)
|
12
|
-
|
13
|
-
rack (~>
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
multi_json (~> 1.0)
|
11
|
+
rack (~> 1.5.2)
|
12
|
+
rack-test (~> 0.6.2)
|
13
|
+
activemodel (4.0.0)
|
14
|
+
activesupport (= 4.0.0)
|
15
|
+
builder (~> 3.1.0)
|
16
|
+
activerecord (4.0.0)
|
17
|
+
activemodel (= 4.0.0)
|
18
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
19
|
+
activesupport (= 4.0.0)
|
20
|
+
arel (~> 4.0.0)
|
21
|
+
activerecord-deprecated_finders (1.0.3)
|
22
|
+
activesupport (4.0.0)
|
23
|
+
i18n (~> 0.6, >= 0.6.4)
|
24
|
+
minitest (~> 4.2)
|
25
|
+
multi_json (~> 1.3)
|
26
|
+
thread_safe (~> 0.1)
|
27
|
+
tzinfo (~> 0.3.37)
|
28
|
+
addressable (2.3.5)
|
31
29
|
after_commit_action (0.1.3)
|
32
30
|
activerecord (>= 3.0.0)
|
33
|
-
arel (
|
34
|
-
|
31
|
+
arel (4.0.0)
|
32
|
+
atomic (1.1.14)
|
33
|
+
awesome_print (1.2.0)
|
34
|
+
builder (3.1.4)
|
35
|
+
database_cleaner (1.1.1)
|
35
36
|
diff-lcs (1.1.3)
|
36
37
|
erubis (2.7.0)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
faraday (0.8.8)
|
39
|
+
multipart-post (~> 1.2.0)
|
40
|
+
git (1.2.6)
|
41
|
+
github_api (0.10.1)
|
42
|
+
addressable
|
43
|
+
faraday (~> 0.8.1)
|
44
|
+
hashie (>= 1.2)
|
45
|
+
multi_json (~> 1.4)
|
46
|
+
nokogiri (~> 1.5.2)
|
47
|
+
oauth2
|
48
|
+
hashie (2.0.5)
|
49
|
+
highline (1.6.19)
|
50
|
+
hike (1.2.3)
|
51
|
+
httpauth (0.2.0)
|
52
|
+
i18n (0.6.5)
|
53
|
+
jeweler (1.8.7)
|
54
|
+
builder
|
41
55
|
bundler (~> 1.0)
|
42
56
|
git (>= 1.2.5)
|
57
|
+
github_api (= 0.10.1)
|
58
|
+
highline (>= 1.6.15)
|
59
|
+
nokogiri (= 1.5.10)
|
43
60
|
rake
|
44
61
|
rdoc
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
62
|
+
json (1.8.0)
|
63
|
+
jwt (0.1.8)
|
64
|
+
multi_json (>= 1.5)
|
65
|
+
mail (2.5.4)
|
49
66
|
mime-types (~> 1.16)
|
50
67
|
treetop (~> 1.4.8)
|
51
|
-
mime-types (1.
|
52
|
-
|
68
|
+
mime-types (1.25)
|
69
|
+
minitest (4.7.5)
|
70
|
+
multi_json (1.8.1)
|
71
|
+
multi_xml (0.5.5)
|
72
|
+
multipart-post (1.2.0)
|
73
|
+
nokogiri (1.5.10)
|
74
|
+
oauth2 (0.9.2)
|
75
|
+
faraday (~> 0.8)
|
76
|
+
httpauth (~> 0.2)
|
77
|
+
jwt (~> 0.1.4)
|
78
|
+
multi_json (~> 1.0)
|
79
|
+
multi_xml (~> 0.5)
|
80
|
+
rack (~> 1.2)
|
53
81
|
polyglot (0.3.3)
|
54
|
-
rack (1.
|
55
|
-
rack-cache (1.2)
|
56
|
-
rack (>= 0.4)
|
57
|
-
rack-ssl (1.3.2)
|
58
|
-
rack
|
82
|
+
rack (1.5.2)
|
59
83
|
rack-test (0.6.2)
|
60
84
|
rack (>= 1.0)
|
61
|
-
rails (
|
62
|
-
actionmailer (=
|
63
|
-
actionpack (=
|
64
|
-
activerecord (=
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
railties (
|
70
|
-
actionpack (=
|
71
|
-
activesupport (=
|
72
|
-
rack-ssl (~> 1.3.2)
|
85
|
+
rails (4.0.0)
|
86
|
+
actionmailer (= 4.0.0)
|
87
|
+
actionpack (= 4.0.0)
|
88
|
+
activerecord (= 4.0.0)
|
89
|
+
activesupport (= 4.0.0)
|
90
|
+
bundler (>= 1.3.0, < 2.0)
|
91
|
+
railties (= 4.0.0)
|
92
|
+
sprockets-rails (~> 2.0.0)
|
93
|
+
railties (4.0.0)
|
94
|
+
actionpack (= 4.0.0)
|
95
|
+
activesupport (= 4.0.0)
|
73
96
|
rake (>= 0.8.7)
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
rdoc (3.12)
|
97
|
+
thor (>= 0.18.1, < 2.0)
|
98
|
+
rake (10.1.0)
|
99
|
+
rdoc (3.12.2)
|
78
100
|
json (~> 1.4)
|
79
101
|
rspec (2.10.0)
|
80
102
|
rspec-core (~> 2.10.0)
|
@@ -85,25 +107,33 @@ GEM
|
|
85
107
|
diff-lcs (~> 1.1.3)
|
86
108
|
rspec-extra-formatters (1.0.0)
|
87
109
|
rspec-mocks (2.10.1)
|
88
|
-
sprockets (2.
|
110
|
+
sprockets (2.10.0)
|
89
111
|
hike (~> 1.2)
|
90
112
|
multi_json (~> 1.0)
|
91
113
|
rack (~> 1.0)
|
92
114
|
tilt (~> 1.1, != 1.3.0)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
115
|
+
sprockets-rails (2.0.0)
|
116
|
+
actionpack (>= 3.0)
|
117
|
+
activesupport (>= 3.0)
|
118
|
+
sprockets (~> 2.8)
|
119
|
+
sqlite3 (1.3.8)
|
120
|
+
thor (0.18.1)
|
121
|
+
thread_safe (0.1.3)
|
122
|
+
atomic
|
123
|
+
tilt (1.4.1)
|
124
|
+
treetop (1.4.15)
|
97
125
|
polyglot
|
98
126
|
polyglot (>= 0.3.1)
|
99
|
-
tzinfo (0.3.
|
127
|
+
tzinfo (0.3.37)
|
100
128
|
|
101
129
|
PLATFORMS
|
102
130
|
ruby
|
103
131
|
|
104
132
|
DEPENDENCIES
|
105
133
|
after_commit_action
|
106
|
-
|
134
|
+
awesome_print
|
135
|
+
bundler (>= 1.2.0)
|
136
|
+
database_cleaner (>= 1.1.1)
|
107
137
|
jeweler (~> 1.8.3)
|
108
138
|
rails (>= 3.1.0)
|
109
139
|
rdoc (~> 3.12)
|
data/README.md
CHANGED
@@ -20,13 +20,19 @@ Then run ```bundle update ```
|
|
20
20
|
|
21
21
|
## Database Schema
|
22
22
|
|
23
|
-
You
|
23
|
+
You must create the necessary columns for all counter caches. You can use counter_culture's generator to create a skeleton migration:
|
24
|
+
|
25
|
+
```
|
26
|
+
rails generate counter_culture Category products_count
|
27
|
+
```
|
28
|
+
|
29
|
+
Which will generate a migration with code like the following:
|
24
30
|
```ruby
|
25
31
|
add_column :categories, :products_count, :integer, :null => false, :default => 0
|
26
32
|
```
|
27
|
-
|
33
|
+
Note that the column must be ```NOT NULL``` and have a default of zero for this gem to work correctly.
|
28
34
|
|
29
|
-
If you are adding counter caches to existing data, you must [manually populate their values](#manually-populating-counter-cache-values).
|
35
|
+
If you are adding counter caches to existing data, you must add code to [manually populate their values](#manually-populating-counter-cache-values) to the generated migration.
|
30
36
|
|
31
37
|
## Usage
|
32
38
|
|
@@ -143,6 +149,8 @@ end
|
|
143
149
|
Now, the ```Category``` model will keep the counter cache in ```product_weight_ounces``` up-to-date.
|
144
150
|
The value in the counter cache will be the sum of the ```weight_ounces``` values in each of the associated Product records.
|
145
151
|
|
152
|
+
The ```:delta_column``` option supports all numeric column types, not just ```:integer```. Specifically, ```:float``` is supported and tested.
|
153
|
+
|
146
154
|
### Dynamically over-writing affected foreign keys
|
147
155
|
|
148
156
|
```ruby
|
@@ -220,7 +228,13 @@ Manually populating counter caches with dynamicall over-written foreign keys (``
|
|
220
228
|
|
221
229
|
#### Polymorphic associations
|
222
230
|
|
223
|
-
counter_culture currently does *not* support polymorphic associations. Check [this issue](https://github.com/
|
231
|
+
counter_culture currently does *not* support polymorphic associations. Check [this issue](https://github.com/magnusvk/counter_culture/issues/4) for progress and alternatives.
|
232
|
+
|
233
|
+
## A note on testing
|
234
|
+
|
235
|
+
counter_culture will not update counters in your automated tests *if* you use transactional fixtures. That's because transactional fixtures roll back all your database transactions and they are never committed. But counter_culture will only update its counters in the ```after_commit``` callback, which in this case will never run.
|
236
|
+
|
237
|
+
counter_culture itself has extensive automated tests so there should not be a need to test counter caches in your own tests. I therefore recommend removing any checks of counter caches as that will avoid this issue. If that is not an option for you, you will have to turn off transactional fixtures and use something like [database_cleaner](https://github.com/bmabey/database_cleaner) instead to clean your database between tests.
|
224
238
|
|
225
239
|
## Contributing to counter_culture
|
226
240
|
|
@@ -234,4 +248,4 @@ counter_culture currently does *not* support polymorphic associations. Check [th
|
|
234
248
|
|
235
249
|
## Copyright
|
236
250
|
|
237
|
-
Copyright (c) 2012 BestVendor. See LICENSE.txt for further details.
|
251
|
+
Copyright (c) 2012-2013 BestVendor, Magnus von Koeller. See LICENSE.txt for further details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.15
|
data/counter_culture.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "counter_culture"
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.15"
|
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-
|
12
|
+
s.date = "2013-10-05"
|
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 = [
|
@@ -19,7 +19,9 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.files = [
|
20
20
|
".document",
|
21
21
|
".rspec",
|
22
|
-
".
|
22
|
+
".ruby-gemset",
|
23
|
+
".ruby-version",
|
24
|
+
"CHANGELOG.md",
|
23
25
|
"Gemfile",
|
24
26
|
"Gemfile.lock",
|
25
27
|
"LICENSE.txt",
|
@@ -29,6 +31,8 @@ Gem::Specification.new do |s|
|
|
29
31
|
"circle.yml",
|
30
32
|
"counter_culture.gemspec",
|
31
33
|
"lib/counter_culture.rb",
|
34
|
+
"lib/generators/counter_culture_generator.rb",
|
35
|
+
"lib/generators/templates/counter_culture_migration.rb.erb",
|
32
36
|
"spec/counter_culture_spec.rb",
|
33
37
|
"spec/models/category.rb",
|
34
38
|
"spec/models/company.rb",
|
@@ -36,6 +40,8 @@ Gem::Specification.new do |s|
|
|
36
40
|
"spec/models/industry.rb",
|
37
41
|
"spec/models/product.rb",
|
38
42
|
"spec/models/review.rb",
|
43
|
+
"spec/models/simple_dependent.rb",
|
44
|
+
"spec/models/simple_main.rb",
|
39
45
|
"spec/models/user.rb",
|
40
46
|
"spec/rails_app/.gitignore",
|
41
47
|
"spec/rails_app/Gemfile",
|
@@ -91,26 +97,28 @@ Gem::Specification.new do |s|
|
|
91
97
|
s.homepage = "http://github.com/bestvendor/counter_culture"
|
92
98
|
s.licenses = ["MIT"]
|
93
99
|
s.require_paths = ["lib"]
|
94
|
-
s.rubygems_version = "
|
100
|
+
s.rubygems_version = "2.0.7"
|
95
101
|
s.summary = "Turbo-charged counter caches for your Rails app."
|
96
102
|
|
97
103
|
if s.respond_to? :specification_version then
|
98
|
-
s.specification_version =
|
104
|
+
s.specification_version = 4
|
99
105
|
|
100
106
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
101
107
|
s.add_development_dependency(%q<rails>, [">= 3.1.0"])
|
102
108
|
s.add_development_dependency(%q<rspec>, ["~> 2.10.0"])
|
103
109
|
s.add_development_dependency(%q<after_commit_action>, [">= 0"])
|
110
|
+
s.add_development_dependency(%q<awesome_print>, [">= 0"])
|
104
111
|
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
105
|
-
s.add_development_dependency(%q<bundler>, ["
|
112
|
+
s.add_development_dependency(%q<bundler>, [">= 1.2.0"])
|
106
113
|
s.add_development_dependency(%q<jeweler>, ["~> 1.8.3"])
|
107
114
|
s.add_runtime_dependency(%q<after_commit_action>, ["~> 0.1.3"])
|
108
115
|
else
|
109
116
|
s.add_dependency(%q<rails>, [">= 3.1.0"])
|
110
117
|
s.add_dependency(%q<rspec>, ["~> 2.10.0"])
|
111
118
|
s.add_dependency(%q<after_commit_action>, [">= 0"])
|
119
|
+
s.add_dependency(%q<awesome_print>, [">= 0"])
|
112
120
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
113
|
-
s.add_dependency(%q<bundler>, ["
|
121
|
+
s.add_dependency(%q<bundler>, [">= 1.2.0"])
|
114
122
|
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
115
123
|
s.add_dependency(%q<after_commit_action>, ["~> 0.1.3"])
|
116
124
|
end
|
@@ -118,8 +126,9 @@ Gem::Specification.new do |s|
|
|
118
126
|
s.add_dependency(%q<rails>, [">= 3.1.0"])
|
119
127
|
s.add_dependency(%q<rspec>, ["~> 2.10.0"])
|
120
128
|
s.add_dependency(%q<after_commit_action>, [">= 0"])
|
129
|
+
s.add_dependency(%q<awesome_print>, [">= 0"])
|
121
130
|
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
122
|
-
s.add_dependency(%q<bundler>, ["
|
131
|
+
s.add_dependency(%q<bundler>, [">= 1.2.0"])
|
123
132
|
s.add_dependency(%q<jeweler>, ["~> 1.8.3"])
|
124
133
|
s.add_dependency(%q<after_commit_action>, ["~> 0.1.3"])
|
125
134
|
end
|
data/lib/counter_culture.rb
CHANGED
@@ -75,10 +75,11 @@ module CounterCulture
|
|
75
75
|
klass = relation_klass(hash[:relation])
|
76
76
|
|
77
77
|
# we are only interested in the id and the count of related objects (that's this class itself)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
78
|
+
if hash[:delta_column]
|
79
|
+
query = klass.select("#{klass.table_name}.id, SUM(COALESCE(#{self.table_name}.#{hash[:delta_column]},0)) AS count")
|
80
|
+
else
|
81
|
+
query = klass.select("#{klass.table_name}.id, COUNT(#{self.table_name}.id ) AS count")
|
82
|
+
end
|
82
83
|
# respect the deleted_at column if it exists
|
83
84
|
query = query.where("#{self.table_name}.deleted_at IS NULL") if self.column_names.include?('deleted_at')
|
84
85
|
|
@@ -88,7 +89,7 @@ module CounterCulture
|
|
88
89
|
# iterate over all the possible counter cache column names
|
89
90
|
column_names.each do |where, column_name|
|
90
91
|
# if there are additional conditions, add them here
|
91
|
-
|
92
|
+
counts_query = query.where(where)
|
92
93
|
|
93
94
|
# we need to work our way back from the end-point of the relation to this class itself;
|
94
95
|
# make a list of arrays pointing to the second-to-last, third-to-last, etc.
|
@@ -99,27 +100,36 @@ module CounterCulture
|
|
99
100
|
# lives in
|
100
101
|
reverse_relation.each do |cur_relation|
|
101
102
|
reflect = relation_reflect(cur_relation)
|
102
|
-
|
103
|
+
counts_query = counts_query.joins("JOIN #{reflect.active_record.table_name} ON #{reflect.table_name}.id = #{reflect.active_record.table_name}.#{reflect.foreign_key}")
|
103
104
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
klass.
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
105
|
+
|
106
|
+
# iterate in batches; otherwise we might run out of memory when there's a lot of
|
107
|
+
# instances and we try to load all their counts at once
|
108
|
+
start = 0
|
109
|
+
batch_size = options[:batch_size] || 1000
|
110
|
+
while (records = klass.reorder(full_primary_key(klass) + " ASC").offset(start).limit(batch_size)).any?
|
111
|
+
# collect the counts for this batch in an id => count hash; this saves time relative
|
112
|
+
# to running one query per record
|
113
|
+
counts = counts_query.reorder(full_primary_key(klass) + " ASC").offset(start).limit(batch_size).group(full_primary_key(klass)).inject({}){|memo, model| memo[model.id] = model.count.to_i; memo}
|
114
|
+
|
115
|
+
# now iterate over all the models and see whether their counts are right
|
116
|
+
records.each do |model|
|
117
|
+
if model.read_attribute(column_name) != counts[model.id].to_i
|
118
|
+
# keep track of what we fixed, e.g. for a notification email
|
119
|
+
fixed<< {
|
120
|
+
:entity => klass.name,
|
121
|
+
:id => model.id,
|
122
|
+
:what => column_name,
|
123
|
+
:wrong => model.send(column_name),
|
124
|
+
:right => counts[model.id]
|
125
|
+
}
|
126
|
+
# use update_all because it's faster and because a fixed counter-cache shouldn't
|
127
|
+
# update the timestamp
|
128
|
+
klass.where(:id => model.id).update_all(column_name => counts[model.id].to_i)
|
129
|
+
end
|
122
130
|
end
|
131
|
+
|
132
|
+
start += batch_size
|
123
133
|
end
|
124
134
|
end
|
125
135
|
end
|
@@ -128,6 +138,11 @@ module CounterCulture
|
|
128
138
|
end
|
129
139
|
|
130
140
|
private
|
141
|
+
# the string to pass to order() in order to sort by primary key
|
142
|
+
def full_primary_key(klass)
|
143
|
+
"#{klass.quoted_table_name}.#{klass.quoted_primary_key}"
|
144
|
+
end
|
145
|
+
|
131
146
|
# gets the reflect object on the given relation
|
132
147
|
#
|
133
148
|
# relation: a symbol or array of symbols; specifies the relation
|
@@ -254,7 +269,7 @@ module CounterCulture
|
|
254
269
|
if id_to_change && options[:counter_column]
|
255
270
|
delta_magnitude = if options[:delta_column]
|
256
271
|
delta_attr_name = options[:was] ? "#{options[:delta_column]}_was" : options[:delta_column]
|
257
|
-
self.send(delta_attr_name)
|
272
|
+
self.send(delta_attr_name) || 0
|
258
273
|
else
|
259
274
|
1
|
260
275
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rails/generators/active_record'
|
2
|
+
|
3
|
+
class CounterCultureGenerator < ActiveRecord::Generators::Base
|
4
|
+
|
5
|
+
desc "Create a migration that adds counter-cache columns to a model"
|
6
|
+
|
7
|
+
argument :counter_cache_columns, :required => true, :type => :array,
|
8
|
+
:desc => "The names of the counter cache columns to add",
|
9
|
+
:banner => "counter_cache_one counter_cache_two counter_cache_three ..."
|
10
|
+
|
11
|
+
source_root File.expand_path("../templates", __FILE__)
|
12
|
+
|
13
|
+
def generate_migration
|
14
|
+
migration_template "counter_culture_migration.rb.erb", "db/migrate/#{migration_file_name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def migration_name
|
20
|
+
"add_#{counter_cache_columns.join("_")}_to_#{name.underscore.pluralize}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def migration_file_name
|
24
|
+
"#{migration_name}.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
def migration_class_name
|
28
|
+
migration_name.camelize
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration
|
2
|
+
|
3
|
+
def self.up
|
4
|
+
<% counter_cache_columns.each do |column| %>
|
5
|
+
add_column :<%= table_name %>, :<%= column %>, :integer, :null => false, :default => 0
|
6
|
+
<% end %>
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
<% counter_cache_columns.each do |column| %>
|
11
|
+
remove_column :<%= table_name %>, :<%= column %>
|
12
|
+
<% end %>
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -7,8 +7,17 @@ require 'models/review'
|
|
7
7
|
require 'models/user'
|
8
8
|
require 'models/category'
|
9
9
|
require 'models/has_string_id'
|
10
|
+
require 'models/simple_main'
|
11
|
+
require 'models/simple_dependent'
|
12
|
+
|
13
|
+
require 'database_cleaner'
|
14
|
+
DatabaseCleaner.strategy = :deletion
|
10
15
|
|
11
16
|
describe "CounterCulture" do
|
17
|
+
before(:each) do
|
18
|
+
DatabaseCleaner.clean
|
19
|
+
end
|
20
|
+
|
12
21
|
it "increments counter cache on create" do
|
13
22
|
user = User.create
|
14
23
|
product = Product.create
|
@@ -950,7 +959,7 @@ describe "CounterCulture" do
|
|
950
959
|
end
|
951
960
|
|
952
961
|
it "should fix a string counter cache correctly" do
|
953
|
-
string_id = HasStringId.create({:id => "bbb"}
|
962
|
+
string_id = HasStringId.create({:id => "bbb"})
|
954
963
|
|
955
964
|
user = User.create :has_string_id_id => string_id.id
|
956
965
|
|
@@ -1000,8 +1009,8 @@ describe "CounterCulture" do
|
|
1000
1009
|
end
|
1001
1010
|
|
1002
1011
|
it "should work correctly with string keys" do
|
1003
|
-
string_id = HasStringId.create(
|
1004
|
-
string_id2 = HasStringId.create(
|
1012
|
+
string_id = HasStringId.create(id: "1")
|
1013
|
+
string_id2 = HasStringId.create(id: "abc")
|
1005
1014
|
|
1006
1015
|
user = User.create :has_string_id_id => string_id.id
|
1007
1016
|
|
@@ -1037,6 +1046,58 @@ describe "CounterCulture" do
|
|
1037
1046
|
it "should raise a good error message when calling fix_counts with no caches defined" do
|
1038
1047
|
expect { Category.counter_culture_fix_counts }.to raise_error "No counter cache defined on Category"
|
1039
1048
|
end
|
1049
|
+
|
1050
|
+
it "should correctly fix the counter caches with thousands of records" do
|
1051
|
+
# first, clean up
|
1052
|
+
SimpleDependent.delete_all
|
1053
|
+
SimpleMain.delete_all
|
1054
|
+
|
1055
|
+
10000.times do |i|
|
1056
|
+
main = SimpleMain.create
|
1057
|
+
3.times { main.simple_dependents.create }
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
SimpleMain.find_each { |main| main.simple_dependents_count.should == 3 }
|
1061
|
+
|
1062
|
+
SimpleMain.order('random()').limit(50).update_all simple_dependents_count: 1
|
1063
|
+
SimpleDependent.counter_culture_fix_counts
|
1064
|
+
|
1065
|
+
SimpleMain.find_each { |main| main.simple_dependents_count.should == 3 }
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
it "should correctly sum up flaot values" do
|
1069
|
+
user = User.create
|
1070
|
+
|
1071
|
+
r1 = Review.create :user_id => user.id, :value => 3.4
|
1072
|
+
|
1073
|
+
user.reload
|
1074
|
+
user.review_value_sum.round(1).should == 3.4
|
1075
|
+
|
1076
|
+
r2 = Review.create :user_id => user.id, :value => 7.2
|
1077
|
+
|
1078
|
+
user.reload
|
1079
|
+
user.review_value_sum.round(1).should == 10.6
|
1080
|
+
|
1081
|
+
r3 = Review.create :user_id => user.id, :value => 5
|
1082
|
+
|
1083
|
+
user.reload
|
1084
|
+
user.review_value_sum.round(1).should == 15.6
|
1085
|
+
|
1086
|
+
r2.destroy
|
1087
|
+
|
1088
|
+
user.reload
|
1089
|
+
user.review_value_sum.round(1).should == 8.4
|
1090
|
+
|
1091
|
+
r3.destroy
|
1092
|
+
|
1093
|
+
user.reload
|
1094
|
+
user.review_value_sum.round(1).should == 3.4
|
1095
|
+
|
1096
|
+
r1.destroy
|
1097
|
+
|
1098
|
+
user.reload
|
1099
|
+
user.review_value_sum.round(1).should == 0
|
1100
|
+
end
|
1040
1101
|
|
1041
1102
|
describe "#previous_model" do
|
1042
1103
|
let(:user){User.create :name => "John Smith", :manages_company_id => 1}
|
data/spec/models/review.rb
CHANGED
@@ -7,6 +7,7 @@ class Review < ActiveRecord::Base
|
|
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
9
|
counter_culture :user, :column_name => 'review_approvals_count', :delta_column => 'approvals'
|
10
|
+
counter_culture :user, :column_name => 'review_value_sum', :delta_column => 'value'
|
10
11
|
counter_culture [:user, :manages_company]
|
11
12
|
counter_culture [:user, :manages_company], :column_name => 'review_approvals_count', :delta_column => 'approvals'
|
12
13
|
counter_culture [:user, :manages_company, :industry]
|
data/spec/schema.rb
CHANGED
@@ -45,6 +45,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
|
|
45
45
|
t.integer "user_id"
|
46
46
|
t.integer "product_id"
|
47
47
|
t.integer "approvals"
|
48
|
+
t.float "value"
|
48
49
|
end
|
49
50
|
|
50
51
|
create_table "users", :force => true do |t|
|
@@ -56,6 +57,7 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
|
|
56
57
|
t.integer "tried_count", :default => 0, :null => false
|
57
58
|
t.integer "review_approvals_count", :default => 0, :null => false
|
58
59
|
t.string "has_string_id_id"
|
60
|
+
t.float "review_value_sum", :default => 0.0, :null => false
|
59
61
|
end
|
60
62
|
|
61
63
|
create_table "categories", :force => true do |t|
|
@@ -70,4 +72,12 @@ ActiveRecord::Schema.define(:version => 20120522160158) do
|
|
70
72
|
end
|
71
73
|
add_index "has_string_ids", :id, :unique => true
|
72
74
|
|
75
|
+
create_table "simple_mains", :force => true do |t|
|
76
|
+
t.integer "simple_dependents_count", :null => false, :default => 0
|
77
|
+
end
|
78
|
+
|
79
|
+
create_table "simple_dependents", :force => true do |t|
|
80
|
+
t.integer "simple_main_id"
|
81
|
+
end
|
82
|
+
|
73
83
|
end
|
metadata
CHANGED
@@ -1,36 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: counter_culture
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.15
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Magnus von Koeller
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-10-05 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: rails
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 3.1.0
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: 3.1.0
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rspec
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - ~>
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - ~>
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,23 +41,34 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: after_commit_action
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: awesome_print
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
60
67
|
- !ruby/object:Gem::Version
|
61
68
|
version: '0'
|
62
69
|
- !ruby/object:Gem::Dependency
|
63
70
|
name: rdoc
|
64
71
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
72
|
requirements:
|
67
73
|
- - ~>
|
68
74
|
- !ruby/object:Gem::Version
|
@@ -70,7 +76,6 @@ dependencies:
|
|
70
76
|
type: :development
|
71
77
|
prerelease: false
|
72
78
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
79
|
requirements:
|
75
80
|
- - ~>
|
76
81
|
- !ruby/object:Gem::Version
|
@@ -78,23 +83,20 @@ dependencies:
|
|
78
83
|
- !ruby/object:Gem::Dependency
|
79
84
|
name: bundler
|
80
85
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
86
|
requirements:
|
83
|
-
- -
|
87
|
+
- - '>='
|
84
88
|
- !ruby/object:Gem::Version
|
85
|
-
version: 1.2.0
|
89
|
+
version: 1.2.0
|
86
90
|
type: :development
|
87
91
|
prerelease: false
|
88
92
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
93
|
requirements:
|
91
|
-
- -
|
94
|
+
- - '>='
|
92
95
|
- !ruby/object:Gem::Version
|
93
|
-
version: 1.2.0
|
96
|
+
version: 1.2.0
|
94
97
|
- !ruby/object:Gem::Dependency
|
95
98
|
name: jeweler
|
96
99
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
100
|
requirements:
|
99
101
|
- - ~>
|
100
102
|
- !ruby/object:Gem::Version
|
@@ -102,7 +104,6 @@ dependencies:
|
|
102
104
|
type: :development
|
103
105
|
prerelease: false
|
104
106
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
107
|
requirements:
|
107
108
|
- - ~>
|
108
109
|
- !ruby/object:Gem::Version
|
@@ -110,7 +111,6 @@ dependencies:
|
|
110
111
|
- !ruby/object:Gem::Dependency
|
111
112
|
name: after_commit_action
|
112
113
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
114
|
requirements:
|
115
115
|
- - ~>
|
116
116
|
- !ruby/object:Gem::Version
|
@@ -118,7 +118,6 @@ dependencies:
|
|
118
118
|
type: :runtime
|
119
119
|
prerelease: false
|
120
120
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
121
|
requirements:
|
123
122
|
- - ~>
|
124
123
|
- !ruby/object:Gem::Version
|
@@ -136,7 +135,9 @@ extra_rdoc_files:
|
|
136
135
|
files:
|
137
136
|
- .document
|
138
137
|
- .rspec
|
139
|
-
- .
|
138
|
+
- .ruby-gemset
|
139
|
+
- .ruby-version
|
140
|
+
- CHANGELOG.md
|
140
141
|
- Gemfile
|
141
142
|
- Gemfile.lock
|
142
143
|
- LICENSE.txt
|
@@ -146,6 +147,8 @@ files:
|
|
146
147
|
- circle.yml
|
147
148
|
- counter_culture.gemspec
|
148
149
|
- lib/counter_culture.rb
|
150
|
+
- lib/generators/counter_culture_generator.rb
|
151
|
+
- lib/generators/templates/counter_culture_migration.rb.erb
|
149
152
|
- spec/counter_culture_spec.rb
|
150
153
|
- spec/models/category.rb
|
151
154
|
- spec/models/company.rb
|
@@ -153,6 +156,8 @@ files:
|
|
153
156
|
- spec/models/industry.rb
|
154
157
|
- spec/models/product.rb
|
155
158
|
- spec/models/review.rb
|
159
|
+
- spec/models/simple_dependent.rb
|
160
|
+
- spec/models/simple_main.rb
|
156
161
|
- spec/models/user.rb
|
157
162
|
- spec/rails_app/.gitignore
|
158
163
|
- spec/rails_app/Gemfile
|
@@ -207,29 +212,25 @@ files:
|
|
207
212
|
homepage: http://github.com/bestvendor/counter_culture
|
208
213
|
licenses:
|
209
214
|
- MIT
|
215
|
+
metadata: {}
|
210
216
|
post_install_message:
|
211
217
|
rdoc_options: []
|
212
218
|
require_paths:
|
213
219
|
- lib
|
214
220
|
required_ruby_version: !ruby/object:Gem::Requirement
|
215
|
-
none: false
|
216
221
|
requirements:
|
217
|
-
- -
|
222
|
+
- - '>='
|
218
223
|
- !ruby/object:Gem::Version
|
219
224
|
version: '0'
|
220
|
-
segments:
|
221
|
-
- 0
|
222
|
-
hash: -1126156113488497245
|
223
225
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
224
|
-
none: false
|
225
226
|
requirements:
|
226
|
-
- -
|
227
|
+
- - '>='
|
227
228
|
- !ruby/object:Gem::Version
|
228
229
|
version: '0'
|
229
230
|
requirements: []
|
230
231
|
rubyforge_project:
|
231
|
-
rubygems_version:
|
232
|
+
rubygems_version: 2.0.7
|
232
233
|
signing_key:
|
233
|
-
specification_version:
|
234
|
+
specification_version: 4
|
234
235
|
summary: Turbo-charged counter caches for your Rails app.
|
235
236
|
test_files: []
|
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm 1.9.3@counter_culture
|