counter_culture 1.12.0 → 2.0.0

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: f9d98f26efe6fc7cba57f168e7da8b2d9714df7c
4
- data.tar.gz: 6b0b08fef2199be674313c4bac316b16cc40ac8d
3
+ metadata.gz: 626c6d44c921e9bf854359c6f2360683392c8053
4
+ data.tar.gz: 171c902ee182fbe717f3fee383b2932bc8e31207
5
5
  SHA512:
6
- metadata.gz: f5174d35328f38ace6423f37c183a9d9f377508b50ee31d6e8c13a9af0fe39e563711898176e8c2911ae7e7b8c669c5e32dc9901b1d55d346bb3a3ba0437706a
7
- data.tar.gz: 053cb5c12961aaf86a6bd0dbb43e23ebd1d5a05b2d18c39fac6e741518286ca4509e209a171fa84d1ee411c860228f535e11a7eedf1ff97c5d54a3dc92178d59
6
+ metadata.gz: 34dd015103dc4a1da9c49feeda4009da59662ccfd6dc7d972f7c6c05eb0e4dbd72db452c8dc113dbb50903e854dacd6db112a370890f6572013931742e82b989
7
+ data.tar.gz: 84fd5aefae33c42919e10dd91a8eb4b747d564b38994702f7dfdae76c1810fd5a1d910a5be9cbcf260095838902fed8005bcb360f4447c5c143c1a0d7bdbf778
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 2.0.0 (June 12, 2018)
2
+
3
+ Breaking changes:
4
+ - execute_after_commit was removed
5
+ - Removed workaround for incorrect counts when triggering updates from an `after_create` hook. Your options if this applies to you:
6
+ * continue using counter_culture 1.12.0
7
+ * upgrade to Rails 5.1.5 which fixes the underlying issue in Rails
8
+ * avoid triggering further updates on the same model in `after_create`; simply set the attribute in `before_create` instead
9
+
10
+ Bugfixes:
11
+ - Multiple updates in one transaction will now be processed correctly (#222)
12
+
1
13
  ## 1.12.0 (June 8, 2018)
2
14
 
3
15
  Improvements:
data/README.md CHANGED
@@ -216,16 +216,6 @@ You may also specify a custom timestamp column that gets updated only when a par
216
216
 
217
217
  With this option, any time the `category_counter_cache` changes both the `category_count_changed` and `updated_at` columns will get updated.
218
218
 
219
- ### Executing counter cache updates after commit
220
-
221
- By default, counter_culture will run counter cache updates inside of the same ActiveRecord transaction that triggered it. (Note that this bevavior [changed from version 0.2.3 to 1.0.0](CHANGELOG.md#100-november-15-2016).) If you would like to run counter cache updates outside of that transaction, for example because you are experiencing [deadlocks with older versions of PostgreSQL](http://mina.naguib.ca/blog/2010/11/22/postgresql-foreign-key-deadlocks.html), you can enable that behavior:
222
- ```ruby
223
- counter_culture :category, execute_after_commit: true
224
- ```
225
-
226
- Please note that using `execute_after_commit` in conjunction with transactional
227
- fixtures will lead to your tests no longer seeing updated counter values.
228
-
229
219
  ### Manually populating counter cache values
230
220
 
231
221
  You will sometimes want to populate counter-cache values from primary data. This is required when adding counter-caches to existing data. It is also recommended to run this regularly (at BestVendor, we run it once a week) to catch any incorrect values in the counter caches.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.12.0
1
+ 2.0.0
@@ -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 1.12.0 ruby lib
5
+ # stub: counter_culture 2.0.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "counter_culture"
9
- s.version = "1.12.0"
9
+ s.version = "2.0.0"
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 = "2018-06-08"
14
+ s.date = "2018-06-18"
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, :delta_magnitude, :execute_after_commit ]
3
+ CONFIG_OPTIONS = [ :column_names, :counter_cache_name, :delta_column, :foreign_key_values, :touch, :delta_magnitude]
4
4
  ACTIVE_RECORD_VERSION = Gem.loaded_specs["activerecord"].version
5
5
 
6
6
  attr_reader :model, :relation, *CONFIG_OPTIONS
@@ -9,13 +9,16 @@ module CounterCulture
9
9
  @model = model
10
10
  @relation = relation.is_a?(Enumerable) ? relation : [relation]
11
11
 
12
+ if options.fetch(:execute_after_commit, false)
13
+ fail("execute_after_commit was removed; updates now run within the transaction")
14
+ end
15
+
12
16
  @counter_cache_name = options.fetch(:column_name, "#{model.name.tableize}_count")
13
17
  @column_names = options[:column_names]
14
18
  @delta_column = options[:delta_column]
15
19
  @foreign_key_values = options[:foreign_key_values]
16
20
  @touch = options.fetch(:touch, false)
17
21
  @delta_magnitude = options[:delta_magnitude] || 1
18
- @execute_after_commit = options.fetch(:execute_after_commit, false)
19
22
  @with_papertrail = options.fetch(:with_papertrail, false)
20
23
  end
21
24
 
@@ -29,7 +32,6 @@ module CounterCulture
29
32
  # :delta_column => override the default count delta (1) with the value of this column in the counted record
30
33
  # :was => whether to get the current value or the old value of the
31
34
  # first part of the relation
32
- # :execute_after_commit => execute the column update outside of the transaction to avoid deadlocks
33
35
  # :with_papertrail => update the column via Papertrail touch_with_version method
34
36
  def change_counter_cache(obj, options)
35
37
  change_counter_column = options.fetch(:counter_column) { counter_cache_name_for(obj) }
@@ -45,43 +47,41 @@ module CounterCulture
45
47
  else
46
48
  counter_delta_magnitude_for(obj)
47
49
  end
48
- execute_change_counter_cache(obj, options) do
49
- # increment or decrement?
50
- operator = options[:increment] ? '+' : '-'
51
-
52
- # we don't use Rails' update_counters because we support changing the timestamp
53
- quoted_column = model.connection.quote_column_name(change_counter_column)
54
-
55
- updates = []
56
- # this updates the actual counter
57
- updates << "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{delta_magnitude}"
58
- # and here we update the timestamp, if so desired
59
- if touch
60
- current_time = obj.send(:current_time_from_proper_timezone)
61
- timestamp_columns = obj.send(:timestamp_attributes_for_update_in_model)
62
- timestamp_columns << touch if touch != true
63
- timestamp_columns.each do |timestamp_column|
64
- updates << "#{timestamp_column} = '#{current_time.to_formatted_s(:db)}'"
65
- end
50
+ # increment or decrement?
51
+ operator = options[:increment] ? '+' : '-'
52
+
53
+ # we don't use Rails' update_counters because we support changing the timestamp
54
+ quoted_column = model.connection.quote_column_name(change_counter_column)
55
+
56
+ updates = []
57
+ # this updates the actual counter
58
+ updates << "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{delta_magnitude}"
59
+ # and here we update the timestamp, if so desired
60
+ if touch
61
+ current_time = obj.send(:current_time_from_proper_timezone)
62
+ timestamp_columns = obj.send(:timestamp_attributes_for_update_in_model)
63
+ timestamp_columns << touch if touch != true
64
+ timestamp_columns.each do |timestamp_column|
65
+ updates << "#{timestamp_column} = '#{current_time.to_formatted_s(:db)}'"
66
66
  end
67
+ end
67
68
 
68
- klass = relation_klass(relation, source: obj, was: options[:was])
69
- primary_key = relation_primary_key(relation, source: obj, was: options[:was])
70
-
71
- if @with_papertrail
72
- instance = klass.where(primary_key => id_to_change).first
73
- if instance
74
- if instance.paper_trail.respond_to?(:save_with_version)
75
- # touch_with_version is deprecated starting in PaperTrail 9.0.0
76
- instance.paper_trail.save_with_version(validate: false)
77
- else
78
- instance.paper_trail.touch_with_version
79
- end
69
+ klass = relation_klass(relation, source: obj, was: options[:was])
70
+ primary_key = relation_primary_key(relation, source: obj, was: options[:was])
71
+
72
+ if @with_papertrail
73
+ instance = klass.where(primary_key => id_to_change).first
74
+ if instance
75
+ if instance.paper_trail.respond_to?(:save_with_version)
76
+ # touch_with_version is deprecated starting in PaperTrail 9.0.0
77
+ instance.paper_trail.save_with_version(validate: false)
78
+ else
79
+ instance.paper_trail.touch_with_version
80
80
  end
81
81
  end
82
-
83
- klass.where(primary_key => id_to_change).update_all updates.join(', ')
84
82
  end
83
+
84
+ klass.where(primary_key => id_to_change).update_all updates.join(', ')
85
85
  end
86
86
  end
87
87
 
@@ -278,14 +278,6 @@ module CounterCulture
278
278
  end
279
279
 
280
280
  private
281
- def execute_change_counter_cache(obj, options)
282
- if execute_after_commit
283
- obj.execute_after_commit { yield }
284
- else
285
- yield
286
- end
287
- end
288
-
289
281
  def attribute_was(obj, attr)
290
282
  changes_method =
291
283
  if ACTIVE_RECORD_VERSION >= Gem::Version.new("5.1.0")
@@ -89,61 +89,38 @@ module CounterCulture
89
89
  end
90
90
 
91
91
  private
92
- # need to make sure counter_culture is only activated once
93
- # per commit; otherwise, if we do an update in an after_create,
94
- # we would be triggered twice within the same transaction -- once
95
- # for the create, once for the update
96
- def _wrap_in_counter_culture_active(&block)
97
- if @_counter_culture_active
98
- # don't do anything; we are already active for this transaction
99
- else
100
- block.call
101
- execute_after_commit { @_counter_culture_active = false}
102
- end
103
- end
104
-
105
92
  # called by after_create callback
106
93
  def _update_counts_after_create
107
- _wrap_in_counter_culture_active do
108
- @_counter_culture_active = true
109
- self.class.after_commit_counter_cache.each do |counter|
110
- # increment counter cache
111
- counter.change_counter_cache(self, :increment => true)
112
- end
94
+ self.class.after_commit_counter_cache.each do |counter|
95
+ # increment counter cache
96
+ counter.change_counter_cache(self, :increment => true)
113
97
  end
114
98
  end
115
99
 
116
100
  # called by after_destroy callback
117
101
  def _update_counts_after_destroy
118
- _wrap_in_counter_culture_active do
119
- @_counter_culture_active = true
120
- self.class.after_commit_counter_cache.each do |counter|
121
- # decrement counter cache
122
- counter.change_counter_cache(self, :increment => false)
123
- end
102
+ self.class.after_commit_counter_cache.each do |counter|
103
+ # decrement counter cache
104
+ counter.change_counter_cache(self, :increment => false)
124
105
  end
125
106
  end
126
107
 
127
108
  # called by after_update callback
128
109
  def _update_counts_after_update
129
- _wrap_in_counter_culture_active do
130
- self.class.after_commit_counter_cache.each do |counter|
131
- # figure out whether the applicable counter cache changed (this can happen
132
- # with dynamic column names)
133
- counter_cache_name_was = counter.counter_cache_name_for(counter.previous_model(self))
134
- counter_cache_name = counter.counter_cache_name_for(self)
135
-
136
- if counter.first_level_relation_changed?(self) ||
137
- (counter.delta_column && counter.attribute_changed?(self, counter.delta_column)) ||
138
- counter_cache_name != counter_cache_name_was
139
-
140
- @_counter_culture_active = true
141
-
142
- # increment the counter cache of the new value
143
- counter.change_counter_cache(self, :increment => true, :counter_column => counter_cache_name)
144
- # decrement the counter cache of the old value
145
- counter.change_counter_cache(self, :increment => false, :was => true, :counter_column => counter_cache_name_was)
146
- end
110
+ self.class.after_commit_counter_cache.each do |counter|
111
+ # figure out whether the applicable counter cache changed (this can happen
112
+ # with dynamic column names)
113
+ counter_cache_name_was = counter.counter_cache_name_for(counter.previous_model(self))
114
+ counter_cache_name = counter.counter_cache_name_for(self)
115
+
116
+ if counter.first_level_relation_changed?(self) ||
117
+ (counter.delta_column && counter.attribute_changed?(self, counter.delta_column)) ||
118
+ counter_cache_name != counter_cache_name_was
119
+
120
+ # increment the counter cache of the new value
121
+ counter.change_counter_cache(self, :increment => true, :counter_column => counter_cache_name)
122
+ # decrement the counter cache of the old value
123
+ counter.change_counter_cache(self, :increment => false, :was => true, :counter_column => counter_cache_name_was)
147
124
  end
148
125
  end
149
126
  end
@@ -148,6 +148,34 @@ describe "CounterCulture" do
148
148
  expect(user2.reload.review_approvals_count).to eq(69)
149
149
  end
150
150
 
151
+ it "works with multiple saves in one transcation" do
152
+ user = User.create
153
+ product = Product.create
154
+
155
+ expect(user.reviews_count).to eq(0)
156
+ expect(user.review_approvals_count).to eq(0)
157
+
158
+ Review.transaction do
159
+ review1 = Review.create!(user_id: user.id, product_id: product.id, approvals: 0)
160
+
161
+ user.reload
162
+ expect(user.reviews_count).to eq(1)
163
+ expect(user.review_approvals_count).to eq(0)
164
+
165
+ review1.update_attributes!(approvals: 42)
166
+
167
+ user.reload
168
+ expect(user.reviews_count).to eq(1)
169
+ expect(user.review_approvals_count).to eq(42)
170
+
171
+ review2 = Review.create!(user_id: user.id, product_id: product.id, approvals: 1)
172
+
173
+ user.reload
174
+ expect(user.reviews_count).to eq(2)
175
+ expect(user.review_approvals_count).to eq(43)
176
+ end
177
+ end
178
+
151
179
  it "treats null delta column values as 0" do
152
180
  user = User.create
153
181
  product = Product.create
@@ -20,6 +20,7 @@ class Review < ActiveRecord::Base
20
20
  after_create :update_some_text
21
21
 
22
22
  def update_some_text
23
+ return unless Gem::Version.new(Rails.version) >= Gem::Version.new('5.1.5')
23
24
  update_attribute(:some_text, rand(36**12).to_s(36))
24
25
  end
25
26
 
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: 1.12.0
4
+ version: 2.0.0
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: 2018-06-08 00:00:00.000000000 Z
11
+ date: 2018-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: after_commit_action