counter_culture 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -19,4 +19,5 @@ end
19
19
 
20
20
  group :test do
21
21
  gem "sqlite3"
22
+ gem "rspec-extra-formatters"
22
23
  end
data/Gemfile.lock CHANGED
@@ -83,6 +83,7 @@ GEM
83
83
  rspec-core (2.10.1)
84
84
  rspec-expectations (2.10.0)
85
85
  diff-lcs (~> 1.1.3)
86
+ rspec-extra-formatters (0.4)
86
87
  rspec-mocks (2.10.1)
87
88
  sprockets (2.1.3)
88
89
  hike (~> 1.2)
@@ -106,4 +107,5 @@ DEPENDENCIES
106
107
  rails
107
108
  rdoc (~> 3.12)
108
109
  rspec (~> 2.10.0)
110
+ rspec-extra-formatters
109
111
  sqlite3
data/README.md CHANGED
@@ -12,7 +12,7 @@ Turbo-charged counter caches for your Rails app. Huge improvements over the Rail
12
12
  Add counter_culture to your Gemfile:
13
13
 
14
14
  ```ruby
15
- gem 'counter_culture', '~> 0.1.4'
15
+ gem 'counter_culture', '~> 0.1.7'
16
16
  ```
17
17
 
18
18
  Then run ```bundle update ```
@@ -95,6 +95,21 @@ end
95
95
 
96
96
  Now, the ```Category``` model will keep two up-to-date counter-caches in the ```awesome_count``` and ```sucky_count``` columns of the ```categories``` table. Products with type ```'awesome'``` will affect only the ```awesome_count```, while products with type ```'sucky'``` will affect only the ```sucky_count```. This will also work with multi-level counter caches.
97
97
 
98
+ ### Conditional counter cache
99
+
100
+ ```ruby
101
+ class Product < ActiveRecord::Base
102
+ belongs_to :category
103
+ counter_culture :category, :column_name => Proc.new {|model| model.special? ? 'special_count' : nil }
104
+ end
105
+
106
+ class Category < ActiveRecord::Base
107
+ has_many :products
108
+ end
109
+ ```
110
+
111
+ Now, the ```Category``` model will keep the counter cache in ```special_count``` up-to-date. Only products where ```special?``` returns true will affect the special_count.
112
+
98
113
  ### Dynamically over-writing affected foreign keys
99
114
 
