counter-cache 0.1.0 → 0.3.0

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: 65f431cac9a84b8abe458919608c07c66501c68e
4
- data.tar.gz: c0582fc1f4d829fc50162ce50746b33fdf06f2c8
3
+ metadata.gz: 2f919c6ae2950453f8c265ef31e5f60cbe492c47
4
+ data.tar.gz: baaf7090d810fe10ced19ce76928ce5d911f250a
5
5
  SHA512:
6
- metadata.gz: db21a9548ac98191ddf559ff962c78f221656998efbe5f1a0c2af14e153e7256647fa3f54bc4f00faa8b3d34b9feabd9f94d6f82410805b241eb3384593ba1a9
7
- data.tar.gz: 8b4abafb442cbdedef2ddffae83ebc7d71a418dedbef791ad3f963abc7c039b45e44fa8d4d2d992514da60091e8d555932439555c319f33923ac0fc12271f5a4
6
+ metadata.gz: f800e2e2fd6dbd1c9c60a6dd9f9a91fd84f1b42960e961631e8e23ae9ed5dd849cd6b662bcecd9c46db8e0772e13c9576465c8ac0e5839552c466b3746131957
7
+ data.tar.gz: 38275d8b622ebe622e2106a1b8118a4cb89901409e740ee67611fc485479535127adb55e74b565f94b96eb478337619b2d1975a3d0306a75d679de6544050ab0
data/.travis.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  rvm:
2
- - "1.9.3"
3
2
  - "2.1.1"
3
+ - "2.3.0"
4
4
  gemfile:
5
5
  - gemfiles/activerecord_3.gemfile
6
6
  - gemfiles/activerecord_4.0.gemfile
data/README.md CHANGED
@@ -1,9 +1,25 @@
1
- # Counter::Cache
1
+ # Counter::Cache
2
2
 
