tantot 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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/tantot/changes.rb +65 -0
- data/lib/tantot/collector/base.rb +6 -0
- data/lib/tantot/collector/block.rb +78 -0
- data/lib/tantot/collector/watcher.rb +87 -0
- data/lib/tantot/collector.rb +76 -0
- data/lib/tantot/config.rb +17 -0
- data/lib/tantot/errors.rb +4 -0
- data/lib/tantot/extensions/chewy.rb +88 -0
- data/lib/tantot/extensions/grape/middleware.rb +17 -0
- data/lib/tantot/formatter/compact.rb +9 -0
- data/lib/tantot/formatter/detailed.rb +9 -0
- data/lib/tantot/formatter.rb +10 -0
- data/lib/tantot/observe.rb +84 -0
- data/lib/tantot/performer/bypass.rb +9 -0
- data/lib/tantot/performer/inline.rb +9 -0
- data/lib/tantot/performer/sidekiq.rb +19 -0
- data/lib/tantot/performer.rb +17 -0
- data/lib/tantot/railtie.rb +23 -0
- data/lib/tantot/registry.rb +11 -0
- data/lib/tantot/version.rb +3 -0
- data/lib/tantot/watcher.rb +16 -0
- data/lib/tantot.rb +62 -0
- data/spec/changes_spec.rb +60 -0
- data/spec/extensions/chewy_spec.rb +127 -0
- data/spec/sidekiq_spec.rb +100 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/tantot_spec.rb +299 -0
- data/tantot.gemspec +31 -0
- metadata +202 -0
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'tantot/formatter/compact'
|
2
|
+
require 'tantot/formatter/detailed'
|
3
|
+
|
4
|
+
module Tantot
|
5
|
+
module Formatter
|
6
|
+
def self.resolve(name)
|
7
|
+
"Tantot::Formatter::#{name.to_s.camelize}".safe_constantize or raise "Can't find formatter class `#{name}`"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'cityhash'
|
2
|
+
|
3
|
+
module Tantot
|
4
|
+
module Observe
|
5
|
+
module Helpers
|
6
|
+
def condition_proc(context)
|
7
|
+
attributes = context[:attributes]
|
8
|
+
options = context[:options]
|
9
|
+
proc do
|
10
|
+
has_changes = attributes.any? ? (self.destroyed? || (self._watch_changes.keys & attributes).any?) : true
|
11
|
+
has_changes && (!options.key?(:if) || self.instance_exec(&options[:if]))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_proc(context)
|
16
|
+
proc do
|
17
|
+
attributes = context[:attributes]
|
18
|
+
watched_changes =
|
19
|
+
if attributes.any?
|
20
|
+
if self.destroyed?
|
21
|
+
attributes.each.with_object({}) {|attr, hash| hash[attr] = [self[attr]]}
|
22
|
+
else
|
23
|
+
self._watch_changes.slice(*attributes)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
self._watch_changes
|
27
|
+
end
|
28
|
+
|
29
|
+
Tantot.collector.push(context, self, watched_changes)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
extend Helpers
|
35
|
+
|
36
|
+
module ActiveRecordMethods
|
37
|
+
extend ActiveSupport::Concern
|
38
|
+
|
39
|
+
def _watch_changes
|
40
|
+
Tantot.config.use_after_commit_callbacks ? self.previous_changes : self.changes
|
41
|
+
end
|
42
|
+
|
43
|
+
class_methods do
|
44
|
+
# watch watcher, :attr, :attr, :attr, option: :value
|
45
|
+
# watch :attr, :attr, option: :value, &block
|
46
|
+
# watch watcher, option: :value
|
47
|
+
# watch option: :value, &block
|
48
|
+
def watch(*args, &block)
|
49
|
+
options = args.extract_options!
|
50
|
+
|
51
|
+
watcher = args.first.is_a?(String) || args.first.is_a?(Class) ? Tantot.derive_watcher(args.shift) : nil
|
52
|
+
unless !!watcher ^ block_given?
|
53
|
+
raise ArgumentError.new("At least one, and only one of `watcher` or `block` can be passed")
|
54
|
+
end
|
55
|
+
|
56
|
+
attributes = args.collect(&:to_s)
|
57
|
+
|
58
|
+
context = {
|
59
|
+
model: self,
|
60
|
+
attributes: attributes,
|
61
|
+
options: options
|
62
|
+
}
|
63
|
+
|
64
|
+
context[:watcher] = watcher if watcher
|
65
|
+
context[:block_id] = CityHash.hash64(block.source_location.collect(&:to_s).join) if block_given?
|
66
|
+
|
67
|
+
Tantot.collector.register_watch(context, block)
|
68
|
+
|
69
|
+
callback_options = {
|
70
|
+
if: Observe.condition_proc(context)
|
71
|
+
}
|
72
|
+
update_proc = Observe.update_proc(context)
|
73
|
+
|
74
|
+
if Tantot.config.use_after_commit_callbacks
|
75
|
+
after_commit(callback_options, &update_proc)
|
76
|
+
else
|
77
|
+
after_save(callback_options, &update_proc)
|
78
|
+
after_destroy(callback_options, &update_proc)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Tantot
|
2
|
+
module Performer
|
3
|
+
class Sidekiq
|
4
|
+
class Worker
|
5
|
+
include ::Sidekiq::Worker
|
6
|
+
|
7
|
+
def perform(context, changes)
|
8
|
+
context, changes = Tantot.collector.unmarshal(context, changes)
|
9
|
+
Tantot.collector.perform(context, changes)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(context, changes)
|
14
|
+
context, changes = Tantot.collector.marshal(context, changes)
|
15
|
+
Tantot::Performer::Sidekiq::Worker.perform_async(context, changes)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'tantot/performer/bypass'
|
2
|
+
require 'tantot/performer/inline'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'sidekiq'
|
6
|
+
require 'tantot/performer/sidekiq'
|
7
|
+
rescue LoadError
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
module Tantot
|
12
|
+
module Performer
|
13
|
+
def self.resolve(name)
|
14
|
+
"Tantot::Performer::#{name.to_s.camelize}".safe_constantize or raise "Can't find performer class `#{name}`"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Tantot
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
class RequestStrategy
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
Tantot.collector.run { @app.call(env) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
console do |app|
|
14
|
+
# Will sweep after every push (unfortunately)
|
15
|
+
Tantot.config.console_mode = true
|
16
|
+
end
|
17
|
+
|
18
|
+
initializer 'tantot.request_strategy' do |app|
|
19
|
+
Tantot.logger.debug { "[Tantot] Installing Rails middleware" }
|
20
|
+
app.config.middleware.insert_after(Rails::Rack::Logger, RequestStrategy)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Tantot
|
2
|
+
module Watcher
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_attribute :watcher_options_hash
|
7
|
+
end
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def watcher_options(opts = {})
|
11
|
+
self.watcher_options_hash ||= Tantot::Config.instance.default_watcher_options
|
12
|
+
self.watcher_options_hash = self.watcher_options_hash.merge(opts)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/tantot.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'tantot/version'
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext'
|
5
|
+
require 'singleton'
|
6
|
+
|
7
|
+
require 'tantot/errors'
|
8
|
+
require 'tantot/config'
|
9
|
+
require 'tantot/registry'
|
10
|
+
require 'tantot/changes'
|
11
|
+
require 'tantot/watcher'
|
12
|
+
require 'tantot/performer'
|
13
|
+
require 'tantot/formatter'
|
14
|
+
require 'tantot/collector'
|
15
|
+
require 'tantot/observe'
|
16
|
+
|
17
|
+
require 'tantot/extensions/chewy'
|
18
|
+
require 'tantot/extensions/grape/middleware'
|
19
|
+
|
20
|
+
require 'tantot/railtie' if defined?(::Rails::Railtie)
|
21
|
+
|
22
|
+
ActiveSupport.on_load(:active_record) do
|
23
|
+
ActiveRecord::Base.send(:include, Tantot::Observe::ActiveRecordMethods)
|
24
|
+
ActiveRecord::Base.send(:include, Tantot::Extensions::Chewy)
|
25
|
+
end
|
26
|
+
|
27
|
+
module Tantot
|
28
|
+
class << self
|
29
|
+
attr_writer :logger
|
30
|
+
|
31
|
+
def derive_watcher(name)
|
32
|
+
watcher =
|
33
|
+
if name.is_a?(Class)
|
34
|
+
name
|
35
|
+
else
|
36
|
+
class_name = "#{name.camelize}Watcher"
|
37
|
+
watcher = class_name.safe_constantize
|
38
|
+
raise Tantot::UnderivableWatcher, "Can not find watcher named `#{class_name}`" unless watcher
|
39
|
+
watcher
|
40
|
+
end
|
41
|
+
raise Tantot::UnderivableWatcher, "Watcher class does not include Tantot::Watcher: #{watcher}" unless watcher.included_modules.include?(Tantot::Watcher)
|
42
|
+
watcher
|
43
|
+
end
|
44
|
+
|
45
|
+
def collector
|
46
|
+
Thread.current[:tantot_collector] ||= Tantot::Collector::Manager.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def config
|
50
|
+
Tantot::Config.instance
|
51
|
+
end
|
52
|
+
|
53
|
+
def registry
|
54
|
+
Tantot::Registry.instance
|
55
|
+
end
|
56
|
+
|
57
|
+
def logger
|
58
|
+
@logger || Rails.logger
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Tantot::Changes do
|
4
|
+
|
5
|
+
describe Tantot::Changes::ById do
|
6
|
+
let(:raw_changes) { {1 => {"id" => [nil, 1], "name" => [nil, "foo"]}, 2 => {"name" => ["foo", nil]}, 3 => {"id" => [3, nil], "name" => ["bar", "baz", nil]}} }
|
7
|
+
subject { described_class.new(raw_changes) }
|
8
|
+
|
9
|
+
it "should find ids" do
|
10
|
+
expect(subject.ids).to eq([1, 2, 3])
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should find all values for an attribute, removing nil by default" do
|
14
|
+
expect(subject.for_attribute(:name)).to eq(["foo", "bar", "baz"])
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find all values for an attribute, including nil" do
|
18
|
+
expect(subject.for_attribute(:name, false)).to eq([nil, "foo", "bar", "baz"])
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should find all changed attributes" do
|
22
|
+
expect(subject.attributes).to eq([:id, :name])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should correctly implement ==" do
|
26
|
+
expect(subject).to eq(described_class.new(raw_changes))
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should implement Enumerable" do
|
30
|
+
expect(subject.collect {|id, changes| [id, changes]}).to eq(raw_changes.to_a)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe Tantot::Changes::ByModel do
|
35
|
+
before do
|
36
|
+
stub_const('City', Class.new)
|
37
|
+
stub_const('Country', Class.new)
|
38
|
+
end
|
39
|
+
let(:city_changes) { {1 => {"id" => [nil, 1], "name" => [nil, "foo"]}, 2 => {"name" => ["foo", nil]}, 3 => {"id" => [3, nil], "name" => ["bar", "baz", nil]}} }
|
40
|
+
let(:country_changes) { {1 => {'id' => [nil, 1]}} }
|
41
|
+
let(:raw_changes) { {City => city_changes, Country => country_changes} }
|
42
|
+
subject { described_class.new(raw_changes) }
|
43
|
+
|
44
|
+
it "should find models" do
|
45
|
+
expect(subject.models).to eq([City, Country])
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should implement []" do
|
49
|
+
expect(subject[City]).to eq(Tantot::Changes::ById.new(city_changes))
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should implement Enumerable" do
|
53
|
+
expect(subject.collect {|model, changes| [model, changes]}).to eq([
|
54
|
+
[City, Tantot::Changes::ById.new(city_changes)],
|
55
|
+
[Country, Tantot::Changes::ById.new(country_changes)]
|
56
|
+
])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Tantot::Extensions::Chewy do
|
4
|
+
|
5
|
+
# Stub the Chewy namespace
|
6
|
+
before do
|
7
|
+
stub_const("Chewy", {})
|
8
|
+
end
|
9
|
+
|
10
|
+
[nil, :self, :class_method, :block].product([:some, :all]).each do |backreference_opt, attribute_opt|
|
11
|
+
it "should update indexes using backreference: #{backreference_opt.inspect}, attributes: #{attribute_opt}" do
|
12
|
+
chewy_type = double
|
13
|
+
|
14
|
+
watch_index_params = ['foo']
|
15
|
+
watch_index_params << :id if attribute_opt == :some
|
16
|
+
|
17
|
+
block_callback = proc do |changes|
|
18
|
+
self.yielded_changes = changes
|
19
|
+
[1, 2, 3]
|
20
|
+
end
|
21
|
+
|
22
|
+
case backreference_opt
|
23
|
+
when nil, :block
|
24
|
+
when :self
|
25
|
+
watch_index_params << {method: :self}
|
26
|
+
when :class_method
|
27
|
+
watch_index_params << {method: :class_get_ids}
|
28
|
+
end
|
29
|
+
|
30
|
+
stub_model(:city) do
|
31
|
+
class_attribute :yielded_changes
|
32
|
+
|
33
|
+
if backreference_opt == :block
|
34
|
+
watch_index(*watch_index_params, &block_callback)
|
35
|
+
else
|
36
|
+
watch_index(*watch_index_params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.class_get_ids(changes)
|
40
|
+
self.yielded_changes = changes
|
41
|
+
[1, 2, 3]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
city1 = city2 = nil
|
46
|
+
|
47
|
+
Tantot.collector.run do
|
48
|
+
city1 = City.create!
|
49
|
+
city2 = City.create!
|
50
|
+
|
51
|
+
# Stub the integration point between us and Chewy
|
52
|
+
expect(Chewy).to receive(:strategy).with(:atomic).and_yield
|
53
|
+
expect(Chewy).to receive(:derive_type).with('foo').and_return(chewy_type)
|
54
|
+
|
55
|
+
# Depending on backreference
|
56
|
+
case backreference_opt
|
57
|
+
when nil, :self
|
58
|
+
# Implicit and self reference will update with the created model id
|
59
|
+
expect(chewy_type).to receive(:update_index).with([city1.id, city2.id], {})
|
60
|
+
when :class_method, :block
|
61
|
+
# Validate that the returned ids are updated
|
62
|
+
expect(chewy_type).to receive(:update_index).with([1, 2, 3], {})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Make sure the callbacks received the changes
|
67
|
+
if [:class_method, :block].include?(backreference_opt)
|
68
|
+
expect(City.yielded_changes).to eq(Tantot::Changes::ById.new({city1.id => {"id" => [nil, city1.id]}, city2.id => {"id" => [nil, city2.id]}}))
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should allow registering an index watch on self (all attributes, destroy)" do
|
75
|
+
chewy_type = double
|
76
|
+
|
77
|
+
stub_model(:city) do
|
78
|
+
watch_index 'foo'
|
79
|
+
end
|
80
|
+
|
81
|
+
city = City.create!
|
82
|
+
Tantot.collector.sweep(performer: :bypass)
|
83
|
+
|
84
|
+
Tantot.collector.run do
|
85
|
+
city.destroy
|
86
|
+
|
87
|
+
expect(Chewy).to receive(:strategy).with(:atomic).and_yield
|
88
|
+
expect(Chewy).to receive(:derive_type).with('foo').and_return(chewy_type)
|
89
|
+
expect(chewy_type).to receive(:update_index).with([city.id], {})
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should allow registering an index watch on self (all attributes, destroy, block)" do
|
94
|
+
chewy_type = double
|
95
|
+
|
96
|
+
stub_model(:city) do
|
97
|
+
watch_index 'foo' do |changes|
|
98
|
+
changes.ids
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
city = City.create!
|
103
|
+
Tantot.collector.sweep(performer: :bypass)
|
104
|
+
|
105
|
+
Tantot.collector.run do
|
106
|
+
city.destroy
|
107
|
+
|
108
|
+
expect(Chewy).to receive(:strategy).with(:atomic).and_yield
|
109
|
+
expect(Chewy).to receive(:derive_type).with('foo').and_return(chewy_type)
|
110
|
+
expect(chewy_type).to receive(:update_index).with([city.id], {})
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should allow returning nothing in a callback" do
|
115
|
+
stub_model(:city) do
|
116
|
+
watch_index('foo') { 1 if false }
|
117
|
+
watch_index('bar') { [] }
|
118
|
+
watch_index('baz') { nil }
|
119
|
+
end
|
120
|
+
|
121
|
+
Tantot.collector.run do
|
122
|
+
City.create!
|
123
|
+
|
124
|
+
expect(Chewy).not_to receive(:derive_type)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if defined?(::Sidekiq)
|
4
|
+
require 'sidekiq/testing'
|
5
|
+
|
6
|
+
describe Tantot::Performer::Sidekiq do
|
7
|
+
around do |example|
|
8
|
+
Tantot.config.performer = :sidekiq
|
9
|
+
example.run
|
10
|
+
Tantot.config.performer = :inline
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Tantot::Collector::Watcher do
|
14
|
+
|
15
|
+
class SidekiqWatcher
|
16
|
+
include Tantot::Watcher
|
17
|
+
|
18
|
+
def perform(changes)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
Sidekiq::Worker.clear_all
|
24
|
+
stub_model(:city) do
|
25
|
+
watch SidekiqWatcher, :name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should call a sidekiq worker" do
|
30
|
+
Tantot.collector.run do
|
31
|
+
City.create name: 'foo'
|
32
|
+
end
|
33
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.size).to eq(1)
|
34
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.first["args"]).to eq([{"watcher" => "SidekiqWatcher", "collector_class" => "Tantot::Collector::Watcher"}, {"City" => {"1" => {"name" => [nil, 'foo']}}}])
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should call the watcher" do
|
38
|
+
::Sidekiq::Testing.inline! do
|
39
|
+
Tantot.collector.run do
|
40
|
+
city = City.create name: 'foo'
|
41
|
+
expect_any_instance_of(SidekiqWatcher).to receive(:perform).with(Tantot::Changes::ByModel.new({City => {city.id => {"name" => [nil, 'foo']}}}))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should skip sidekiq and process atomically when `sweep`ing, then resume using sidekiq" do
|
47
|
+
Sidekiq::Testing.fake! do
|
48
|
+
Tantot.collector.run do
|
49
|
+
# Create a model, then sweep. It should have called perform wihtout triggering a sidekiq worker
|
50
|
+
city = City.create name: 'foo'
|
51
|
+
expect_any_instance_of(SidekiqWatcher).to receive(:perform).with(Tantot::Changes::ByModel.new({City => {city.id => {"name" => [nil, 'foo']}}}))
|
52
|
+
Tantot.collector.sweep(performer: :inline, watcher: SidekiqWatcher)
|
53
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.size).to eq(0)
|
54
|
+
|
55
|
+
# Further modifications should trigger through sidekiq when exiting the strategy block
|
56
|
+
city.name = 'bar'
|
57
|
+
city.save
|
58
|
+
end
|
59
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.size).to eq(1)
|
60
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.first["args"]).to eq([{"watcher" => "SidekiqWatcher", "collector_class" => "Tantot::Collector::Watcher"}, {"City" => {"1" => {"name" => ['foo', 'bar']}}}])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe Tantot::Collector::Block do
|
66
|
+
let(:value) { {changed: false} }
|
67
|
+
let(:changes) { {obj: nil} }
|
68
|
+
|
69
|
+
before do
|
70
|
+
Sidekiq::Worker.clear_all
|
71
|
+
v = value
|
72
|
+
c = changes
|
73
|
+
stub_model(:city) do
|
74
|
+
watch(:name) {|changes| v[:changed] = true; c[:obj] = changes}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should call a sidekiq worker" do
|
79
|
+
Tantot.collector.run do
|
80
|
+
City.create name: 'foo'
|
81
|
+
end
|
82
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.size).to eq(1)
|
83
|
+
block_id = Tantot.registry.watch_config.keys.last
|
84
|
+
expect(Tantot::Performer::Sidekiq::Worker.jobs.first["args"]).to eq([{"block_id" => block_id, "collector_class" => "Tantot::Collector::Block"}, {"1" => {"name" => [nil, 'foo']}}])
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should call the watcher" do
|
88
|
+
::Sidekiq::Testing.inline! do
|
89
|
+
city = nil
|
90
|
+
Tantot.collector.run do
|
91
|
+
city = City.create name: 'foo'
|
92
|
+
end
|
93
|
+
expect(value[:changed]).to be_truthy
|
94
|
+
expect(changes[:obj]).to eq(Tantot::Changes::ById.new({city.id => {"name" => [nil, 'foo']}}))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
|
3
|
+
Bundler.require
|
4
|
+
|
5
|
+
require 'active_record'
|
6
|
+
require 'database_cleaner'
|
7
|
+
|
8
|
+
Tantot.logger = Logger.new(STDOUT)
|
9
|
+
|
10
|
+
def stub_class(name, superclass = nil, &block)
|
11
|
+
stub_const(name.to_s.camelize, Class.new(superclass || Object, &block))
|
12
|
+
end
|
13
|
+
|
14
|
+
def stub_model(name, superclass = nil, &block)
|
15
|
+
stub_class(name, superclass || ActiveRecord::Base, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
|
19
|
+
|
20
|
+
ActiveRecord::Schema.define do
|
21
|
+
create_table :countries do |t|
|
22
|
+
t.column :name, :string
|
23
|
+
t.column :country_code, :string
|
24
|
+
t.column :rating, :integer
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :cities do |t|
|
28
|
+
t.column :country_id, :integer
|
29
|
+
t.column :name, :string
|
30
|
+
t.column :rating, :integer
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec.configure do |config|
|
35
|
+
config.before(:suite) do
|
36
|
+
DatabaseCleaner.clean_with :transaction
|
37
|
+
DatabaseCleaner.strategy = :transaction
|
38
|
+
end
|
39
|
+
|
40
|
+
config.before do
|
41
|
+
DatabaseCleaner.start
|
42
|
+
end
|
43
|
+
|
44
|
+
config.after do
|
45
|
+
DatabaseCleaner.clean
|
46
|
+
end
|
47
|
+
end
|