100
115
  ```ruby
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.6
1
+ 0.1.7
data/circle.yml ADDED
@@ -0,0 +1,3 @@
1
+ database:
2
+ override:
3
+ - echo "No database setup required..."
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "counter_culture"
8
- s.version = "0.1.6"
8
+ s.version = "0.1.7"
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 = "2012-06-07"
12
+ s.date = "2012-09-17"
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 = [
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
  "README.md",
27
27
  "Rakefile",
28
28
  "VERSION",
29
+ "circle.yml",
29
30
  "counter_culture.gemspec",
30
31
  "lib/counter_culture.rb",
31
32
  "spec/counter_culture_spec.rb",
@@ -89,7 +90,7 @@ Gem::Specification.new do |s|
89
90
  s.homepage = "http://github.com/bestvendor/counter_culture"
90
91
  s.licenses = ["MIT"]
91
92
  s.require_paths = ["lib"]
92
- s.rubygems_version = "1.8.21"
93
+ s.rubygems_version = "1.8.24"
93
94
  s.summary = "Turbo-charged counter caches for your Rails app."
94
95
 
95
96
  if s.respond_to? :specification_version then
@@ -174,7 +174,7 @@ module CounterCulture
174
174
  def _update_counts_after_create
175
175
  self.class.after_commit_counter_cache.each do |hash|
176
176
  # increment counter cache
177
- change_counter_cache(true, hash)
177
+ change_counter_cache(hash.merge(:increment => true))
178
178
  end
179
179
  end
180
180
 
@@ -182,55 +182,79 @@ module CounterCulture
182
182
  def _update_counts_after_destroy
183
183
  self.class.after_commit_counter_cache.each do |hash|
184
184
  # decrement counter cache
185
- change_counter_cache(false, hash)
185
+ change_counter_cache(hash.merge(:increment => false))
186
186
  end
187
187
  end
188
188
 
189
189
  # called by after_update callback
190
190
  def _update_counts_after_update
191
191
  self.class.after_commit_counter_cache.each do |hash|
192
- # only update counter caches if the foreign key changed
193
- if send("#{first_level_relation_foreign_key(hash[:relation])}_changed?")
192
+ # figure out whether the applicable counter cache changed (this can happen
193
+ # with dynamic column names)
194
+ counter_cache_name_was = counter_cache_name_for(previous_model, hash[:counter_cache_name])
195
+ counter_cache_name = counter_cache_name_for(self, hash[:counter_cache_name])
196
+
197
+ if send("#{first_level_relation_foreign_key(hash[:relation])}_changed?") || counter_cache_name != counter_cache_name_was
194
198
  # increment the counter cache of the new value
195
- change_counter_cache(true, hash)
199
+ change_counter_cache(hash.merge(:increment => true, :counter_column => counter_cache_name))
196
200
  # decrement the counter cache of the old value
197
- change_counter_cache(false, hash, true)
201
+ change_counter_cache(hash.merge(:increment => false, :was => true, :counter_column => counter_cache_name_was))
198
202
  end
199
203
  end
200
204
  end
201
205
 
202
206
  # increments or decrements a counter cache
203
207
  #
204
- # increment: true to increment, false to decrement
205
- # hash:
208
+ # options:
209
+ # :increment => true to increment, false to decrement
206
210
  # :relation => which relation to increment the count on,
207
211
  # :counter_cache_name => the column name of the counter cache
208
- # was: whether to get the current value or the old value of the
209
- # first part of the relation
210
- def change_counter_cache(increment, hash, was = false)
212
+ # :counter_column => overrides :counter_cache_name
213
+ # :was => whether to get the current value or the old value of the
214
+ # first part of the relation
215
+ def change_counter_cache(options)
216
+ options[:counter_column] = counter_cache_name_for(self, options[:counter_cache_name]) unless options.has_key?(:counter_column)
217
+
211
218
  # default to the current foreign key value
212
- id_to_change = foreign_key_value(hash[:relation], was)
219
+ id_to_change = foreign_key_value(options[:relation], options[:was])
213
220
  # allow overwriting of foreign key value by the caller
214
- id_to_change = hash[:foreign_key_values].call(id_to_change) if hash[:foreign_key_values]
221
+ id_to_change = options[:foreign_key_values].call(id_to_change) if options[:foreign_key_values]
222
+
223
+ if id_to_change && options[:counter_column]
224
+ execute_after_commit do
225
+ # increment or decrement?
226
+ method = options[:increment] ? :increment_counter : :decrement_counter
227
+
228
+ # do it!
229
+ relation_klass(options[:relation]).send(method, options[:counter_column], id_to_change)
230
+ end
231
+ end
232
+ end
215
233
 
234
+ # Gets the name of the counter cache for a specific object
235
+ #
236
+ # obj: object to calculate the counter cache name for
237
+ # cache_name_finder: object used to calculate the cache name
238
+ def counter_cache_name_for(obj, cache_name_finder)
216
239
  # figure out what the column name is
217
- if hash[:counter_cache_name].is_a? Proc
240
+ if cache_name_finder.is_a? Proc
218
241
  # dynamic column name -- call the Proc
219
- counter_cache_name = hash[:counter_cache_name].call(self)
242
+ cache_name_finder.call(obj)
220
243
  else
221
244
  # static column name
222
- counter_cache_name = hash[:counter_cache_name]
245
+ cache_name_finder
223
246
  end
247
+ end
224
248
 
225
- if id_to_change && counter_cache_name
226
- execute_after_commit do
227
- # increment or decrement?
228
- method = increment ? :increment_counter : :decrement_counter
229
-
230
- # do it!
231
- relation_klass(hash[:relation]).send(method, counter_cache_name, id_to_change)
232
- end
249
+ # Creates a copy of the current model with changes rolled back
250
+ def previous_model
251
+ prev = self.dup
252
+
253
+ self.changed_attributes.each_pair do |key, value|
254
+ prev.send("#{key}=".to_sym, value)
233
255
  end
256
+
257
+ prev
234
258
  end
235
259
 
236
260
  # gets the value of the foreign key on the given relation
@@ -298,6 +298,68 @@ describe "CounterCulture" do
298
298
  user2.using_count.should == 0
299
299
  user2.tried_count.should == 0
300
300
  end
301
+
302
+ describe "conditional counts on update" do
303
+ let(:product) {Product.create!}
304
+ let(:user) {User.create!}
305
+
306
+ it "should increment and decrement if changing column name" do
307
+ user.using_count.should == 0
308
+ user.tried_count.should == 0
309
+
310
+ review = Review.create :user_id => user.id, :product_id => product.id, :review_type => "using"
311
+ user.reload
312
+
313
+ user.using_count.should == 1
314
+ user.tried_count.should == 0
315
+
316
+ review.review_type = "tried"
317
+ review.save!
318
+
319
+ user.reload
320
+
321
+ user.using_count.should == 0
322
+ user.tried_count.should == 1
323
+ end
324
+
325
+ it "should increment if changing from a nil column name" do
326
+ user.using_count.should == 0
327
+ user.tried_count.should == 0
328
+
329
+ review = Review.create :user_id => user.id, :product_id => product.id, :review_type => nil
330
+ user.reload
331
+
332
+ user.using_count.should == 0
333
+ user.tried_count.should == 0
334
+
335
+ review.review_type = "tried"
336
+ review.save!
337
+
338
+ user.reload
339
+
340
+ user.using_count.should == 0
341
+ user.tried_count.should == 1
342
+ end
343
+
344
+ it "should decrement if changing column name to nil" do
345
+ user.using_count.should == 0
346
+ user.tried_count.should == 0
347
+
348
+ review = Review.create :user_id => user.id, :product_id => product.id, :review_type => "using"
349
+ user.reload
350
+
351
+ user.using_count.should == 1
352
+ user.tried_count.should == 0
353
+
354
+ review.review_type = nil
355
+ review.save!
356
+
357
+ user.reload
358
+
359
+ user.using_count.should == 0
360
+ user.tried_count.should == 0
361
+ end
362
+ end
301
363
 
302
364
  it "increments third-level counter cache on create" do
303
365
  industry = Industry.create
@@ -799,5 +861,21 @@ describe "CounterCulture" do
799
861
  user.using_count.should == 1
800
862
  user.tried_count.should == 1
801
863
  end
864
+
865
+ describe "#previous_model" do
866
+ let(:user){User.create :name => "John Smith", :company_id => 1}
867
+
868
+ it "should return a copy of the original model" do
869
+ user.name = "Joe Smith"
870
+ user.company_id = 2
871
+ prev = user.send(:previous_model)
872
+
873
+ prev.name.should == "John Smith"
874
+ prev.company_id.should == 1
875
+
876
+ user.name.should =="Joe Smith"
877
+ user.company_id.should == 2
878
+ end
879
+ end
802
880
 
803
881
  end
@@ -9,5 +9,5 @@ class Review < ActiveRecord::Base
9
9
  counter_culture [:user, :company]
10
10
  counter_culture [:user, :company, :industry]
11
11
  counter_culture [:user, :company, :industry], :column_name => 'rexiews_count'
12
- counter_culture [:user, :company, :industry], :column_name => Proc.new { |model| "#{model.review_type}_count" }
12
+ counter_culture [:user, :company, :industry], :column_name => Proc.new { |model| model.review_type ? "#{model.review_type}_count" : nil }
13
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: counter_culture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-07 00:00:00.000000000 Z
12
+ date: 2012-09-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -143,6 +143,7 @@ files:
143
143
  - README.md
144
144
  - Rakefile
145
145
  - VERSION
146
+ - circle.yml
146
147
  - counter_culture.gemspec
147
148
  - lib/counter_culture.rb
148
149
  - spec/counter_culture_spec.rb
@@ -217,7 +218,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
217
218
  version: '0'
218
219
  segments:
219
220
  - 0
220
- hash: -2752242088378468016
221
+ hash: -255493455800926186
221
222
  required_rubygems_version: !ruby/object:Gem::Requirement
222
223
  none: false
223
224
  requirements:
@@ -226,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
227
  version: '0'
227
228
  requirements: []
228
229
  rubyforge_project:
229
- rubygems_version: 1.8.21
230
+ rubygems_version: 1.8.24
230
231
  signing_key:
231
232
  specification_version: 3
232
233
  summary: Turbo-charged counter caches for your Rails app.