3
- [![Build Status](https://travis-ci.org/wanelo/counter-cache.svg?branch=master)](https://travis-ci.org/wanelo/counter-cache)
3
+ [![Gem Version](https://badge.fury.io/rb/counter-cache.png)](http://badge.fury.io/rb/ventable)
4
+ [![Build Status](https://travis-ci.org/wanelo/counter-cache.svg?branch=master&fl)](https://travis-ci.org/wanelo/counter-cache)
5
+ [![Code Climate](https://codeclimate.com/github/wanelo/counter-cache.png)](https://codeclimate.com/github/wanelo/counter-cache)
4
6
 
5
7
  Counting things is hard, counting them at scale is even harder, so control when things are counted.
6
8
 
9
+ Any time your application performs pagination, the underlying library probably issues a `select count(*) from ...`
10
+ request to the database, because all paginators need to know how many pages there are. This works on a small-to-medium
11
+ dataset, and in an application with relatively low web traffic. But at high traffic volume, live counts saturate CPU on the
12
+ database server. This is because sorting typically happens on CPU of the database server, using a small amount of heap
13
+ RAM or even worse — using temp files, which grinds the disk IO to a hault. Web requests become slower and
14
+ slower, start to pile up in various queues, and eventually saturate all of the app servers.
15
+ There you are, the site is down.
16
+
17
+ This gem provides a solution that works at scale, and will help you keep your site up.
18
+
19
+ This library is battle-tested at Wanelo, where it has been running for several years.
20
+
21
+ ### Overview
22
+
7
23
  [Rails Counter Caches](http://railscasts.com/episodes/23-counter-cache-column) are a convenient way to keep counters on
8
24
  models that have many children. Without them, you always do live counts, which do not scale. But at high scale, Rails
9
25
  counter caches create update contention on singe models, especially for social sites where any single model might become
@@ -98,6 +114,19 @@ Counter caches are configured on the models from the perspective of the child mo
98
114
 
99
115
  #### Basic Counter with recalculation:
100
116
 
117
+ ```ruby
118
+ class Post
119
+ include Counter::Cache
120
+
121
+ counter_cache_on column: :posts_count, # users.posts_count
122
+ relation: :user,
123
+ relation_class_name: "User",
124
+ method: :calculate_posts_count # This is a method on the user.
125
+ end
126
+ ```
127
+
128
+ #### To allow a timestamp to be updated:
129
+
101
130
  ```ruby
102
131
  class Post
103
132
  include Counter::Cache
@@ -106,6 +135,7 @@ class Post
106
135
  relation: :user,
107
136
  relation_class_name: "User",
108
137
  method: :calculate_posts_count, # This is a method on the user.
138
+ touch_column: :posts_updated_at
109
139
  end
110
140
  ```
111
141
 
data/Rakefile CHANGED
@@ -2,5 +2,9 @@ require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
-
6
5
  task :default => :spec
6
+ task :all do
7
+ sh 'bundle exec appraisal install'
8
+ sh 'bundle exec appraisal rspec'
9
+ end
10
+
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- counter-cache (0.0.2)
4
+ counter-cache (0.3.0)
5
5
  activerecord (>= 3.0)
6
6
 
7
7
  GEM
@@ -90,3 +90,6 @@ DEPENDENCIES
90
90
  rake
91
91
  rspec (>= 3.0)
92
92
  sqlite3
93
+
94
+ BUNDLED WITH
95
+ 1.11.2
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- counter-cache (0.0.2)
4
+ counter-cache (0.3.0)
5
5
  activerecord (>= 3.0)
6
6
 
7
7
  GEM
@@ -96,3 +96,6 @@ DEPENDENCIES
96
96
  rake
97
97
  rspec (>= 3.0)
98
98
  sqlite3
99
+
100
+ BUNDLED WITH
101
+ 1.11.2
@@ -1,84 +1,91 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- counter-cache (0.0.2)
4
+ counter-cache (0.3.0)
5
5
  activerecord (>= 3.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- activemodel (4.1.4)
11
- activesupport (= 4.1.4)
10
+ activemodel (4.1.14.1)
11
+ activesupport (= 4.1.14.1)
12
12
  builder (~> 3.1)
13
- activerecord (4.1.4)
14
- activemodel (= 4.1.4)
15
- activesupport (= 4.1.4)
13
+ activerecord (4.1.14.1)
14
+ activemodel (= 4.1.14.1)
15
+ activesupport (= 4.1.14.1)
16
16
  arel (~> 5.0.0)
17
- activesupport (4.1.4)
17
+ activesupport (4.1.14.1)
18
18
  i18n (~> 0.6, >= 0.6.9)
19
19
  json (~> 1.7, >= 1.7.7)
20
20
  minitest (~> 5.1)
21
21
  thread_safe (~> 0.1)
22
22
  tzinfo (~> 1.1)
23
- appraisal (1.0.0)
23
+ appraisal (2.1.0)
24
24
  bundler
25
25
  rake
26
26
  thor (>= 0.14.0)
27
27
  arel (5.0.1.20140414130214)
28
28
  builder (3.2.2)
29
- celluloid (0.15.2)
30
- timers (~> 1.1.0)
31
29
  coderay (1.1.0)
32
30
  diff-lcs (1.2.5)
33
31
  fakeredis (0.5.0)
34
32
  redis (~> 3.0)
35
- ffi (1.9.3)
33
+ ffi (1.9.10)
36
34
  formatador (0.2.5)
37
- guard (2.6.1)
35
+ guard (2.13.0)
38
36
  formatador (>= 0.2.4)
39
- listen (~> 2.7)
37
+ listen (>= 2.7, <= 4.0)
40
38
  lumberjack (~> 1.0)
39
+ nenv (~> 0.1)
40
+ notiffany (~> 0.0)
41
41
  pry (>= 0.9.12)
42
+ shellany (~> 0.0)
42
43
  thor (>= 0.18.1)
43
- guard-rspec (4.3.1)
44
+ guard-compat (1.2.1)
45
+ guard-rspec (4.6.4)
44
46
  guard (~> 2.1)
45
- rspec (>= 2.14, < 4.0)
46
- i18n (0.6.11)
47
- json (1.8.1)
48
- listen (2.7.9)
49
- celluloid (>= 0.15.2)
47
+ guard-compat (~> 1.1)
48
+ rspec (>= 2.99.0, < 4.0)
49
+ i18n (0.7.0)
50
+ json (1.8.3)
51
+ listen (3.0.5)
50
52
  rb-fsevent (>= 0.9.3)
51
53
  rb-inotify (>= 0.9)
52
- lumberjack (1.0.9)
54
+ lumberjack (1.0.10)
53
55
  method_source (0.8.2)
54
- minitest (5.4.0)
55
- pry (0.10.0)
56
+ minitest (5.8.4)
57
+ nenv (0.2.0)
58
+ notiffany (0.0.8)
59
+ nenv (~> 0.1)
60
+ shellany (~> 0.0)
61
+ pry (0.10.3)
56
62
  coderay (~> 1.1.0)
57
63
  method_source (~> 0.8.1)
58
64
  slop (~> 3.4)
59
- rake (10.3.2)
60
- rb-fsevent (0.9.4)
65
+ rake (10.5.0)
66
+ rb-fsevent (0.9.7)
61
67
  rb-inotify (0.9.5)
62
68
  ffi (>= 0.5.0)
63
- redis (3.1.0)
64
- rspec (3.0.0)
65
- rspec-core (~> 3.0.0)
66
- rspec-expectations (~> 3.0.0)
67
- rspec-mocks (~> 3.0.0)
68
- rspec-core (3.0.3)
69
- rspec-support (~> 3.0.0)
70
- rspec-expectations (3.0.3)
69
+ redis (3.2.2)
70
+ rspec (3.4.0)
71
+ rspec-core (~> 3.4.0)
72
+ rspec-expectations (~> 3.4.0)
73
+ rspec-mocks (~> 3.4.0)
74
+ rspec-core (3.4.2)
75
+ rspec-support (~> 3.4.0)
76
+ rspec-expectations (3.4.0)
71
77
  diff-lcs (>= 1.2.0, < 2.0)
72
- rspec-support (~> 3.0.0)
73
- rspec-mocks (3.0.3)
74
- rspec-support (~> 3.0.0)
75
- rspec-support (3.0.3)
78
+ rspec-support (~> 3.4.0)
79
+ rspec-mocks (3.4.1)
80
+ diff-lcs (>= 1.2.0, < 2.0)
81
+ rspec-support (~> 3.4.0)
82
+ rspec-support (3.4.1)
83
+ shellany (0.0.1)
76
84
  slop (3.6.0)
77
- sqlite3 (1.3.9)
85
+ sqlite3 (1.3.11)
78
86
  thor (0.19.1)
79
- thread_safe (0.3.4)
80
- timers (1.1.0)
81
- tzinfo (1.2.1)
87
+ thread_safe (0.3.5)
88
+ tzinfo (1.2.2)
82
89
  thread_safe (~> 0.1)
83
90
 
84
91
  PLATFORMS
@@ -95,3 +102,6 @@ DEPENDENCIES
95
102
  rake
96
103
  rspec (>= 3.0)
97
104
  sqlite3
105
+
106
+ BUNDLED WITH
107
+ 1.11.2
@@ -11,14 +11,16 @@ module Counter
11
11
  private
12
12
 
13
13
  def create_and_enqueue(delay, cached)
14
+ parameters = { relation_class_name: relation_class,
15
+ relation_id: relation_id,
16
+ column: options.column,
17
+ method: options.method,
18
+ cache: cached,
19
+ counter: counter_class_name }
20
+ parameters[:touch_column] = options.touch_column unless options.touch_column.nil?
14
21
  options.worker_adapter.enqueue(delay,
15
22
  source_object_class_name,
16
- { relation_class_name: relation_class,
17
- relation_id: relation_id,
18
- column: options.column,
19
- method: options.method,
20
- cache: cached,
21
- counter: counter_class_name })
23
+ parameters)
22
24
  end
23
25
  end
24
26
  end
@@ -18,6 +18,7 @@ module Counter
18
18
 
19
19
  def save_new_value!(value)
20
20
  relation_object.send("#{options.column}=", value)
21
+ relation_object.send("#{options.touch_column}=", DateTime.now) if options.touch_column
21
22
  relation_object.save!(validate: false)
22
23
  end
23
24
 
@@ -13,6 +13,10 @@ module Counter
13
13
  options[:column]
14
14
  end
15
15
 
16
+ def touch_column
17
+ options[:touch_column]
18
+ end
19
+
16
20
  def relation
17
21
  options[:relation]
18
22
  end
@@ -1,5 +1,5 @@
1
1
  module Counter
2
2
  module Cache
3
- VERSION = "0.1.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -2,52 +2,106 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe Counter::Cache::Counters::BufferCounter::Enqueuer do
4
4
  let(:worker_adapter) { double }
5
- let(:options) { double(worker_adapter: worker_adapter,
6
- wait: 10,
7
- column: "boo",
8
- method: "calculate_boo",
9
- cached?: true,
10
- recalculation?: false,
11
- recalculation_delay: 20) }
12
5
 
13
6
  let(:source_object_class_name) { "BooUser" }
14
7
  let(:relation_id) { 1 }
15
8
  let(:relation_type) { "Boo" }
16
9
 
17
- let(:enqueuer) { Counter::Cache::Counters::BufferCounter::Enqueuer.new(options, source_object_class_name, relation_id, relation_type, "SuperCounter") }
18
-
19
10
  describe '#enqueue' do
20
- before do
21
- expect(worker_adapter).to receive(:enqueue).with(10,
22
- "BooUser",
23
- { relation_class_name: "Boo",
24
- relation_id: 1,
25
- column: "boo",
26
- method: "calculate_boo",
27
- cache: true,
28
- counter: "SuperCounter" })
29
- end
30
-
31
- describe 'when recalculation is true' do
11
+ context "without touch column" do
32
12
  before do
33
- expect(options).to receive(:recalculation?).and_return(true)
34
- end
13
+ @options = double(worker_adapter: worker_adapter,
14
+ wait: 10,
15
+ column: "boo",
16
+ method: "calculate_boo",
17
+ cached?: true,
18
+ recalculation?: false,
19
+ recalculation_delay: 20)
20
+
21
+ @enqueuer = Counter::Cache::Counters::BufferCounter::Enqueuer.new(@options, source_object_class_name, relation_id, relation_type, "SuperCounter")
35
22
 
36
- it "enqueues two jobs" do
37
- expect(worker_adapter).to receive(:enqueue).with(20,
23
+ expect(worker_adapter).to receive(:enqueue).with(10,
38
24
  "BooUser",
39
25
  { relation_class_name: "Boo",
40
26
  relation_id: 1,
41
27
  column: "boo",
42
28
  method: "calculate_boo",
43
- cache: false,
29
+ cache: true,
44
30
  counter: "SuperCounter" })
45
- enqueuer.enqueue!(double)
31
+ end
32
+
33
+ describe 'when recalculation is true' do
34
+ before do
35
+ expect(@options).to receive(:recalculation?).and_return(true)
36
+ end
37
+
38
+ it "enqueues two jobs" do
39
+ expect(@options).to receive(:touch_column).twice
40
+ expect(worker_adapter).to receive(:enqueue).with(20,
41
+ "BooUser",
42
+ { relation_class_name: "Boo",
43
+ relation_id: 1,
44
+ column: "boo",
45
+ method: "calculate_boo",
46
+ cache: false,
47
+ counter: "SuperCounter" })
48
+ @enqueuer.enqueue!(double)
49
+ end
50
+ end
51
+
52
+ it 'enqueues one job' do
53
+ expect(@options).to receive(:touch_column)
54
+ @enqueuer.enqueue!(double)
46
55
  end
47
56
  end
48
57
 
49
- it 'enqueues one job' do
50
- enqueuer.enqueue!(double)
58
+ context "with touch column" do
59
+ before(:each) do
60
+ @options = double(worker_adapter: worker_adapter,
61
+ wait: 10,
62
+ column: "boo",
63
+ touch_column: "boo_updated_at",
64
+ method: "calculate_boo",
65
+ cached?: true,
66
+ recalculation?: false,
67
+ recalculation_delay: 20)
68
+ @enqueuer = Counter::Cache::Counters::BufferCounter::Enqueuer.new(@options, source_object_class_name, relation_id, relation_type, "SuperCounter")
69
+
70
+ expect(worker_adapter).to receive(:enqueue).with(10,
71
+ "BooUser",
72
+ { relation_class_name: "Boo",
73
+ relation_id: 1,
74
+ column: "boo",
75
+ touch_column: "boo_updated_at",
76
+ method: "calculate_boo",
77
+ cache: true,
78
+ counter: "SuperCounter" })
79
+ end
80
+
81
+ describe 'when recalculation is true' do
82
+ before do
83
+ expect(@options).to receive(:recalculation?).and_return(true)
84
+ end
85
+
86
+ it "enqueues two jobs" do
87
+ expect(@options).to receive(:touch_column).exactly(4).times
88
+ expect(worker_adapter).to receive(:enqueue).with(20,
89
+ "BooUser",
90
+ { relation_class_name: "Boo",
91
+ relation_id: 1,
92
+ column: "boo",
93
+ touch_column: "boo_updated_at",
94
+ method: "calculate_boo",
95
+ cache: false,
96
+ counter: "SuperCounter" })
97
+ @enqueuer.enqueue!(double)
98
+ end
99
+ end
100
+
101
+ it 'enqueues one job' do
102
+ expect(@options).to receive(:touch_column)
103
+ @enqueuer.enqueue!(double)
104
+ end
51
105
  end
52
106
  end
53
107
  end
@@ -4,7 +4,7 @@ RSpec.describe Counter::Cache::Counters::BufferCounter::Saver do
4
4
  class Boo
5
5
  end
6
6
 
7
- let(:relation_object) { double(boo_count: 2) }
7
+ let(:relation_object) { double(boo_count: 2, boo_updated_at: DateTime.now) }
8
8
  let(:options) { double(relation_class_name: "Boo", relation_id: 1, column: "boo_count", method: nil, source_object_class_name: Boo) }
9
9
  let(:saver) { Counter::Cache::Counters::BufferCounter::Saver.new(options) }
10
10
 
@@ -21,6 +21,7 @@ RSpec.describe Counter::Cache::Counters::BufferCounter::Saver do
21
21
 
22
22
  before do
23
23
  allow(options).to receive(:cached?).and_return(true)
24
+ allow(options).to receive(:touch_column).and_return(nil)
24
25
  expect(relation_object).to receive(:boo_count=).with(4)
25
26
  expect(relation_object).to receive(:save!)
26
27
  expect(counting_store).to receive(:del)
@@ -41,15 +42,29 @@ RSpec.describe Counter::Cache::Counters::BufferCounter::Saver do
41
42
  expect(counting_store).to receive(:del)
42
43
  end
43
44
 
44
- it 'saves the value' do
45
- expect(relation_object).to receive(:boo_count=).with(4)
46
- expect(relation_object).to receive(:save!)
47
- saver.save!
45
+ context "with touch column" do
46
+ it 'saves the value' do
47
+ allow(options).to receive(:touch_column).and_return(:boo_updated_at)
48
+ expect(relation_object).to receive(:boo_count=).with(4)
49
+ expect(relation_object).to receive(:boo_updated_at=).with(DateTime.now)
50
+ expect(relation_object).to receive(:save!)
51
+ saver.save!
52
+ end
53
+ end
54
+
55
+ context "without touch column" do
56
+ it 'saves the value' do
57
+ allow(options).to receive(:touch_column).and_return(nil)
58
+ expect(relation_object).to receive(:boo_count=).with(4)
59
+ expect(relation_object).to receive(:save!)
60
+ saver.save!
61
+ end
48
62
  end
49
63
  end
50
64
 
51
65
  describe 'when cached? is false' do
52
66
  before do
67
+ allow(options).to receive(:touch_column).and_return(nil)
53
68
  allow(options).to receive(:cached?).and_return(false)
54
69
  end
55
70
 
@@ -34,6 +34,13 @@ RSpec.describe Counter::Cache::OptionsParser do
34
34
  end
35
35
  end
36
36
 
37
+ describe "#touch_column" do
38
+ let(:options) { { touch_column: "touch_column_name" } }
39
+ it "returns option if set" do
40
+ expect(parser.touch_column).to eq("touch_column_name")
41
+ end
42
+ end
43
+
37
44
  describe "#method" do
38
45
  let(:options) { { method: "method" } }
39
46
  it "returns option if set" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: counter-cache
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Henry & Matt Camuto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-11-16 00:00:00.000000000 Z
11
+ date: 2016-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -207,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
207
  version: '0'
208
208
  requirements: []
209
209
  rubyforge_project:
210
- rubygems_version: 2.2.2
210
+ rubygems_version: 2.5.1
211
211
  signing_key:
212
212
  specification_version: 4
213
213
  summary: Counting is hard.