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,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
|
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-
|
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/
|
176
|
+
- lib/lifesaver/config.rb
|
176
177
|
- lib/lifesaver/index_worker.rb
|
177
|
-
- lib/lifesaver/
|
178
|
-
- lib/lifesaver/
|
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/
|
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.
|
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/
|
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
|
data/lib/lifesaver/marshal.rb
DELETED
@@ -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
|