tantot 0.1.8 → 0.1.9
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 +4 -4
- data/lib/tantot/agent/block.rb +7 -1
- data/lib/tantot/agent.rb +2 -2
- data/lib/tantot/errors.rb +3 -0
- data/lib/tantot/extensions/chewy.rb +74 -71
- data/lib/tantot/observe.rb +7 -2
- data/lib/tantot/strategy/bypass.rb +1 -1
- data/lib/tantot/strategy/chewy.rb +1 -2
- data/lib/tantot/version.rb +1 -1
- data/spec/collector/block_spec.rb +22 -3
- data/spec/extensions/chewy_spec.rb +9 -0
- data/spec/observe_spec.rb +59 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2858d3a360d204db3c1755fecc65a5e86b3fb8e
|
4
|
+
data.tar.gz: 54d6490ddd105d5ff50eebae905eb91ace85c4e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6db43766e1c9dd8e2a56ecd81386bc01a46b6c300fc91d823063fe5418df2e0d74910edc4053eb7e20216ea6316383e03d57ed35bbe0e6f9bffeee3d247bbbcf
|
7
|
+
data.tar.gz: 64a89f3516777115416ce38f48d92317f30390c3acfbb8f770d41bbf12e6cdafd07877fc75e9d65391865099f1308fad5f2fc17792e8c6119ec8967394714aff
|
data/lib/tantot/agent/block.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
+
require 'cityhash'
|
2
|
+
|
1
3
|
module Tantot
|
2
4
|
module Agent
|
3
5
|
class Block < Base
|
4
6
|
def self.identify(watch)
|
5
7
|
if watch.block.present?
|
6
|
-
"#{watch.model.to_s}|#{watch.options.inspect}"
|
8
|
+
CityHash.hash64("#{watch.model.to_s}|#{watch.attributes.inspect}|#{watch.options.inspect}")
|
7
9
|
else
|
8
10
|
nil
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
14
|
+
def setup_watch(watch)
|
15
|
+
raise Tantot::MultipleWatchesProhibited.new("Can't have multiple block watches per model with the same attributes and options: #{debug_block(watch.block)}") if @watches.any?
|
16
|
+
end
|
17
|
+
|
12
18
|
def perform(changes_by_model)
|
13
19
|
# Block agent always has only one watch
|
14
20
|
block = watches.first.block
|
data/lib/tantot/agent.rb
CHANGED
@@ -10,8 +10,8 @@ module Tantot
|
|
10
10
|
|
11
11
|
def self.resolve!(watch)
|
12
12
|
agent_classes = AGENT_CLASSES.collect {|klass| [klass, klass.identify(watch)]}.reject {|_klass, id| id.nil?}
|
13
|
-
raise UnresolvableAgent("Can't resolve agent for watch: #{watch.inspect}") unless agent_classes.any?
|
14
|
-
raise UnresolvableAgent("More than one agent manages watch: #{watch.inspect}") if agent_classes.many?
|
13
|
+
raise Tantot::UnresolvableAgent.new("Can't resolve agent for watch: #{watch.inspect}. Specify either a watcher class or define a block.") unless agent_classes.any?
|
14
|
+
raise Tantot::UnresolvableAgent.new("More than one agent manages watch: #{watch.inspect}") if agent_classes.many?
|
15
15
|
agent_classes.first
|
16
16
|
end
|
17
17
|
|
data/lib/tantot/errors.rb
CHANGED
@@ -31,16 +31,16 @@ module Tantot
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
watch('tantot/extensions/chewy/chewy', *args, watch_options)
|
35
34
|
Tantot::Extensions::Chewy.register_watch(self, type_name, options, block)
|
35
|
+
watch('tantot/extensions/chewy/chewy', *args, watch_options)
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.register_watch(model, type_name, options, block)
|
40
40
|
method = options.delete(:method)
|
41
41
|
model._tantot_chewy_callbacks ||= {}
|
42
|
-
model._tantot_chewy_callbacks
|
43
|
-
model._tantot_chewy_callbacks[type_name]
|
42
|
+
raise MultipleWatchesProhibited.new("Cannot register an index watch on the same type more than once: #{type_name}") if model._tantot_chewy_callbacks.key?(type_name)
|
43
|
+
model._tantot_chewy_callbacks[type_name] = [method, options, block]
|
44
44
|
end
|
45
45
|
|
46
46
|
class ChewyWatcher
|
@@ -51,84 +51,87 @@ module Tantot
|
|
51
51
|
def perform(changes_by_model)
|
52
52
|
changes_by_model.each do |model, changes_by_id|
|
53
53
|
model_watches = model._tantot_chewy_callbacks
|
54
|
-
model_watches.each do |type_name,
|
54
|
+
model_watches.each do |type_name, (method, options, block)|
|
55
55
|
# Find type
|
56
|
-
reference =
|
57
|
-
|
58
|
-
if type_name.arity.zero?
|
59
|
-
instance_exec(&type_name)
|
60
|
-
else
|
61
|
-
type_name.call(self)
|
62
|
-
end
|
63
|
-
else
|
64
|
-
type_name
|
65
|
-
end
|
56
|
+
reference = get_chewy_type(type_name)
|
57
|
+
backreference = get_ids_to_update(model, changes_by_id, method, options, block)
|
66
58
|
|
67
|
-
|
68
|
-
|
69
|
-
block = watch_args[:block]
|
70
|
-
options = watch_args[:options]
|
71
|
-
association = options[:association]
|
59
|
+
if backreference
|
60
|
+
backreference.compact!
|
72
61
|
|
73
|
-
#
|
74
|
-
backreference
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
case reflection.through_reflection.macro
|
85
|
-
when :belongs_to
|
86
|
-
reflection.through_reflection.klass.where(reflection.through_reflection.klass.primary_key => changes_by_id.for_attribute(reflection.through_reflection.foreign_key))
|
87
|
-
when :has_many, :has_one
|
88
|
-
reflection.through_reflection.klass.where(reflection.through_reflection.foreign_key => changes_by_id.ids)
|
89
|
-
end
|
90
|
-
case reflection.source_reflection.macro
|
91
|
-
when :belongs_to
|
92
|
-
through_query.pluck(reflection.source_reflection.foreign_key)
|
93
|
-
when :has_many
|
94
|
-
reflection.source_reflection.klass.where(reflection.source_reflection.foreign_key => (through_query.ids)).ids
|
95
|
-
end
|
96
|
-
else
|
97
|
-
reflection.klass.where(reflection.foreign_key => changes_by_id.ids).ids
|
98
|
-
end
|
99
|
-
end
|
100
|
-
else
|
101
|
-
if (method && method.to_sym == :self) || (!method && !block)
|
102
|
-
# Simply extract keys from changes
|
103
|
-
changes_by_id.keys
|
104
|
-
elsif method
|
105
|
-
# We need to call `method`.
|
106
|
-
# Try to find it on the class. If so, call it once with all changes.
|
107
|
-
# There is no API to call per-instance since objects can be already destroyed
|
108
|
-
# when using the sidekiq performer
|
109
|
-
model.send(method, changes_by_id)
|
110
|
-
elsif block
|
111
|
-
# Since we can be post-destruction of the model, we can't load models here
|
112
|
-
# Thus, the signature of the block callback is |changes| which are all
|
113
|
-
# the changes to all the models
|
114
|
-
model.instance_exec(changes_by_id, &block)
|
115
|
-
end
|
116
|
-
end
|
62
|
+
# Make sure there are any backreferences
|
63
|
+
if backreference.any?
|
64
|
+
Tantot.logger.debug { "[Tantot] [Chewy] [update_index] #{reference} (#{backreference.count} objects): #{backreference.inspect}" }
|
65
|
+
::Chewy.derive_type(reference).update_index(backreference, {})
|
66
|
+
end
|
67
|
+
else
|
68
|
+
# nothing to update
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
117
73
|
|
118
|
-
|
119
|
-
|
74
|
+
def get_chewy_type(type_name)
|
75
|
+
if type_name.is_a?(Proc)
|
76
|
+
if type_name.arity.zero?
|
77
|
+
instance_exec(&type_name)
|
78
|
+
else
|
79
|
+
type_name.call(self)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
type_name
|
83
|
+
end
|
84
|
+
end
|
120
85
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
86
|
+
def get_ids_to_update(model, changes_by_id, method, options, block)
|
87
|
+
if options.key?(:association)
|
88
|
+
resolve_association(model, options[:association], changes_by_id)
|
89
|
+
else
|
90
|
+
if (method && method.to_sym == :self) || (!method && !block)
|
91
|
+
# Simply extract keys from changes
|
92
|
+
changes_by_id.keys
|
93
|
+
elsif method
|
94
|
+
# We need to call `method`.
|
95
|
+
# Try to find it on the class. If so, call it once with all changes.
|
96
|
+
# There is no API to call per-instance since objects can be already destroyed
|
97
|
+
# when using the sidekiq performer
|
98
|
+
model.send(method, changes_by_id)
|
99
|
+
elsif block
|
100
|
+
# Since we can be post-destruction of the model, we can't load models here
|
101
|
+
# Thus, the signature of the block callback is |changes| which are all
|
102
|
+
# the changes to all the models
|
103
|
+
model.instance_exec(changes_by_id, &block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
127
107
|
|
108
|
+
def resolve_association(model, association, changes_by_id)
|
109
|
+
reflection = model.reflect_on_association(association)
|
110
|
+
reflection.check_validity!
|
111
|
+
case reflection.macro
|
112
|
+
when :belongs_to
|
113
|
+
changes_by_id.for_attribute(reflection.foreign_key)
|
114
|
+
when :has_one, :has_many
|
115
|
+
if reflection.options[:through]
|
116
|
+
through_query =
|
117
|
+
case reflection.through_reflection.macro
|
118
|
+
when :belongs_to
|
119
|
+
reflection.through_reflection.klass.where(reflection.through_reflection.klass.primary_key => changes_by_id.for_attribute(reflection.through_reflection.foreign_key))
|
120
|
+
when :has_many, :has_one
|
121
|
+
reflection.through_reflection.klass.where(reflection.through_reflection.foreign_key => changes_by_id.ids)
|
122
|
+
end
|
123
|
+
case reflection.source_reflection.macro
|
124
|
+
when :belongs_to
|
125
|
+
through_query.pluck(reflection.source_reflection.foreign_key)
|
126
|
+
when :has_many
|
127
|
+
reflection.source_reflection.klass.where(reflection.source_reflection.foreign_key => (through_query.ids)).ids
|
128
128
|
end
|
129
|
+
else
|
130
|
+
reflection.klass.where(reflection.foreign_key => changes_by_id.ids).ids
|
129
131
|
end
|
130
132
|
end
|
131
133
|
end
|
134
|
+
|
132
135
|
end
|
133
136
|
end
|
134
137
|
end
|
data/lib/tantot/observe.rb
CHANGED
@@ -57,7 +57,10 @@ module Tantot
|
|
57
57
|
# it if it is a string or class
|
58
58
|
watcher = args.first.is_a?(String) || args.first.is_a?(Class) ? args.shift : nil
|
59
59
|
|
60
|
-
|
60
|
+
raise ArgumentError.new("Only symbols are allowed as attribute filters") unless args.all? {|arg| arg.is_a?(Symbol)}
|
61
|
+
raise ArgumentError.new("Only one of arguments or :only option are valid attribute filters") if args.any? && options.key?(:only)
|
62
|
+
|
63
|
+
only_attributes = Array.wrap(options.fetch(:only, args)).collect(&:to_s)
|
61
64
|
always_attributes = Array.wrap(options.fetch(:always, [])).collect(&:to_s)
|
62
65
|
|
63
66
|
# Setup watch
|
@@ -72,7 +75,7 @@ module Tantot
|
|
72
75
|
watch.block = block
|
73
76
|
watch.watcher = watcher
|
74
77
|
|
75
|
-
Tantot.agent_registry.register(watch)
|
78
|
+
agent = Tantot.agent_registry.register(watch)
|
76
79
|
|
77
80
|
# Setup and register callbacks
|
78
81
|
callback_options = {}.tap do |opts|
|
@@ -86,6 +89,8 @@ module Tantot
|
|
86
89
|
after_save(callback_options, &update_proc)
|
87
90
|
after_destroy(callback_options, &update_proc)
|
88
91
|
end
|
92
|
+
|
93
|
+
agent
|
89
94
|
end
|
90
95
|
end
|
91
96
|
end
|
@@ -9,8 +9,7 @@ module Tantot
|
|
9
9
|
raise AgentNotFound.new("No registered agent with id #{id}") unless agent
|
10
10
|
|
11
11
|
::Chewy.strategy(chewy_strategy) do
|
12
|
-
agent.
|
13
|
-
Tantot.manager.perform(context, changes)
|
12
|
+
agent.perform(Tantot::Strategy::Sidekiq.unmarshal(changes_by_model))
|
14
13
|
end
|
15
14
|
end
|
16
15
|
end
|
data/lib/tantot/version.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe Tantot::Agent::Block do
|
4
|
-
|
5
|
-
context 'using a block' do
|
4
|
+
context "normal usage" do
|
6
5
|
let(:value) { {changes: 0} }
|
7
6
|
let(:changes) { {obj: nil} }
|
8
7
|
before do
|
@@ -22,7 +21,7 @@ describe Tantot::Agent::Block do
|
|
22
21
|
expect(changes[:obj]).to eq(Tantot::Changes::ById.new({city.id => {"id" => [nil, 1]}}))
|
23
22
|
end
|
24
23
|
|
25
|
-
it "call a single time if multiple changes occur" do
|
24
|
+
it "should call a single time if multiple changes occur" do
|
26
25
|
Tantot.manager.run do
|
27
26
|
3.times { City.create! }
|
28
27
|
end
|
@@ -30,4 +29,24 @@ describe Tantot::Agent::Block do
|
|
30
29
|
expect(changes[:obj]).to eq(Tantot::Changes::ById.new({1=>{"id"=>[nil, 1]}, 2=>{"id"=>[nil, 2]}, 3=>{"id"=>[nil, 3]}}))
|
31
30
|
end
|
32
31
|
end
|
32
|
+
|
33
|
+
context "validations" do
|
34
|
+
it "should prevent registering twice with the same options" do
|
35
|
+
expect do
|
36
|
+
stub_model(:city) do
|
37
|
+
watch {}
|
38
|
+
watch {}
|
39
|
+
end
|
40
|
+
end.to raise_error(Tantot::MultipleWatchesProhibited, /Can't have multiple/)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should allow registering twice with different options" do
|
44
|
+
expect do
|
45
|
+
stub_model(:city) do
|
46
|
+
watch {}
|
47
|
+
watch(:name) {}
|
48
|
+
end
|
49
|
+
end.not_to raise_error
|
50
|
+
end
|
51
|
+
end
|
33
52
|
end
|
@@ -4,6 +4,15 @@ if defined?(::Chewy)
|
|
4
4
|
|
5
5
|
describe Tantot::Extensions::Chewy do
|
6
6
|
|
7
|
+
context "validations" do
|
8
|
+
it "should prevent registering on the same type more than once" do
|
9
|
+
stub_model(:city) do
|
10
|
+
watch_index 'foo'
|
11
|
+
end
|
12
|
+
expect { City.watch_index 'foo' }.to raise_error(Tantot::MultipleWatchesProhibited, /same type more than once/)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
[nil, :self, :class_method, :block].product([:some, :all]).each do |backreference_opt, attribute_opt|
|
8
17
|
context "should update indexes using backreference: #{backreference_opt.inspect}, attributes: #{attribute_opt}" do
|
9
18
|
let(:chewy_type) { double }
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Tantot::Observe::ActiveRecordMethods do
|
4
|
+
|
5
|
+
describe ".watch" do
|
6
|
+
before do
|
7
|
+
stub_class(:some_watcher) do
|
8
|
+
include Tantot::Watcher
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should validate that at least a watcher or a block is defined" do
|
13
|
+
expect do
|
14
|
+
stub_model(:no_block_no_watcher_model) do
|
15
|
+
watch
|
16
|
+
end
|
17
|
+
end.to raise_error(Tantot::UnresolvableAgent, /Can't resolve/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should validate that no more than one agent can be specified" do
|
21
|
+
expect do
|
22
|
+
stub_model(:block_and_watcher_model) do
|
23
|
+
watch(SomeWatcher) {}
|
24
|
+
end
|
25
|
+
end.to raise_error(Tantot::UnresolvableAgent, /More than one/)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow registering a simple block watch" do
|
29
|
+
stub_model(:block_model) do
|
30
|
+
watch {}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should allow registering a simple watcher watch" do
|
35
|
+
stub_model(:watcher_model) do
|
36
|
+
watch SomeWatcher
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should treat additional arguments as the :only option" do
|
41
|
+
stub_model(:block_model)
|
42
|
+
|
43
|
+
agent = BlockModel.watch(:foo, :bar) {}
|
44
|
+
|
45
|
+
expect(agent.watches.first.attributes[:only]).to eq(['foo', 'bar'])
|
46
|
+
end
|
47
|
+
|
48
|
+
it "prevent bad arguments" do
|
49
|
+
stub_model(:block_model)
|
50
|
+
|
51
|
+
expect { BlockModel.watch(SomeWatcher, SomeWatcher, :foo, :bar) }.to raise_error(ArgumentError, /symbol/)
|
52
|
+
expect { BlockModel.watch(:foo, "string", :bar) {} }.to raise_error(ArgumentError, /symbol/)
|
53
|
+
expect { BlockModel.watch(:foo, SomeWatcher, :bar) {} }.to raise_error(ArgumentError, /symbol/)
|
54
|
+
expect { BlockModel.watch(:foo, :bar, only: :baz) {} }.to raise_error(ArgumentError, /Only one of/)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tantot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- François-Pierre Bouchard
|
@@ -183,6 +183,7 @@ files:
|
|
183
183
|
- spec/collector/options_spec.rb
|
184
184
|
- spec/collector/watcher_spec.rb
|
185
185
|
- spec/extensions/chewy_spec.rb
|
186
|
+
- spec/observe_spec.rb
|
186
187
|
- spec/sidekiq_spec.rb
|
187
188
|
- spec/spec_helper.rb
|
188
189
|
- spec/tantot_spec.rb
|
@@ -217,6 +218,7 @@ test_files:
|
|
217
218
|
- spec/collector/options_spec.rb
|
218
219
|
- spec/collector/watcher_spec.rb
|
219
220
|
- spec/extensions/chewy_spec.rb
|
221
|
+
- spec/observe_spec.rb
|
220
222
|
- spec/sidekiq_spec.rb
|
221
223
|
- spec/spec_helper.rb
|
222
224
|
- spec/tantot_spec.rb
|