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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/Guardfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +6 -0
- data/counter-cache.gemspec +30 -0
- data/lib/counter/cache.rb +28 -0
- data/lib/counter/cache/active_record_updater.rb +23 -0
- data/lib/counter/cache/config.rb +12 -0
- data/lib/counter/cache/counters/buffer_counter.rb +25 -0
- data/lib/counter/cache/counters/buffer_counter/enqueuer.rb +27 -0
- data/lib/counter/cache/counters/buffer_counter/key.rb +23 -0
- data/lib/counter/cache/counters/buffer_counter/relation_finder.rb +29 -0
- data/lib/counter/cache/counters/buffer_counter/saver.rb +70 -0
- data/lib/counter/cache/counters/buffer_counter/updater.rb +54 -0
- data/lib/counter/cache/options_parser.rb +69 -0
- data/lib/counter/cache/redis.rb +40 -0
- data/lib/counter/cache/version.rb +5 -0
- data/spec/features/counter_spec.rb +95 -0
- data/spec/lib/counter/cache/active_record_updater_spec.rb +26 -0
- data/spec/lib/counter/cache/buffer_counter/enqueuer_spec.rb +53 -0
- data/spec/lib/counter/cache/buffer_counter/key_spec.rb +13 -0
- data/spec/lib/counter/cache/buffer_counter/relation_finder_spec.rb +49 -0
- data/spec/lib/counter/cache/buffer_counter/saver_spec.rb +84 -0
- data/spec/lib/counter/cache/buffer_counter/updater_spec.rb +96 -0
- data/spec/lib/counter/cache/buffer_counter_spec.rb +27 -0
- data/spec/lib/counter/cache/config_spec.rb +36 -0
- data/spec/lib/counter/cache/options_parser_spec.rb +176 -0
- data/spec/lib/counter/cache/redis_spec.rb +44 -0
- data/spec/lib/counter/cache_spec.rb +14 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/support/models.rb +87 -0
- data/spec/support/worker_adapter.rb +8 -0
- metadata +208 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
module Counter
|
2
|
+
module Cache
|
3
|
+
class OptionsParser < Struct.new(:options)
|
4
|
+
def worker_adapter
|
5
|
+
options[:worker_adapter] || Counter::Cache.configuration.default_worker_adapter
|
6
|
+
end
|
7
|
+
|
8
|
+
def source_object_class_name
|
9
|
+
options[:source_object_class_name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def column
|
13
|
+
options[:column]
|
14
|
+
end
|
15
|
+
|
16
|
+
def relation
|
17
|
+
options[:relation]
|
18
|
+
end
|
19
|
+
|
20
|
+
def relation_class_name
|
21
|
+
options[:relation_class_name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def relation_id
|
25
|
+
options[:relation_id]
|
26
|
+
end
|
27
|
+
|
28
|
+
def method
|
29
|
+
options[:method]
|
30
|
+
end
|
31
|
+
|
32
|
+
def cached?
|
33
|
+
option_or_true options[:cache]
|
34
|
+
end
|
35
|
+
|
36
|
+
def recalculation?
|
37
|
+
option_or_true options[:recalculation]
|
38
|
+
end
|
39
|
+
|
40
|
+
def polymorphic?
|
41
|
+
options[:polymorphic]
|
42
|
+
end
|
43
|
+
|
44
|
+
def if_value
|
45
|
+
options[:if]
|
46
|
+
end
|
47
|
+
|
48
|
+
def wait(source_object)
|
49
|
+
wait = options[:wait]
|
50
|
+
if wait.respond_to?(:call)
|
51
|
+
wait.call(source_object)
|
52
|
+
else
|
53
|
+
wait
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def recalculation_delay
|
58
|
+
options[:recalculation_delay] || Counter::Cache.configuration.recalculation_delay
|
59
|
+
end
|
60
|
+
|
61
|
+
protected
|
62
|
+
|
63
|
+
def option_or_true(val)
|
64
|
+
val || val.nil?
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Counter
|
2
|
+
module Cache
|
3
|
+
class Redis
|
4
|
+
def incr(key)
|
5
|
+
with_redis do |redis|
|
6
|
+
redis.incr key
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def decr(key)
|
11
|
+
with_redis do |redis|
|
12
|
+
redis.decr(key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key)
|
17
|
+
with_redis do |redis|
|
18
|
+
redis.get(key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def del(key)
|
23
|
+
with_redis do |redis|
|
24
|
+
redis.del(key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def with_redis
|
31
|
+
redis_pool = Counter::Cache.configuration.redis_pool
|
32
|
+
return yield redis_pool unless redis_pool.respond_to?(:with)
|
33
|
+
|
34
|
+
redis_pool.with do |redis|
|
35
|
+
yield redis
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'support/models'
|
3
|
+
require 'support/worker_adapter'
|
4
|
+
require 'fakeredis'
|
5
|
+
|
6
|
+
RSpec.describe "Counting" do
|
7
|
+
|
8
|
+
before do
|
9
|
+
ActiveRecord::Base.silence { CreateModelsForTest.migrate(:up) }
|
10
|
+
Counter::Cache.configure do |c|
|
11
|
+
c.redis_pool = Redis.new
|
12
|
+
c.default_worker_adapter = TestWorkerAdapter.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
ActiveRecord::Base.silence { CreateModelsForTest.migrate(:down) }
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:user) { User.create }
|
21
|
+
|
22
|
+
describe '#posts_count' do
|
23
|
+
it 'increments' do
|
24
|
+
expect {
|
25
|
+
user.posts.create
|
26
|
+
}.to change { user.reload.posts_count }.by(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'decrements' do
|
30
|
+
post = user.posts.create
|
31
|
+
expect {
|
32
|
+
post.destroy
|
33
|
+
}.to change { user.reload.posts_count }.by(-1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#posts_ar_count' do
|
38
|
+
it 'increments' do
|
39
|
+
expect {
|
40
|
+
user.posts.create
|
41
|
+
}.to change { user.reload.posts_ar_count }.by(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'decrements' do
|
45
|
+
post = user.posts.create
|
46
|
+
expect {
|
47
|
+
post.destroy
|
48
|
+
}.to change { user.reload.posts_ar_count }.by(-1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#polymorphic followers_count' do
|
53
|
+
let(:follower_user) { User.create }
|
54
|
+
it 'increments' do
|
55
|
+
expect {
|
56
|
+
Follow.create(user: follower_user, followee: user)
|
57
|
+
}.to change { user.reload.followers_count }.by(1)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'decrements' do
|
61
|
+
follow = Follow.create(user: follower_user, followee: user)
|
62
|
+
expect {
|
63
|
+
follow.destroy
|
64
|
+
}.to change { user.reload.followers_count }.by(-1)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '#users_i_follow_count' do
|
69
|
+
let(:follower_user) { User.create }
|
70
|
+
|
71
|
+
it 'increments' do
|
72
|
+
expect {
|
73
|
+
Follow.create(user: follower_user, followee: user)
|
74
|
+
}.to change { follower_user.reload.users_i_follow_count }.by(1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'decrements' do
|
78
|
+
follow = Follow.create(user: follower_user, followee: user)
|
79
|
+
expect {
|
80
|
+
follow.destroy
|
81
|
+
}.to change { follower_user.reload.users_i_follow_count }.by(-1)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#bogus_followed_count' do
|
86
|
+
let(:follower_user) { User.create }
|
87
|
+
|
88
|
+
it 'eventually recalculates' do
|
89
|
+
expect(user.reload.bogus_followed_count).to_not eq(101)
|
90
|
+
Follow.create(user: follower_user, followee: user)
|
91
|
+
expect(user.reload.bogus_followed_count).to eq(101)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Counter::Cache::ActiveRecordUpdater do
|
4
|
+
let(:counter_class) { double }
|
5
|
+
let(:counter) { double }
|
6
|
+
let(:options) { { counter_class: counter_class } }
|
7
|
+
subject { Counter::Cache::ActiveRecordUpdater.new(options) }
|
8
|
+
|
9
|
+
let(:record) { double }
|
10
|
+
|
11
|
+
describe "#after_create" do
|
12
|
+
it "Calls update on counter instance" do
|
13
|
+
expect(counter).to receive(:update).with(:incr)
|
14
|
+
expect(counter_class).to receive(:new).with(record, options).and_return(counter)
|
15
|
+
subject.after_create(record)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#after_destroy" do
|
20
|
+
it "Calls update on counter instance" do
|
21
|
+
expect(counter).to receive(:update).with(:decr)
|
22
|
+
expect(counter_class).to receive(:new).with(record, options).and_return(counter)
|
23
|
+
subject.after_destroy(record)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Counter::Cache::Counters::BufferCounter::Enqueuer do
|
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
|
+
|
13
|
+
let(:source_object_class_name) { "BooUser" }
|
14
|
+
let(:relation_id) { 1 }
|
15
|
+
let(:relation_type) { "Boo" }
|
16
|
+
|
17
|
+
let(:enqueuer) { Counter::Cache::Counters::BufferCounter::Enqueuer.new(options, source_object_class_name, relation_id, relation_type, "SuperCounter") }
|
18
|
+
|
19
|
+
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
|
32
|
+
before do
|
33
|
+
expect(options).to receive(:recalculation?).and_return(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "enqueues two jobs" do
|
37
|
+
expect(worker_adapter).to receive(:enqueue).with(20,
|
38
|
+
"BooUser",
|
39
|
+
{ relation_class_name: "Boo",
|
40
|
+
relation_id: 1,
|
41
|
+
column: "boo",
|
42
|
+
method: "calculate_boo",
|
43
|
+
cache: false,
|
44
|
+
counter: "SuperCounter" })
|
45
|
+
enqueuer.enqueue!(double)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'enqueues one job' do
|
50
|
+
enqueuer.enqueue!(double)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Counter::Cache::Counters::BufferCounter::Key do
|
4
|
+
let(:options) { double(relation_class_name: "Boo", relation: "boo", column: "boos_count", relation_id: nil) }
|
5
|
+
let(:source_object) { double(boo_id: 1) }
|
6
|
+
let(:key) { Counter::Cache::Counters::BufferCounter::Key.new(source_object, options) }
|
7
|
+
|
8
|
+
describe '#to_s' do
|
9
|
+
it 'returns the key with the class, id, and column' do
|
10
|
+
expect(key.to_s).to eq("cc:Bo:1:boos")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Counter::Cache::Counters::BufferCounter::RelationFinder do
|
4
|
+
let(:options) { double }
|
5
|
+
let(:source_object) { double }
|
6
|
+
let(:finder) { Counter::Cache::Counters::BufferCounter::RelationFinder.new(source_object, options) }
|
7
|
+
|
8
|
+
describe '#relation_class' do
|
9
|
+
context 'when relation_class_name is present' do
|
10
|
+
let(:options) { double(relation_class_name: "Boo") }
|
11
|
+
|
12
|
+
it 'returns the relation_class_name' do
|
13
|
+
expect(finder.relation_class).to eq("Boo")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'when polymorphic?' do
|
18
|
+
let(:options) { double(polymorphic?: true, relation: "boo", relation_class_name: nil) }
|
19
|
+
let(:source_object) { double(boo_type: "Boo") }
|
20
|
+
|
21
|
+
it 'asks for the type' do
|
22
|
+
expect(finder.relation_class).to eq("Boo")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'no relation_class_name or polymorphic' do
|
27
|
+
let(:options) { double(relation_class_name: nil, polymorphic?: false, relation: "boo") }
|
28
|
+
|
29
|
+
before do
|
30
|
+
reflection = double
|
31
|
+
expect(reflection).to receive_message_chain("class_name.to_s.camelize") { "Boo" }
|
32
|
+
expect(source_object).to receive(:reflections).and_return({:boo => reflection})
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'asks active record for the class name' do
|
36
|
+
expect(finder.relation_class).to eq("Boo")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#relation_id' do
|
42
|
+
let(:options) { double(relation: "boo", relation_id: nil) }
|
43
|
+
let(:source_object) { double(boo_id: 123) }
|
44
|
+
|
45
|
+
it 'calls relation_id on the source object' do
|
46
|
+
expect(finder.relation_id).to eq(123)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Counter::Cache::Counters::BufferCounter::Saver do
|
4
|
+
class Boo
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:relation_object) { double(boo_count: 2) }
|
8
|
+
let(:options) { double(relation_class_name: "Boo", relation_id: 1, column: "boo_count", method: nil, source_object_class_name: Boo) }
|
9
|
+
let(:saver) { Counter::Cache::Counters::BufferCounter::Saver.new(options) }
|
10
|
+
|
11
|
+
describe '#save!' do
|
12
|
+
let(:counting_store) { double(get: nil) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
allow(Boo).to receive(:find_by_id).and_return(relation_object)
|
16
|
+
allow(Counter::Cache.configuration).to receive(:counting_data_store).and_return(counting_store)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "yielding" do
|
20
|
+
let(:counting_store) { double(get: 2) }
|
21
|
+
|
22
|
+
before do
|
23
|
+
allow(options).to receive(:cached?).and_return(true)
|
24
|
+
expect(relation_object).to receive(:boo_count=).with(4)
|
25
|
+
expect(relation_object).to receive(:save!)
|
26
|
+
expect(counting_store).to receive(:del)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "with block" do
|
30
|
+
it "yields" do
|
31
|
+
expect { |b| saver.save!(&b) }.to yield_with_args(2, 4, relation_object, "boo_count")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'when cached? is true' do
|
37
|
+
let(:counting_store) { double(get: 2) }
|
38
|
+
|
39
|
+
before do
|
40
|
+
allow(options).to receive(:cached?).and_return(true)
|
41
|
+
expect(counting_store).to receive(:del)
|
42
|
+
end
|
43
|
+
|
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!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'when cached? is false' do
|
52
|
+
before do
|
53
|
+
allow(options).to receive(:cached?).and_return(false)
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'when method is passed' do
|
57
|
+
before do
|
58
|
+
allow(options).to receive(:method).and_return("call_this_thing")
|
59
|
+
allow(relation_object).to receive(:call_this_thing).and_return(4)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'saves the value' do
|
63
|
+
expect(relation_object).to receive(:boo_count=).with(4)
|
64
|
+
expect(relation_object).to receive(:save!)
|
65
|
+
saver.save!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'when method is not passed' do
|
70
|
+
|
71
|
+
before do
|
72
|
+
allow(Boo).to receive(:table_name).and_return("boos")
|
73
|
+
allow(relation_object).to receive(:boos).and_return(double(count: 4))
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'saves the value' do
|
77
|
+
expect(relation_object).to receive(:boo_count=).with(4)
|
78
|
+
expect(relation_object).to receive(:save!)
|
79
|
+
saver.save!
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Counter::Cache::Counters::BufferCounter::Updater do
|
4
|
+
let(:options) { double(relation: "boo", relation_class_name: "Boo", column: "boo", relation_id: nil) }
|
5
|
+
let(:source_object) { double(boo_id: 1) }
|
6
|
+
let(:updater) { Counter::Cache::Counters::BufferCounter::Updater.new(source_object, options, "Hello") }
|
7
|
+
|
8
|
+
describe "#update" do
|
9
|
+
describe "when valid" do
|
10
|
+
it "sends direction and enqueues" do
|
11
|
+
expect(updater).to receive(:valid?) { true }
|
12
|
+
expect(updater).to receive(:decr) { true }
|
13
|
+
expect(updater).to receive(:enqueue) { true }
|
14
|
+
updater.update!(:decr)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "when invalid" do
|
19
|
+
it "sends direction and enqueues" do
|
20
|
+
expect(updater).to receive(:valid?) { false }
|
21
|
+
expect(updater).to receive(:decr).never
|
22
|
+
expect(updater).to receive(:enqueue).never
|
23
|
+
updater.update!(:decr)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#enqueue" do
|
29
|
+
it 'constructs and calls enqueue! on the enqueue' do
|
30
|
+
expect(Counter::Cache::Counters::BufferCounter::Enqueuer).to receive(:new).with(options,
|
31
|
+
source_object.class.name,
|
32
|
+
1,
|
33
|
+
"Boo",
|
34
|
+
"Hello")
|
35
|
+
.and_return(double(enqueue!: true))
|
36
|
+
updater.send(:enqueue)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#decr" do
|
41
|
+
it 'calls decr on the redis instance' do
|
42
|
+
expect_any_instance_of(Counter::Cache::Redis).to receive(:decr)
|
43
|
+
updater.send(:decr)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#incr" do
|
48
|
+
it 'calls decr on the redis instance' do
|
49
|
+
expect_any_instance_of(Counter::Cache::Redis).to receive(:incr)
|
50
|
+
updater.send(:incr)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "valid?" do
|
55
|
+
describe "With no if value" do
|
56
|
+
let(:options) { double(relation: "boo", if_value: nil, relation_id: nil) }
|
57
|
+
|
58
|
+
describe 'with relation_id' do
|
59
|
+
let(:source_object) { double(boo_id: 123) }
|
60
|
+
|
61
|
+
it 'returns true' do
|
62
|
+
expect(updater.send(:valid?)).to eq(true)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'without relation_id' do
|
67
|
+
let(:source_object) { double(boo_id: nil) }
|
68
|
+
|
69
|
+
it 'returns false' do
|
70
|
+
expect(updater.send(:valid?)).to eq(false)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "With if object" do
|
76
|
+
let(:source_object) { double(boo_id: 123) }
|
77
|
+
let(:options) { double(relation: "boo", if_value: if_value, relation_id: nil) }
|
78
|
+
|
79
|
+
describe 'if value returns false' do
|
80
|
+
let(:if_value) { double(call: true) }
|
81
|
+
|
82
|
+
it 'returns true' do
|
83
|
+
expect(updater.send(:valid?)).to eq(true)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'if value returns true' do
|
88
|
+
let(:if_value) { double(call: false) }
|
89
|
+
|
90
|
+
it 'returns false' do
|
91
|
+
expect(updater.send(:valid?)).to eq(false)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|