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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cfcc2ed5c7a746c8e7bc6c737ba4cf352f026924
4
- data.tar.gz: 06fc27fd9c7c0799f71387717cf087606897df18
3
+ metadata.gz: 31d3e78549aeff7e69173ac1c5723b00be56ef8f
4
+ data.tar.gz: 997a2d03fb2b208894c80c1282ef7094241b12a2
5
5
  SHA512:
6
- metadata.gz: 8f152caccc5b4cfaa49aef5a33a94234555384e1dc1ca7f8bfbe7169c15505bfd44d713015cdb32da10a1840685789782f2b8c286792da6e4e7756d7b1238039
7
- data.tar.gz: ad3d4bfa9a158cafaeab6381b973f987f2380f23c965562345e6ba48d49648167e06177db0f81f28531d62079e57c29a4bf19a89a8b85aac748cf926feb493cb
6
+ metadata.gz: 3bac32314498b675600b72742d7cb5c317666de5d6bec07b377f8ed63b295f20ff919639136373527f725882ed03b369efb37639e0a55dce30c728614c33cd0b
7
+ data.tar.gz: 556109999f9a2f28dc04250e15e89e11420d3194f0b45bf580fe46fed2fbd9fe7e174a1e7705f27ad3dd9b678e07632bf0b003690794584a11cbf8e86210c179
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.2.1 (June 15, 2016)
2
+
3
+ Improvements:
4
+ - Add [`:delta_magnitude` option](https://github.com/magnusvk/counter_culture#dynamic-delta-magnitude)
5
+
1
6
  ## 0.2.0 (April 22, 2016)
2
7
 
3
8
  Improvments:
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 => Proc.new {|model| "#{model.product_type}_count" }
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
- 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.
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 => Proc.new {|model| model.special? ? 'special_count' : nil }
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
- Proc.new {|category_id| [category_id, Category.find_by_id(category_id).try(:parent_category).try(:id)] }
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 => Proc.new {|model| "#{model.product_type}_count" },
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.0
1
+ 0.2.1
@@ -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.0 ruby lib
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.0"
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-04-22"
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
- 1
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 ||= delta_column ? "SUM(COALESCE(#{self_table_name}.#{delta_column},0))" : "COUNT(#{self_table_name}.#{model.primary_key})"
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: Proc.new {|m| m.condition? ? 'conditional_dependents_count' : nil },
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
@@ -1,5 +1,6 @@
1
1
  class Product < ActiveRecord::Base
2
2
  belongs_to :category
3
3
 
4
- counter_culture :category, :foreign_key_values => Proc.new {|foreign_key_value| Category.pluck(:id) }
4
+ counter_culture :category, :foreign_key_values => proc {|foreign_key_value| Category.pluck(:id) }
5
+
5
6
  end
@@ -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 => 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'}
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 => Proc.new { |model| model.review_type ? "#{model.review_type}_count" : nil }
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
@@ -1,4 +1,6 @@
1
1
  class TwitterReview < Review
2
2
  counter_culture :product, column_name: 'twitter_reviews_count'
3
+
3
4
  counter_culture [:user, :manages_company]
5
+
4
6
  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.0
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-04-22 00:00:00.000000000 Z
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