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,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