counter_culture 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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