lifesaver 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/.travis.yml +3 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile +4 -0
- data/README.md +33 -13
- data/lib/lifesaver.rb +34 -11
- data/lib/lifesaver/config.rb +16 -0
- data/lib/lifesaver/index_worker.rb +10 -10
- data/lib/lifesaver/indexing/enqueuer.rb +37 -0
- data/lib/lifesaver/indexing/indexer.rb +40 -0
- data/lib/lifesaver/indexing/model_additions.rb +55 -0
- data/lib/lifesaver/notification/eager_loader.rb +48 -0
- data/lib/lifesaver/notification/enqueuer.rb +24 -0
- data/lib/lifesaver/notification/indexing_graph.rb +91 -0
- data/lib/lifesaver/notification/model_additions.rb +104 -0
- data/lib/lifesaver/notification/notifiable_associations.rb +49 -0
- data/lib/lifesaver/notification/traversal_queue.rb +48 -0
- data/lib/lifesaver/railtie.rb +4 -3
- data/lib/lifesaver/serialized_model.rb +3 -0
- data/lib/lifesaver/version.rb +1 -1
- data/lib/lifesaver/visitor_worker.rb +8 -4
- data/spec/integration/lifesaver_spec.rb +78 -0
- data/spec/spec_helper.rb +12 -10
- data/spec/support/active_record.rb +3 -3
- data/spec/support/test_models.rb +8 -6
- data/spec/support/tire_helper.rb +37 -0
- data/spec/unit/config_spec.rb +17 -0
- data/spec/unit/indexing/indexer_spec.rb +51 -0
- data/spec/unit/indexing/model_addtions_spec.rb +53 -0
- data/spec/unit/notification/eager_loader_spec.rb +64 -0
- data/spec/unit/notification/enqueuer_spec.rb +39 -0
- data/spec/unit/notification/indexing_graph_spec.rb +73 -0
- data/spec/unit/notification/model_additions_spec.rb +69 -0
- data/spec/unit/notification/notifiable_associations_spec.rb +75 -0
- data/spec/unit/notification/traversal_queue_spec.rb +67 -0
- metadata +40 -14
- data/lib/lifesaver/index_graph.rb +0 -53
- data/lib/lifesaver/marshal.rb +0 -43
- data/lib/lifesaver/model_additions.rb +0 -133
- data/spec/lifesaver/index_graph_spec.rb +0 -64
- data/spec/lifesaver/marshal_spec.rb +0 -80
- data/spec/lifesaver/model_additions_spec.rb +0 -91
- data/spec/lifesaver_spec.rb +0 -111
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Config do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'with no options' do
|
6
|
+
let(:config) { Lifesaver::Config.new }
|
7
|
+
|
8
|
+
it 'has the default indexing_queue' do
|
9
|
+
expect(config.indexing_queue).to eql(:lifesaver_indexing)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has the default notification_queue' do
|
13
|
+
expect(config.notification_queue).to eql(:lifesaver_notification)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Indexing::Indexer do
|
4
|
+
context '#perform' do
|
5
|
+
let(:index) { double('index', remove: nil, store: nil) }
|
6
|
+
let(:index_name) { 'test-index-name' }
|
7
|
+
before do
|
8
|
+
Model.stub(:index_name).and_return(index_name)
|
9
|
+
Tire.stub(:index).with(index_name).and_return(index)
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'when operation is update' do
|
13
|
+
let(:model) { Model.new(1) }
|
14
|
+
before do
|
15
|
+
Model.stub(:exists?).with('1').and_return(true)
|
16
|
+
Model.stub(:find).with('1').and_return(model)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'calls tire with the correct arguments' do
|
20
|
+
indexer = Lifesaver::Indexing::Indexer.new(
|
21
|
+
class_name: 'model',
|
22
|
+
model_id: '1',
|
23
|
+
operation: 'update'
|
24
|
+
)
|
25
|
+
|
26
|
+
expect(index).to receive(:store).with(model)
|
27
|
+
|
28
|
+
indexer.perform
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when operation is destroy' do
|
33
|
+
before do
|
34
|
+
Model.stub(:document_type).and_return('class')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'calls tire with the correct arguments' do
|
38
|
+
indexer = Lifesaver::Indexing::Indexer.new(
|
39
|
+
class_name: 'model',
|
40
|
+
model_id: '1',
|
41
|
+
operation: 'destroy'
|
42
|
+
)
|
43
|
+
correct_arguments = { type: 'class', id: '1' }
|
44
|
+
|
45
|
+
expect(index).to receive(:remove).with(correct_arguments)
|
46
|
+
|
47
|
+
indexer.perform
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Indexing::ModelAdditions do
|
4
|
+
let(:post) { Post.new(title: 'Test Post') }
|
5
|
+
|
6
|
+
describe '.enqueues_indexing' do
|
7
|
+
before do
|
8
|
+
post.stub(:enqueue_indexing)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'calls enqueue_indexing on save' do
|
12
|
+
expect(post).to receive(:enqueue_indexing)
|
13
|
+
|
14
|
+
post.save!
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'calls enqueue_indexing on destroy' do
|
18
|
+
expect(post).to receive(:enqueue_indexing)
|
19
|
+
|
20
|
+
post.destroy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#indexing_suppressed?' do
|
25
|
+
it 'is false by default' do
|
26
|
+
expect(post.send(:suppress_indexing?)).to eql(false)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is true if overridden locally' do
|
30
|
+
Lifesaver.suppress_indexing
|
31
|
+
|
32
|
+
expect(post.send(:suppress_indexing?)).to eql(true)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'is false if override is cancelled' do
|
36
|
+
Lifesaver.unsuppress_indexing
|
37
|
+
|
38
|
+
expect(post.send(:suppress_indexing?)).to eql(false)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'is true if set individually' do
|
42
|
+
post.suppress_indexing
|
43
|
+
|
44
|
+
expect(post.send(:suppress_indexing?)).to eql(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'is false if unset individually' do
|
48
|
+
post.unsuppress_indexing
|
49
|
+
|
50
|
+
expect(post.send(:suppress_indexing?)).to eql(false)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Notification::EagerLoader do
|
4
|
+
|
5
|
+
let(:eager_loader) { Lifesaver::Notification::EagerLoader.new }
|
6
|
+
|
7
|
+
describe '#add_model' do
|
8
|
+
|
9
|
+
it 'adds when empty' do
|
10
|
+
eager_loader.add_model('Post', 34)
|
11
|
+
expected_result = { 'Post' => [34] }
|
12
|
+
|
13
|
+
expect(eager_loader.send(:models_to_load)).to eql(expected_result)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'adds to when other model ids exist' do
|
17
|
+
eager_loader.add_model('Post', 34)
|
18
|
+
eager_loader.add_model('Post', 38)
|
19
|
+
eager_loader.add_model('Article', 3)
|
20
|
+
expected_result = { 'Post' => [34, 38], 'Article' => [3] }
|
21
|
+
|
22
|
+
expect(eager_loader.send(:models_to_load)).to eql(expected_result)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'does not add a previously added model' do
|
26
|
+
eager_loader.add_model('Post', 38)
|
27
|
+
eager_loader.add_model('Post', 38)
|
28
|
+
expected_result = { 'Post' => [38] }
|
29
|
+
|
30
|
+
expect(eager_loader.send(:models_to_load)).to eql(expected_result)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#load' do
|
36
|
+
before do
|
37
|
+
Post.stub(:load_with_notifiable_associations).and_return([])
|
38
|
+
eager_loader.add_model('Post', 34)
|
39
|
+
eager_loader.add_model('Post', 38)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'empties the models_to_load' do
|
43
|
+
eager_loader.load
|
44
|
+
|
45
|
+
expect(eager_loader.send(:models_to_load)).to be_empty
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns an array' do
|
49
|
+
expect(eager_loader.load).to eq([])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#empty?' do
|
54
|
+
it 'is true when empty' do
|
55
|
+
expect(eager_loader.empty?).to be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'is false when not empty' do
|
59
|
+
eager_loader.add_model('Post', 38)
|
60
|
+
|
61
|
+
expect(eager_loader.empty?).to be_false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Notification::Enqueuer do
|
4
|
+
let(:models) { [Lifesaver::SerializedModel.new('TestClass', '3')] }
|
5
|
+
let(:enqueuer) { Lifesaver::Notification::Enqueuer.new(models) }
|
6
|
+
before { ::Resque.stub(:enqueue) }
|
7
|
+
|
8
|
+
describe '#enqueue' do
|
9
|
+
context 'when indexing is not suppressed' do
|
10
|
+
context 'and there are models' do
|
11
|
+
it 'calls Resque.enqueue with the correct parameters' do
|
12
|
+
expect(::Resque).to receive(:enqueue)
|
13
|
+
.with(Lifesaver::VisitorWorker, models)
|
14
|
+
|
15
|
+
enqueuer.enqueue
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context 'and there are no models' do
|
19
|
+
it 'does not call Resque.enqueue' do
|
20
|
+
empty_enqueuer = Lifesaver::Notification::Enqueuer.new([])
|
21
|
+
|
22
|
+
expect(::Resque).to_not receive(:enqueue)
|
23
|
+
|
24
|
+
empty_enqueuer.enqueue
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'when indexing is suppressed' do
|
30
|
+
it 'does not call Resque.enqueue' do
|
31
|
+
Lifesaver.suppress_indexing
|
32
|
+
|
33
|
+
expect(::Resque).to_not receive(:enqueue)
|
34
|
+
|
35
|
+
enqueuer.enqueue
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Notification::IndexingGraph do
|
4
|
+
let(:indexing_graph) { Lifesaver::Notification::IndexingGraph.new }
|
5
|
+
|
6
|
+
describe '#initalize_models' do
|
7
|
+
it 'should load serialized models into the loader' do
|
8
|
+
indexing_graph.stub(:add_model_to_loader)
|
9
|
+
serialized_models = []
|
10
|
+
serialized_models << Lifesaver::SerializedModel.new('Post', 14)
|
11
|
+
|
12
|
+
expect(indexing_graph).to receive(:add_model_to_loader).with('Post', 14)
|
13
|
+
|
14
|
+
indexing_graph.initialize_models(serialized_models)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#generate' do
|
19
|
+
context 'when queue is empty' do
|
20
|
+
before do
|
21
|
+
indexing_graph.stub(:queue_full?).and_return(false)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'and loader is empty' do
|
25
|
+
it 'exits and returns models_to_index' do
|
26
|
+
indexing_graph.stub(:loader_full?).and_return(false)
|
27
|
+
indexing_graph.stub(:models_to_index).and_return([Model.new(15)])
|
28
|
+
|
29
|
+
expect(indexing_graph.generate).to eql([Model.new(15)])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'and loader is not empty' do
|
34
|
+
it 'calls load_into_queue' do
|
35
|
+
indexing_graph.stub(:loader_full?).and_return(true, false)
|
36
|
+
indexing_graph.stub(:load_into_queue)
|
37
|
+
|
38
|
+
expect(indexing_graph).to receive(:load_into_queue)
|
39
|
+
|
40
|
+
indexing_graph.generate
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when queue is not empty' do
|
46
|
+
before do
|
47
|
+
indexing_graph.stub(:queue_full?).and_return(true, false)
|
48
|
+
indexing_graph.stub(:pop_model).and_return(Model.new(1))
|
49
|
+
indexing_graph.stub(:add_unvisited_associations)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'adds model if should be indexed' do
|
53
|
+
indexing_graph.stub(:model_should_be_indexed?).and_return(true)
|
54
|
+
|
55
|
+
expect(indexing_graph.generate).to eql([Model.new(1)])
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'does not add a model if it should not be indexed' do
|
59
|
+
indexing_graph.stub(:model_should_be_indexed?).and_return(false)
|
60
|
+
|
61
|
+
expect(indexing_graph.generate).to eql([])
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'adds unvisited associations to the graph' do
|
65
|
+
indexing_graph.stub(:model_should_be_indexed?).and_return(false)
|
66
|
+
|
67
|
+
expect(indexing_graph).to receive(:add_unvisited_associations)
|
68
|
+
|
69
|
+
indexing_graph.generate
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Notification::ModelAdditions do
|
4
|
+
let(:post) { Post.create!(title: 'Test', content: 'Lorem', tags: %w(tests)) }
|
5
|
+
let(:affiliate) { Affiliate.create(name: 'Some place') }
|
6
|
+
let(:author) { Author.create!(name: 'Paul Sorensen', affiliate: affiliate) }
|
7
|
+
let(:authorship) { Authorship.create!(post: post, author: author) }
|
8
|
+
before do
|
9
|
+
post.reload
|
10
|
+
author.reload
|
11
|
+
authorship.reload
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '.notifies_for_indexing' do
|
15
|
+
before do
|
16
|
+
post.stub(:update_associations)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'calls update_associations on save' do
|
20
|
+
expect(post).to receive(:update_associations)
|
21
|
+
|
22
|
+
post.save!
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'calls update_associations on destroy' do
|
26
|
+
expect(post).to receive(:update_associations)
|
27
|
+
|
28
|
+
post.destroy
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.load_with_notifiable_associations' do
|
33
|
+
it 'returns the correct eager-loaded models' do
|
34
|
+
model = Authorship.load_with_notifiable_associations(authorship.id).first
|
35
|
+
|
36
|
+
expect(model.association(:author).loaded?).to be_true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#associations_to_notify' do
|
41
|
+
it 'returns the correct models' do
|
42
|
+
expect(author.associations_to_notify).to eql([authorship])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#needs_to_notify?' do
|
47
|
+
it 'is false if there are no notifiable associations' do
|
48
|
+
expect(post.needs_to_notify?).to be_false
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'is true if there are notifiable associations' do
|
52
|
+
expect(author.needs_to_notify?).to be_true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#models_for_association' do
|
57
|
+
it 'should return an array of models for multiple association' do
|
58
|
+
models = post.models_for_association(:authorships)
|
59
|
+
|
60
|
+
expect(models.first).to be_a_kind_of(Authorship)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should return an array of one model for a singular association' do
|
64
|
+
models = author.models_for_association(:affiliate)
|
65
|
+
|
66
|
+
expect(models.first).to be_a_kind_of(Affiliate)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Lifesaver::Notification::NotifiableAssociations do
|
4
|
+
let(:notifiable_associations) do
|
5
|
+
Lifesaver::Notification::NotifiableAssociations.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe '#populate' do
|
9
|
+
it 'mirrors arguments for both keys' do
|
10
|
+
notifiable_associations.populate(:foo)
|
11
|
+
association_keys = notifiable_associations.send(:association_keys)
|
12
|
+
|
13
|
+
expect(association_keys.on_change).to eql(association_keys.on_notify)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'accepts arguments that are singular' do
|
17
|
+
notifiable_associations.populate(:foo)
|
18
|
+
association_keys = notifiable_associations.send(:association_keys)
|
19
|
+
|
20
|
+
expect(association_keys.on_change).to eql([:foo])
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'accepts arguments that are arrays' do
|
24
|
+
notifiable_associations.populate([:foo, :bar])
|
25
|
+
association_keys = notifiable_associations.send(:association_keys)
|
26
|
+
|
27
|
+
expect(association_keys.on_notify).to eql([:foo, :bar])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'accepts an options hash for :only_on_notify' do
|
31
|
+
notifiable_associations.populate(:foo, only_on_notify: [:baz])
|
32
|
+
association_keys = notifiable_associations.send(:association_keys)
|
33
|
+
|
34
|
+
expect(association_keys.on_notify).to eql([:foo, :baz])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'accepts an options hash for :only_on_change' do
|
38
|
+
notifiable_associations.populate(:foo, only_on_change: [:baz])
|
39
|
+
association_keys = notifiable_associations.send(:association_keys)
|
40
|
+
|
41
|
+
expect(association_keys.on_change).to eql([:foo, :baz])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#any_to_notify?' do
|
46
|
+
it 'is true if the :on_notify array is not empty' do
|
47
|
+
notifiable_associations.populate([], only_on_notify: [:baz])
|
48
|
+
|
49
|
+
expect(notifiable_associations.any_to_notify?).to be_true
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'is false if the :on_notify array is empty' do
|
53
|
+
notifiable_associations.populate([], only_on_change: [:baz])
|
54
|
+
|
55
|
+
expect(notifiable_associations.any_to_notify?).to be_false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#on_notify' do
|
60
|
+
it 'reads the :on_notify key' do
|
61
|
+
notifiable_associations.populate([], only_on_notify: [:baz])
|
62
|
+
keys = notifiable_associations.send(:association_keys)
|
63
|
+
|
64
|
+
expect(notifiable_associations.on_notify).to eql(keys.on_notify)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
describe '#on_change' do
|
68
|
+
it 'reads the :on_change key' do
|
69
|
+
notifiable_associations.populate([], only_on_change: [:baz])
|
70
|
+
keys = notifiable_associations.send(:association_keys)
|
71
|
+
|
72
|
+
expect(notifiable_associations.on_change).to eql(keys.on_change)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|