lifesaver 0.0.1 → 0.1.0

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 (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,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Lifesaver::Notification::TraversalQueue do
4
+ let(:traversal_queue) { Lifesaver::Notification::TraversalQueue.new }
5
+ let(:model) { Model.new(1) }
6
+
7
+ describe '#push' do
8
+ it 'adds an univisited model' do
9
+ traversal_queue.push(model)
10
+
11
+ expect(traversal_queue.size).to eql(1)
12
+ end
13
+
14
+ it 'ignores a visited model' do
15
+ traversal_queue.push(model)
16
+ traversal_queue.push(model)
17
+
18
+ expect(traversal_queue.size).to eql(1)
19
+ end
20
+ end
21
+
22
+ describe '#<<' do
23
+ it 'adds an univisited model' do
24
+ traversal_queue << model
25
+
26
+ expect(traversal_queue.size).to eql(1)
27
+ end
28
+
29
+ it 'ignores a visited model' do
30
+ traversal_queue << model
31
+ traversal_queue << model
32
+
33
+ expect(traversal_queue.size).to eql(1)
34
+ end
35
+ end
36
+
37
+ describe '#pop' do
38
+ context 'when not empty' do
39
+ it 'returns the first model in the queue' do
40
+ another_model = Model.new(3)
41
+ traversal_queue << model
42
+ traversal_queue << another_model
43
+
44
+ expect(traversal_queue.pop).to eql(model)
45
+ end
46
+ end
47
+
48
+ context 'when empty' do
49
+ it 'returns nil' do
50
+ expect(traversal_queue.pop).to be_nil
51
+ end
52
+ end
53
+ end
54
+
55
+ describe '#empty?' do
56
+ it 'is true when empty' do
57
+ expect(traversal_queue.empty?).to be_true
58
+ end
59
+
60
+ it 'is not true when not empty' do
61
+ traversal_queue << model
62
+
63
+ expect(traversal_queue.empty?).to be_false
64
+ end
65
+ end
66
+
67
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lifesaver
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-09 00:00:00.000000000 Z
12
+ date: 2013-12-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -165,6 +165,7 @@ extra_rdoc_files: []
165
165
  files:
166
166
  - .coveralls.yml
167
167
  - .gitignore
168
+ - .rspec
168
169
  - .travis.yml
169
170
  - CHANGELOG.md
170
171
  - Gemfile
@@ -172,21 +173,36 @@ files:
172
173
  - README.md
173
174
  - Rakefile
174
175
  - lib/lifesaver.rb
175
- - lib/lifesaver/index_graph.rb
176
+ - lib/lifesaver/config.rb
176
177
  - lib/lifesaver/index_worker.rb
177
- - lib/lifesaver/marshal.rb
178
- - lib/lifesaver/model_additions.rb
178
+ - lib/lifesaver/indexing/enqueuer.rb
179
+ - lib/lifesaver/indexing/indexer.rb
180
+ - lib/lifesaver/indexing/model_additions.rb
181
+ - lib/lifesaver/notification/eager_loader.rb
182
+ - lib/lifesaver/notification/enqueuer.rb
183
+ - lib/lifesaver/notification/indexing_graph.rb
184
+ - lib/lifesaver/notification/model_additions.rb
185
+ - lib/lifesaver/notification/notifiable_associations.rb
186
+ - lib/lifesaver/notification/traversal_queue.rb
179
187
  - lib/lifesaver/railtie.rb
188
+ - lib/lifesaver/serialized_model.rb
180
189
  - lib/lifesaver/version.rb
181
190
  - lib/lifesaver/visitor_worker.rb
182
191
  - lifesaver.gemspec
183
- - spec/lifesaver/index_graph_spec.rb
184
- - spec/lifesaver/marshal_spec.rb
185
- - spec/lifesaver/model_additions_spec.rb
186
- - spec/lifesaver_spec.rb
192
+ - spec/integration/lifesaver_spec.rb
187
193
  - spec/spec_helper.rb
188
194
  - spec/support/active_record.rb
189
195
  - spec/support/test_models.rb
196
+ - spec/support/tire_helper.rb
197
+ - spec/unit/config_spec.rb
198
+ - spec/unit/indexing/indexer_spec.rb
199
+ - spec/unit/indexing/model_addtions_spec.rb
200
+ - spec/unit/notification/eager_loader_spec.rb
201
+ - spec/unit/notification/enqueuer_spec.rb
202
+ - spec/unit/notification/indexing_graph_spec.rb
203
+ - spec/unit/notification/model_additions_spec.rb
204
+ - spec/unit/notification/notifiable_associations_spec.rb
205
+ - spec/unit/notification/traversal_queue_spec.rb
190
206
  homepage: ''
191
207
  licenses:
192
208
  - MIT
@@ -206,18 +222,28 @@ required_rubygems_version: !ruby/object:Gem::Requirement
206
222
  - - ! '>='
207
223
  - !ruby/object:Gem::Version
208
224
  version: '0'
225
+ segments:
226
+ - 0
227
+ hash: -3695998717918674218
209
228
  requirements: []
210
229
  rubyforge_project:
211
- rubygems_version: 1.8.23
230
+ rubygems_version: 1.8.25
212
231
  signing_key:
213
232
  specification_version: 3
214
233
  summary: Indexes your ActiveRecord models in elasticsearch asynchronously by making
215
234
  use of tire and resque
216
235
  test_files:
217
- - spec/lifesaver/index_graph_spec.rb
218
- - spec/lifesaver/marshal_spec.rb
219
- - spec/lifesaver/model_additions_spec.rb
220
- - spec/lifesaver_spec.rb
236
+ - spec/integration/lifesaver_spec.rb
221
237
  - spec/spec_helper.rb
222
238
  - spec/support/active_record.rb
223
239
  - spec/support/test_models.rb
240
+ - spec/support/tire_helper.rb
241
+ - spec/unit/config_spec.rb
242
+ - spec/unit/indexing/indexer_spec.rb
243
+ - spec/unit/indexing/model_addtions_spec.rb
244
+ - spec/unit/notification/eager_loader_spec.rb
245
+ - spec/unit/notification/enqueuer_spec.rb
246
+ - spec/unit/notification/indexing_graph_spec.rb
247
+ - spec/unit/notification/model_additions_spec.rb
248
+ - spec/unit/notification/notifiable_associations_spec.rb
249
+ - spec/unit/notification/traversal_queue_spec.rb
@@ -1,53 +0,0 @@
1
- module Lifesaver
2
- class IndexGraph
3
- def self.generate(marshalled_models)
4
- models_to_index = []
5
- visited_models = {}
6
- graph = []
7
- marshalled_models.each do |m|
8
- mdl, opts = Lifesaver::Marshal.load(m)
9
- if mdl
10
- if opts[:status] == :notified
11
- graph << mdl
12
- elsif opts[:status] == :changed
13
- visited_models[self.visited_model_key(mdl)] = true
14
- graph |= self.notified_models(mdl, true)
15
- end
16
- end
17
- end
18
- graph.each {|m| visited_models[self.visited_model_key(m)] = true }
19
- while !graph.empty?
20
- mdl = graph.shift
21
- models_to_index << mdl if mdl.has_index?
22
- self.notified_models(mdl).each do |m|
23
- unless visited_models[self.visited_model_key(m)]
24
- visited_models[self.visited_model_key(m)] = true
25
- graph << m
26
- end
27
- end
28
- end
29
- models_to_index
30
- end
31
-
32
- def self.visited_model_key(mdl)
33
- if mdl.is_a?(Hash)
34
- klass = mdl[:class].to_s.classify
35
- "#{klass}_#{mdl[:id]}"
36
- elsif mdl.try(:id)
37
- "#{mdl.class.name}_#{mdl.id}"
38
- end
39
- end
40
-
41
- def self.notified_models(mdl, on_change = false)
42
- if Lifesaver::Marshal.is_serialized?(mdl)
43
- mdl, opts = Lifesaver::Marshal.load(mdl)
44
- end
45
- models = []
46
- key = on_change ? :on_change : :on_notify
47
- mdl.class.notifiable_associations[key].each do |assoc|
48
- models |= mdl.association_models(assoc)
49
- end
50
- models
51
- end
52
- end
53
- end
@@ -1,43 +0,0 @@
1
- module Lifesaver
2
- class Marshal
3
- def self.dump(obj, opts={})
4
- raise unless opts.is_a?(Hash)
5
- opts[:class] = obj.class.name.underscore.to_sym
6
- opts[:id] = obj.id
7
- opts
8
- end
9
-
10
- def self.load(obj)
11
- raise unless self.is_serialized?(obj)
12
- obj = self.sanitize(obj)
13
- klass = obj[:class].to_s.classify.constantize
14
- if klass.exists?(obj[:id])
15
- mdl = klass.find(obj[:id])
16
- obj.delete(:id)
17
- obj.delete(:class)
18
- return mdl, obj
19
- else
20
- nil
21
- end
22
- end
23
-
24
- def self.sanitize(obj)
25
- raise unless obj.is_a?(Hash)
26
- obj = obj.symbolize_keys
27
- obj[:id] = obj[:id].to_i if obj[:id]
28
- obj[:class] = obj[:class].to_sym if obj[:class]
29
- obj[:status] = obj[:status].to_sym if obj[:status]
30
- obj
31
- end
32
-
33
- def self.is_serialized?(obj)
34
- if obj.is_a?(Hash)
35
- obj = self.sanitize(obj)
36
- if obj.key?(:class) && obj.key?(:id)
37
- return true
38
- end
39
- end
40
- false
41
- end
42
- end
43
- end
@@ -1,133 +0,0 @@
1
- module Lifesaver
2
- module ModelAdditions
3
- module ClassMethods
4
-
5
- def notifies_for_indexing(*args)
6
- self.notifiable_associations = { on_change: [], on_notify: [] }
7
- opts = args.pop if args.last.is_a?(Hash)
8
- args.each do |a|
9
- self.notifiable_associations[:on_change] << a
10
- self.notifiable_associations[:on_notify] << a
11
- end
12
- %w(on_change on_notify).each do |k|
13
- opt = opts[("only_" + k).to_sym] if opts
14
- key = k.to_sym
15
- if opt
16
- if opt.is_a?(Array)
17
- self.notifiable_associations[key] |= opt
18
- else
19
- self.notifiable_associations[key] << opt
20
- end
21
- end
22
- end
23
- notification_callbacks
24
- end
25
-
26
- def enqueues_indexing(options = {})
27
- indexing_callbacks
28
- end
29
-
30
- private
31
-
32
- def notification_callbacks(options={})
33
- after_save do
34
- send :update_associations, options.merge(operation: :update)
35
- end
36
- before_destroy do
37
- send :update_associations, options.merge(operation: :destroy)
38
- end
39
- end
40
-
41
- def indexing_callbacks(options={})
42
- after_save do
43
- send :enqueue_indexing, options.merge(operation: :update)
44
- end
45
- after_destroy do
46
- send :enqueue_indexing, options.merge(operation: :destroy)
47
- end
48
- end
49
- end
50
-
51
- def self.included(base)
52
- base.class_attribute :notifiable_associations
53
- base.notifiable_associations = { on_change: [], on_notify: [] }
54
- base.extend(ClassMethods)
55
- end
56
-
57
- def association_models(assoc)
58
- models = []
59
- association = send(assoc.to_sym)
60
- unless association.nil?
61
- if association.respond_to?(:each)
62
- association.each do |m|
63
- models << m
64
- end
65
- else
66
- models << association
67
- end
68
- end
69
- models
70
- end
71
-
72
- def has_index?
73
- self.respond_to?(:tire)
74
- end
75
-
76
- def suppress_indexing
77
- @indexing_suppressed = true
78
- end
79
-
80
- def unsuppress_indexing
81
- @indexing_suppressed = false
82
- end
83
-
84
- private
85
-
86
- def enqueue_indexing(opts)
87
- if has_index? && !suppress_indexing?
88
- ::Resque.enqueue(
89
- Lifesaver::IndexWorker,
90
- self.class.name.underscore.to_sym,
91
- self.id,
92
- opts[:operation]
93
- )
94
- end
95
- end
96
-
97
- def dependent_association_map
98
- dependent = {}
99
- self.class.reflect_on_all_associations.each do |assoc|
100
- dependent[assoc.name.to_sym] = true if assoc.options[:dependent].present?
101
- end
102
- dependent
103
- end
104
-
105
- def update_associations(opts)
106
- models = []
107
- if opts[:operation] == :destroy
108
- dependent = dependent_association_map
109
- assoc_models = []
110
- self.class.notifiable_associations[:on_change].each do |assoc|
111
- assoc_models |= association_models(assoc) unless dependent[assoc]
112
- end
113
- assoc_models.each do |m|
114
- models << Lifesaver::Marshal.dump(m, {status: :notified})
115
- end
116
- elsif opts[:operation] == :update
117
- models << Lifesaver::Marshal.dump(self, {status: :changed})
118
- end
119
-
120
- ::Resque.enqueue(Lifesaver::VisitorWorker, models) unless models.empty?
121
- end
122
-
123
- def validate_options(options)
124
- # on: should only have active model callback verbs (create, update, destroy?)
125
- # after: (next versions after you use resque scheduler) time to schedule
126
- # only: specifies fields that trigger changes
127
- end
128
-
129
- def suppress_indexing?
130
- Lifesaver.indexing_suppressed? || @indexing_suppressed || false
131
- end
132
- end
133
- end
@@ -1,64 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Lifesaver::IndexGraph do
4
- Lifesaver.suppress_indexing
5
-
6
- describe ".visited_model_key" do
7
- it "should return a key when passed an ActiveRecord model" do
8
- post = Post.create(title: "Some post")
9
- expect(Lifesaver::IndexGraph.visited_model_key(post)).to eql("Post_1")
10
- end
11
-
12
- it "should return a key when passed a Hash" do
13
- post = {class: "post", id: "1", status: "notified"}
14
- expect(Lifesaver::IndexGraph.visited_model_key(post)).to eql("Post_1")
15
- end
16
- end
17
-
18
- describe ".notified_models" do
19
- after(:all) do
20
- Post.destroy_all
21
- Author.destroy_all
22
- Authorship.destroy_all
23
- end
24
- context "when passed model has changed" do
25
- before(:each) do
26
- @post = Post.create(title: "Some post")
27
- @author = Author.create(name: "Some guy")
28
- Authorship.create(post: @post, author: @author)
29
- end
30
-
31
- it "should return notified models when passed an ActiveRecord model" do
32
- models = Lifesaver::IndexGraph.notified_models(@post, true)
33
- expect(models.size).to eql(1)
34
- end
35
-
36
- it "should return notified models when passed a Hash" do
37
- post = {class: "post", id: "1", status: "changed"}
38
- models = Lifesaver::IndexGraph.notified_models(post, true)
39
- expect(models.size).to eql(1)
40
- end
41
- end
42
-
43
- context "when passed model has not changed" do
44
- before(:each) do
45
- @post = Post.create(title: "Some post")
46
- @author = Author.create(name: "Some guy")
47
- Authorship.create(post: @post, author: @author)
48
- end
49
-
50
- it "should return notified models when passed an ActiveRecord model" do
51
- models = Lifesaver::IndexGraph.notified_models(@author)
52
- expect(models.size).to eql(1)
53
- end
54
-
55
- it "should return notified models when passed a Hash" do
56
- author = {class: "author", id: "1", status: "notified"}
57
- models = Lifesaver::IndexGraph.notified_models(author)
58
- expect(models.size).to eql(1)
59
- end
60
- end
61
- end
62
-
63
- Lifesaver.unsuppress_indexing
64
- end