counter-cache 0.0.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +29 -0
  9. data/Rakefile +6 -0
  10. data/counter-cache.gemspec +30 -0
  11. data/lib/counter/cache.rb +28 -0
  12. data/lib/counter/cache/active_record_updater.rb +23 -0
  13. data/lib/counter/cache/config.rb +12 -0
  14. data/lib/counter/cache/counters/buffer_counter.rb +25 -0
  15. data/lib/counter/cache/counters/buffer_counter/enqueuer.rb +27 -0
  16. data/lib/counter/cache/counters/buffer_counter/key.rb +23 -0
  17. data/lib/counter/cache/counters/buffer_counter/relation_finder.rb +29 -0
  18. data/lib/counter/cache/counters/buffer_counter/saver.rb +70 -0
  19. data/lib/counter/cache/counters/buffer_counter/updater.rb +54 -0
  20. data/lib/counter/cache/options_parser.rb +69 -0
  21. data/lib/counter/cache/redis.rb +40 -0
  22. data/lib/counter/cache/version.rb +5 -0
  23. data/spec/features/counter_spec.rb +95 -0
  24. data/spec/lib/counter/cache/active_record_updater_spec.rb +26 -0
  25. data/spec/lib/counter/cache/buffer_counter/enqueuer_spec.rb +53 -0
  26. data/spec/lib/counter/cache/buffer_counter/key_spec.rb +13 -0
  27. data/spec/lib/counter/cache/buffer_counter/relation_finder_spec.rb +49 -0
  28. data/spec/lib/counter/cache/buffer_counter/saver_spec.rb +84 -0
  29. data/spec/lib/counter/cache/buffer_counter/updater_spec.rb +96 -0
  30. data/spec/lib/counter/cache/buffer_counter_spec.rb +27 -0
  31. data/spec/lib/counter/cache/config_spec.rb +36 -0
  32. data/spec/lib/counter/cache/options_parser_spec.rb +176 -0
  33. data/spec/lib/counter/cache/redis_spec.rb +44 -0
  34. data/spec/lib/counter/cache_spec.rb +14 -0
  35. data/spec/spec_helper.rb +93 -0
  36. data/spec/support/models.rb +87 -0
  37. data/spec/support/worker_adapter.rb +8 -0
  38. metadata +208 -0
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Counter::Cache::Counters::BufferCounter do
4
+ let(:options) { {} }
5
+ let(:source_object) { double }
6
+ let(:counter) { Counter::Cache::Counters::BufferCounter.new(source_object, options) }
7
+
8
+ describe "#update" do
9
+ it "calls update! on an instance of an updater" do
10
+ updater = double
11
+ expect(updater).to receive(:update!).with(:blah)
12
+ expect(Counter::Cache::Counters::BufferCounter::Updater).to receive(:new).with(source_object,
13
+ an_instance_of(Counter::Cache::OptionsParser),
14
+ "Counter::Cache::Counters::BufferCounter").and_return(updater)
15
+ counter.update(:blah)
16
+ end
17
+ end
18
+
19
+ describe "#save!" do
20
+ it "calls save! on an instance of a saver" do
21
+ saver = double
22
+ expect(saver).to receive(:save!)
23
+ expect(Counter::Cache::Counters::BufferCounter::Saver).to receive(:new).with(an_instance_of(Counter::Cache::OptionsParser)).and_return(saver)
24
+ counter.save!
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Counter::Cache do
4
+ describe ".configure" do
5
+ let(:clazz) { Class.new }
6
+
7
+ it "sets counter class" do
8
+ Counter::Cache.configure do |config|
9
+ config.default_worker_adapter = clazz
10
+ end
11
+ expect(Counter::Cache.configuration.default_worker_adapter).to eq(clazz)
12
+ end
13
+
14
+ it "sets the redis connection" do
15
+ Counter::Cache.configure do |config|
16
+ config.redis_pool = clazz
17
+ end
18
+ expect(Counter::Cache.configuration.redis_pool).to eq(clazz)
19
+ end
20
+
21
+ describe "counting_data_store" do
22
+ it "sets counting_data_store" do
23
+ Counter::Cache.configure do |config|
24
+ config.counting_data_store = clazz
25
+ end
26
+ expect(Counter::Cache.configuration.counting_data_store).to eq(clazz)
27
+ end
28
+
29
+ it "defaults to redis with no option" do
30
+ expect(Counter::Cache.configuration.counting_data_store).to be_instance_of(Counter::Cache::Redis)
31
+ end
32
+ end
33
+
34
+
35
+ end
36
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Counter::Cache::OptionsParser do
4
+
5
+ subject(:parser) { Counter::Cache::OptionsParser.new options }
6
+ let(:options) { {} }
7
+
8
+ describe "#cached?" do
9
+ describe "if cache is nil" do
10
+ it "it should be true" do
11
+ expect(parser.cached?).to eq(true)
12
+ end
13
+ end
14
+
15
+ describe "if cache is false" do
16
+ let(:options) { { cache: false } }
17
+ it "it should be false" do
18
+ expect(parser.cached?).to eq(false)
19
+ end
20
+ end
21
+
22
+ describe "if cache is true" do
23
+ let(:options) { { cache: true } }
24
+ it "it should be false" do
25
+ expect(parser.cached?).to eq(true)
26
+ end
27
+ end
28
+ end
29
+
30
+ describe "#column" do
31
+ let(:options) { { column: "column_name" } }
32
+ it "returns option if set" do
33
+ expect(parser.column).to eq("column_name")
34
+ end
35
+ end
36
+
37
+ describe "#method" do
38
+ let(:options) { { method: "method" } }
39
+ it "returns option if set" do
40
+ expect(parser.method).to eq("method")
41
+ end
42
+ end
43
+
44
+ describe "#polymorphic" do
45
+ let(:options) { { polymorphic: true } }
46
+
47
+ it "returns option if set" do
48
+ expect(parser.polymorphic?).to eq(true)
49
+ end
50
+ end
51
+
52
+ describe "#if_value" do
53
+ let(:options) { { if: true } }
54
+
55
+ it "returns option if set" do
56
+ expect(parser.if_value).to eq(true)
57
+ end
58
+ end
59
+
60
+ describe "#recalculation?" do
61
+ describe "if cache is nil" do
62
+ it "it should be true" do
63
+ expect(parser.recalculation?).to eq(true)
64
+ end
65
+ end
66
+
67
+ describe "if cache is false" do
68
+ let(:options) { { recalculation: false } }
69
+ it "it should be false" do
70
+ expect(parser.recalculation?).to eq(false)
71
+ end
72
+ end
73
+
74
+ describe "if cache is true" do
75
+ let(:options) { { recalculation: true } }
76
+ it "it should be false" do
77
+ expect(parser.recalculation?).to eq(true)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#recalculation_delay" do
83
+ describe "With a option" do
84
+ let(:options) { { recalculation_delay: 1245 } }
85
+ it "returns if option is set" do
86
+ expect(parser.recalculation_delay).to eq(1245)
87
+ end
88
+ end
89
+
90
+ describe "With no option" do
91
+ it "returns default if no option is set" do
92
+ Counter::Cache.configure do |config|
93
+ config.recalculation_delay = 897
94
+ end
95
+ expect(parser.recalculation_delay).to eq(897)
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "#relation" do
101
+ let(:options) { { relation: "relation_name" } }
102
+ it "returns option if set" do
103
+ expect(parser.relation).to eq("relation_name")
104
+ end
105
+ end
106
+
107
+ describe "#relation_class_name" do
108
+ let(:options) { { relation_class_name: "relation_class_name" } }
109
+ it "returns option if set" do
110
+ expect(parser.relation_class_name).to eq("relation_class_name")
111
+ end
112
+ end
113
+
114
+ describe "#relation_id" do
115
+ let(:options) { { relation_id: 1 } }
116
+
117
+ it "returns option if set" do
118
+ expect(parser.relation_id).to eq(1)
119
+ end
120
+ end
121
+
122
+ describe "#source_object_class_name" do
123
+ let(:options) { { source_object_class_name: "class_name" } }
124
+ it "returns option if set" do
125
+ expect(parser.source_object_class_name).to eq("class_name")
126
+ end
127
+ end
128
+
129
+ describe "#wait" do
130
+ let(:source_object) { double("src_obj") }
131
+
132
+ describe "with a value" do
133
+ let(:options) { { wait: 1234 } }
134
+
135
+ it "returns option if set" do
136
+ expect(parser.wait(source_object)).to eq(1234)
137
+ end
138
+ end
139
+
140
+ describe "with a proc" do
141
+ let(:wait_double) { double("callee", call: 123456) }
142
+ let(:options) { { wait: wait_double } }
143
+
144
+ it "returns option if set" do
145
+ expect(parser.wait(source_object)).to eq(123456)
146
+ end
147
+ end
148
+ end
149
+
150
+
151
+ describe "#worker" do
152
+ describe "With a specified worker" do
153
+ let(:options) { { worker_adapter: "Fake" } }
154
+ it "returns worker on option" do
155
+ expect(parser.worker_adapter).to eq("Fake")
156
+ end
157
+ end
158
+
159
+ describe "With no options" do
160
+ let(:options) { {} }
161
+ let(:clazz) { Class.new }
162
+
163
+ before do
164
+ Counter::Cache.configure do |config|
165
+ config.default_worker_adapter = clazz
166
+ end
167
+ end
168
+
169
+ it "returns worker on option" do
170
+ expect(parser.worker_adapter).to eq(clazz)
171
+ end
172
+ end
173
+ end
174
+
175
+
176
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Counter::Cache::Redis do
4
+ let(:redis) { double }
5
+ let(:redis_pool) { double }
6
+
7
+ before do
8
+ Counter::Cache.configure do |c|
9
+ c.redis_pool = redis_pool
10
+ end
11
+
12
+ allow(redis_pool).to receive(:with).and_yield(redis)
13
+ end
14
+
15
+ let(:helper) { Counter::Cache::Redis.new }
16
+
17
+ describe '#decr' do
18
+ it 'calls decr on redis with the key' do
19
+ expect(redis).to receive(:decr).with("hello")
20
+ helper.decr("hello")
21
+ end
22
+ end
23
+
24
+ describe '#incr' do
25
+ it 'calls incr on redis with the key' do
26
+ expect(redis).to receive(:incr).with("hello")
27
+ helper.incr("hello")
28
+ end
29
+ end
30
+
31
+ describe '#del' do
32
+ it 'calls del on redis with the key' do
33
+ expect(redis).to receive(:del).with("hello")
34
+ helper.del("hello")
35
+ end
36
+ end
37
+
38
+ describe '#get' do
39
+ it 'calls get on redis with the key and returns the value' do
40
+ expect(redis).to receive(:get).with("hello").and_return(2)
41
+ expect(helper.get("hello")).to eq(2)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Counter::Cache do
4
+ describe '.included' do
5
+ let(:counter) { double(:counter) }
6
+ let(:clazz) { Class.new { include Counter::Cache } }
7
+
8
+ it 'listens to the after_create and after_destroy' do
9
+ expect(clazz).to receive(:after_create).with(an_instance_of(Counter::Cache::ActiveRecordUpdater)).once { true }
10
+ expect(clazz).to receive(:after_destroy).with(an_instance_of(Counter::Cache::ActiveRecordUpdater)).once { true }
11
+ clazz.counter_cache_on :counter => counter
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,93 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, make a
10
+ # separate helper file that requires this one and then use it only in the specs
11
+ # that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+
18
+ require 'counter/cache'
19
+
20
+ RSpec.configure do |config|
21
+
22
+ config.before do
23
+ Counter::Cache.instance_variable_set(:@configuration, nil)
24
+ end
25
+
26
+ config.after do
27
+ Counter::Cache.instance_variable_set(:@configuration, nil)
28
+ end
29
+
30
+ config.raise_errors_for_deprecations!
31
+ config.disable_monkey_patching!
32
+
33
+ # The settings below are suggested to provide a good initial experience
34
+ # with RSpec, but feel free to customize to your heart's content.
35
+ =begin
36
+ # These two settings work together to allow you to limit a spec run
37
+ # to individual examples or groups you care about by tagging them with
38
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
39
+ # get run.
40
+ config.filter_run :focus
41
+ config.run_all_when_everything_filtered = true
42
+
43
+ # Many RSpec users commonly either run the entire suite or an individual
44
+ # file, and it's useful to allow more verbose output when running an
45
+ # individual spec file.
46
+ if config.files_to_run.one?
47
+ # Use the documentation formatter for detailed output,
48
+ # unless a formatter has already been configured
49
+ # (e.g. via a command-line flag).
50
+ config.default_formatter = 'doc'
51
+ end
52
+
53
+ # Print the 10 slowest examples and example groups at the
54
+ # end of the spec run, to help surface which specs are running
55
+ # particularly slow.
56
+ config.profile_examples = 10
57
+
58
+ # Run specs in random order to surface order dependencies. If you find an
59
+ # order dependency and want to debug it, you can fix the order by providing
60
+ # the seed, which is printed after each run.
61
+ # --seed 1234
62
+ config.order = :random
63
+
64
+ # Seed global randomization in this process using the `--seed` CLI option.
65
+ # Setting this allows you to use `--seed` to deterministically reproduce
66
+ # test failures related to randomization by passing the same `--seed` value
67
+ # as the one that triggered the failure.
68
+ Kernel.srand config.seed
69
+
70
+ # rspec-expectations config goes here. You can use an alternate
71
+ # assertion/expectation library such as wrong or the stdlib/minitest
72
+ # assertions if you prefer.
73
+ config.expect_with :rspec do |expectations|
74
+ # Enable only the newer, non-monkey-patching expect syntax.
75
+ # For more details, see:
76
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
77
+ expectations.syntax = :expect
78
+ end
79
+
80
+ # rspec-mocks config goes here. You can use an alternate test double
81
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
82
+ config.mock_with :rspec do |mocks|
83
+ # Enable only the newer, non-monkey-patching expect syntax.
84
+ # For more details, see:
85
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
86
+ mocks.syntax = :expect
87
+
88
+ # Prevents you from mocking or stubbing a method that does not exist on
89
+ # a real object. This is generally recommended.
90
+ mocks.verify_partial_doubles = true
91
+ end
92
+ =end
93
+ end
@@ -0,0 +1,87 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Migration.verbose = false
4
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
5
+
6
+ class CreateModelsForTest < ActiveRecord::Migration
7
+ def self.up
8
+ create_table :users do |t|
9
+ t.string :name
10
+ t.integer :posts_count, :default => 0
11
+ t.integer :posts_ar_count, :default => 0
12
+ t.integer :followers_count, :default => 0
13
+ t.integer :users_i_follow_count, :default => 0
14
+ t.integer :bogus_followed_count, :default => 0
15
+ end
16
+
17
+ create_table :follows do |t|
18
+ t.integer :user_id
19
+ t.integer :followee_id
20
+ t.string :followee_type
21
+ end
22
+
23
+ create_table :posts do |t|
24
+ t.string :body
25
+ t.belongs_to :user
26
+ end
27
+ end
28
+
29
+ def self.down
30
+ drop_table(:users)
31
+ drop_table(:posts)
32
+ drop_table(:follows)
33
+ end
34
+ end
35
+
36
+ class User < ActiveRecord::Base
37
+ has_many :posts
38
+
39
+ def calculate_posts_count
40
+ posts.count
41
+ end
42
+
43
+ def calculate_bogus_follow_count
44
+ 101
45
+ end
46
+
47
+ end
48
+
49
+ class Follow < ActiveRecord::Base
50
+ belongs_to :user
51
+ belongs_to :followee, polymorphic: true
52
+
53
+ include Counter::Cache
54
+
55
+ counter_cache_on column: :followers_count,
56
+ relation: :followee,
57
+ polymorphic: true,
58
+ recalculation: false
59
+
60
+ counter_cache_on column: :bogus_followed_count,
61
+ relation: :followee,
62
+ polymorphic: true,
63
+ method: :calculate_bogus_follow_count,
64
+ recalculation: true
65
+
66
+ counter_cache_on column: :users_i_follow_count,
67
+ relation: :user,
68
+ if: ->(follow) { follow.followee_type == "User" },
69
+ recalculation: false
70
+ end
71
+
72
+ class Post < ActiveRecord::Base
73
+ belongs_to :user
74
+
75
+ include Counter::Cache
76
+
77
+ counter_cache_on column: :posts_count,
78
+ relation: :user,
79
+ relation_class_name: "User",
80
+ method: :calculate_posts_count,
81
+ recalculation: false
82
+
83
+ counter_cache_on column: :posts_ar_count,
84
+ relation: :user,
85
+ relation_class_name: "User",
86
+ recalculation: false
87
+ end