lifesaver 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.rspec +1 -0
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile +4 -0
  5. data/README.md +33 -13
  6. data/lib/lifesaver.rb +34 -11
  7. data/lib/lifesaver/config.rb +16 -0
  8. data/lib/lifesaver/index_worker.rb +10 -10
  9. data/lib/lifesaver/indexing/enqueuer.rb +37 -0
  10. data/lib/lifesaver/indexing/indexer.rb +40 -0
  11. data/lib/lifesaver/indexing/model_additions.rb +55 -0
  12. data/lib/lifesaver/notification/eager_loader.rb +48 -0
  13. data/lib/lifesaver/notification/enqueuer.rb +24 -0
  14. data/lib/lifesaver/notification/indexing_graph.rb +91 -0
  15. data/lib/lifesaver/notification/model_additions.rb +104 -0
  16. data/lib/lifesaver/notification/notifiable_associations.rb +49 -0
  17. data/lib/lifesaver/notification/traversal_queue.rb +48 -0
  18. data/lib/lifesaver/railtie.rb +4 -3
  19. data/lib/lifesaver/serialized_model.rb +3 -0
  20. data/lib/lifesaver/version.rb +1 -1
  21. data/lib/lifesaver/visitor_worker.rb +8 -4
  22. data/spec/integration/lifesaver_spec.rb +78 -0
  23. data/spec/spec_helper.rb +12 -10
  24. data/spec/support/active_record.rb +3 -3
  25. data/spec/support/test_models.rb +8 -6
  26. data/spec/support/tire_helper.rb +37 -0
  27. data/spec/unit/config_spec.rb +17 -0
  28. data/spec/unit/indexing/indexer_spec.rb +51 -0
  29. data/spec/unit/indexing/model_addtions_spec.rb +53 -0
  30. data/spec/unit/notification/eager_loader_spec.rb +64 -0
  31. data/spec/unit/notification/enqueuer_spec.rb +39 -0
  32. data/spec/unit/notification/indexing_graph_spec.rb +73 -0
  33. data/spec/unit/notification/model_additions_spec.rb +69 -0
  34. data/spec/unit/notification/notifiable_associations_spec.rb +75 -0
  35. data/spec/unit/notification/traversal_queue_spec.rb +67 -0
  36. metadata +40 -14
  37. data/lib/lifesaver/index_graph.rb +0 -53
  38. data/lib/lifesaver/marshal.rb +0 -43
  39. data/lib/lifesaver/model_additions.rb +0 -133
  40. data/spec/lifesaver/index_graph_spec.rb +0 -64
  41. data/spec/lifesaver/marshal_spec.rb +0 -80
  42. data/spec/lifesaver/model_additions_spec.rb +0 -91
  43. 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