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