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 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 "http://rubygems.org"
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", "~> 1.2.0.pre"
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: http://rubygems.org/
2
+ remote: https://rubygems.org/
3
3
  specs:
4
- actionmailer (3.2.10)
5
- actionpack (= 3.2.10)
6
- mail (~> 2.4.4)
7
- actionpack (3.2.10)
8
- activemodel (= 3.2.10)
9
- activesupport (= 3.2.10)
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
- journey (~> 1.0.4)
13
- rack (~> 1.4.0)
14
- rack-cache (~> 1.2)
15
- rack-test (~> 0.6.1)
16
- sprockets (~> 2.2.1)
17
- activemodel (3.2.10)
18
- activesupport (= 3.2.10)
19
- builder (~> 3.0.0)
20
- activerecord (3.2.10)
21
- activemodel (= 3.2.10)
22
- activesupport (= 3.2.10)
23
- arel (~> 3.0.2)
24
- tzinfo (~> 0.3.29)
25
- activeresource (3.2.10)
26
- activemodel (= 3.2.10)
27
- activesupport (= 3.2.10)
28
- activesupport (3.2.10)
29
- i18n (~> 0.6)
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 (3.0.2)
34
- builder (3.0.4)
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
- git (1.2.5)
38
- hike (1.2.1)
39
- i18n (0.6.1)
40
- jeweler (1.8.4)
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
- journey (1.0.4)
46
- json (1.7.6)
47
- mail (2.4.4)
48
- i18n (>= 0.4.0)
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.19)
52
- multi_json (1.5.0)
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.4.3)
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 (3.2.10)
62
- actionmailer (= 3.2.10)
63
- actionpack (= 3.2.10)
64
- activerecord (= 3.2.10)
65
- activeresource (= 3.2.10)
66
- activesupport (= 3.2.10)
67
- bundler (~> 1.0)
68
- railties (= 3.2.10)
69
- railties (3.2.10)
70
- actionpack (= 3.2.10)
71
- activesupport (= 3.2.10)
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
- rdoc (~> 3.4)
75
- thor (>= 0.14.6, < 2.0)
76
- rake (10.0.3)
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.2.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
- sqlite3 (1.3.6)
94
- thor (0.16.0)
95
- tilt (1.3.3)
96
- treetop (1.4.12)
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.35)
127
+ tzinfo (0.3.37)
100
128
 
101
129
  PLATFORMS
102
130
  ruby
103
131
 
104
132
  DEPENDENCIES
105
133
  after_commit_action
106
- bundler (~> 1.2.0.pre)
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 will need to manually create the necessary columns for all counter caches. Use code like the following in your migration:
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
- It is important to make the column ```NOT NULL``` and set a default of zero for this gem to work correctly.
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/bestvendor/counter_culture/issues/4) for progress and alternatives.
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.14
1
+ 0.1.15
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "counter_culture"
8
- s.version = "0.1.14"
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-08-13"
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
- ".rvmrc",
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 = "1.8.25"
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 = 3
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>, ["~> 1.2.0.pre"])
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>, ["~> 1.2.0.pre"])
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>, ["~> 1.2.0.pre"])
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
@@ -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
- 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")
81
- query = query.group("#{klass.table_name}.id")
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
- counts = query.where(where)
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
- counts = counts.joins("JOIN #{reflect.active_record.table_name} ON #{reflect.table_name}.id = #{reflect.active_record.table_name}.#{reflect.foreign_key}")
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
- # and then we collect the counts in an id => count hash
105
- counts = counts.inject({}){|memo, model| memo[model.id] = model.count.to_i; memo}
106
-
107
- # now that we know what the correct counts are, we need to iterate over all instances
108
- # and check whether the count is correct; if not, we correct it
109
- klass.find_each do |model|
110
- if model.read_attribute(column_name) != counts[model.id].to_i
111
- # keep track of what we fixed, e.g. for a notification email
112
- fixed<< {
113
- :entity => klass.name,
114
- :id => model.id,
115
- :what => column_name,
116
- :wrong => model.send(column_name),
117
- :right => counts[model.id]
118
- }
119
- # use update_all because it's faster and because a fixed counter-cache shouldn't
120
- # update the timestamp
121
- klass.where(:id => model.id).update_all(column_name => counts[model.id].to_i)
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).to_i
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"}, :without_protection => true)
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({:id => "1"}, :without_protection => true)
1004
- string_id2 = HasStringId.create({:id => "abc"}, :without_protection => true)
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}
@@ -1,4 +1,4 @@
1
1
  class HasStringId < ActiveRecord::Base
2
- set_primary_key :id
2
+ self.primary_key = :id
3
3
  has_many :users
4
4
  end
@@ -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]
@@ -0,0 +1,5 @@
1
+ class SimpleDependent < ActiveRecord::Base
2
+ belongs_to :simple_main
3
+
4
+ counter_culture :simple_main
5
+ end
@@ -0,0 +1,3 @@
1
+ class SimpleMain < ActiveRecord::Base
2
+ has_many :simple_dependents
3
+ end
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.14
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-08-13 00:00:00.000000000 Z
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.pre
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.pre
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
- - .rvmrc
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: 1.8.25
232
+ rubygems_version: 2.0.7
232
233
  signing_key:
233
- specification_version: 3
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