counter-cache 0.0.1

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