table_sync 4.2.1 → 6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +52 -0
- data/CHANGELOG.md +75 -9
- data/Gemfile.lock +159 -152
- data/README.md +4 -1
- data/docs/message_protocol.md +3 -3
- data/docs/publishing/configuration.md +143 -0
- data/docs/publishing/manual.md +155 -0
- data/docs/publishing/publishers.md +162 -0
- data/docs/publishing.md +35 -119
- data/docs/receiving.md +11 -6
- data/lib/table_sync/errors.rb +31 -22
- data/lib/table_sync/event.rb +35 -0
- data/lib/table_sync/orm_adapter/active_record.rb +25 -0
- data/lib/table_sync/orm_adapter/base.rb +92 -0
- data/lib/table_sync/orm_adapter/sequel.rb +29 -0
- data/lib/table_sync/publishing/batch.rb +53 -0
- data/lib/table_sync/publishing/data/objects.rb +50 -0
- data/lib/table_sync/publishing/data/raw.rb +27 -0
- data/lib/table_sync/publishing/helpers/attributes.rb +63 -0
- data/lib/table_sync/publishing/helpers/debounce.rb +134 -0
- data/lib/table_sync/publishing/helpers/objects.rb +39 -0
- data/lib/table_sync/publishing/message/base.rb +73 -0
- data/lib/table_sync/publishing/message/batch.rb +14 -0
- data/lib/table_sync/publishing/message/raw.rb +54 -0
- data/lib/table_sync/publishing/message/single.rb +13 -0
- data/lib/table_sync/publishing/params/base.rb +66 -0
- data/lib/table_sync/publishing/params/batch.rb +23 -0
- data/lib/table_sync/publishing/params/raw.rb +7 -0
- data/lib/table_sync/publishing/params/single.rb +31 -0
- data/lib/table_sync/publishing/raw.rb +21 -0
- data/lib/table_sync/publishing/single.rb +65 -0
- data/lib/table_sync/publishing.rb +20 -5
- data/lib/table_sync/receiving/config.rb +6 -4
- data/lib/table_sync/receiving/handler.rb +25 -9
- data/lib/table_sync/receiving/model/active_record.rb +1 -1
- data/lib/table_sync/receiving.rb +0 -2
- data/lib/table_sync/setup/active_record.rb +22 -0
- data/lib/table_sync/setup/base.rb +67 -0
- data/lib/table_sync/setup/sequel.rb +26 -0
- data/lib/table_sync/utils/interface_checker.rb +2 -2
- data/lib/table_sync/version.rb +1 -1
- data/lib/table_sync.rb +31 -8
- data/table_sync.gemspec +7 -7
- metadata +58 -37
- data/.travis.yml +0 -34
- data/lib/table_sync/publishing/base_publisher.rb +0 -114
- data/lib/table_sync/publishing/batch_publisher.rb +0 -109
- data/lib/table_sync/publishing/orm_adapter/active_record.rb +0 -32
- data/lib/table_sync/publishing/orm_adapter/sequel.rb +0 -57
- data/lib/table_sync/publishing/publisher.rb +0 -129
data/docs/receiving.md
CHANGED
@@ -68,7 +68,7 @@ The method receives following arguments
|
|
68
68
|
- `events` - array of supported events (optional)
|
69
69
|
- `block` - configuration block with options (optional)
|
70
70
|
|
71
|
-
This method implements logic of mapping `source` to `to_table` (or to `to_model`) and allows customizing
|
71
|
+
This method implements logic of mapping `source` to `to_table` (or to `to_model`) and allows customizing
|
72
72
|
the event handling logic with provided block.
|
73
73
|
You can use one `source` for a lot of `to_table` or `to_moel`.
|
74
74
|
|
@@ -235,12 +235,13 @@ default value is `false`
|
|
235
235
|
Proc that is used to wrap the receiving logic by custom block of code.
|
236
236
|
|
237
237
|
```ruby
|
238
|
-
wrap_receiving do |data:, target_keys:, version_key:, default_values: {}, &receiving_logic|
|
238
|
+
wrap_receiving do |data:, target_keys:, version_key:, default_values: {}, event:, &receiving_logic|
|
239
239
|
receiving_logic.call
|
240
240
|
return makes no sense
|
241
241
|
end
|
242
242
|
```
|
243
243
|
|
244
|
+
event option is current fired event
|
244
245
|
default value is `proc { |&block| block.call }`
|
245
246
|
|
246
247
|
#### before_update
|
@@ -262,15 +263,17 @@ end
|
|
262
263
|
Perform code after updated data was committed.
|
263
264
|
|
264
265
|
```ruby
|
265
|
-
after_commit_on_update do |data:, target_keys:, version_key:, default_values:|
|
266
|
+
after_commit_on_update do |data:, target_keys:, version_key:, default_values:, results:|
|
266
267
|
return makes no sense
|
267
268
|
end
|
268
269
|
|
269
|
-
after_commit_on_update do |data:, target_keys:, version_key:, default_values:|
|
270
|
+
after_commit_on_update do |data:, target_keys:, version_key:, default_values:, results:|
|
270
271
|
return makes no sense
|
271
272
|
end
|
272
273
|
```
|
273
274
|
|
275
|
+
- `results` - returned value from `model.upsert`
|
276
|
+
|
274
277
|
Сan be defined several times. Execution order guaranteed.
|
275
278
|
|
276
279
|
#### before_destroy
|
@@ -292,15 +295,17 @@ end
|
|
292
295
|
Perform code after destroyed data was committed.
|
293
296
|
|
294
297
|
```ruby
|
295
|
-
after_commit_on_destroy do |data:, target_keys:, version_key:|
|
298
|
+
after_commit_on_destroy do |data:, target_keys:, version_key:, results:|
|
296
299
|
return makes no sense
|
297
300
|
end
|
298
301
|
|
299
|
-
after_commit_on_destroy do |data:, target_keys:, version_key:|
|
302
|
+
after_commit_on_destroy do |data:, target_keys:, version_key:, results:|
|
300
303
|
return makes no sense
|
301
304
|
end
|
302
305
|
```
|
303
306
|
|
307
|
+
- `results` - returned value from `model.destroy`
|
308
|
+
|
304
309
|
Сan be defined several times. Execution order guaranteed.
|
305
310
|
|
306
311
|
### Custom model
|
data/lib/table_sync/errors.rb
CHANGED
@@ -3,6 +3,35 @@
|
|
3
3
|
module TableSync
|
4
4
|
Error = Class.new(StandardError)
|
5
5
|
|
6
|
+
NoObjectsForSyncError = Class.new(Error)
|
7
|
+
|
8
|
+
class EventError < Error
|
9
|
+
def initialize(event)
|
10
|
+
super(<<~MSG.squish)
|
11
|
+
Event #{event.inspect} is invalid.#{' '}
|
12
|
+
Expected: #{TableSync::Event::VALID_RAW_EVENTS.inspect}.
|
13
|
+
MSG
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NoPrimaryKeyError < Error
|
18
|
+
def initialize(object_class, object_data, primary_key_columns)
|
19
|
+
super(<<~MSG.squish)
|
20
|
+
Can't find or init an object of #{object_class} with #{object_data.inspect}.
|
21
|
+
Incomplete primary key! object_data must contain: #{primary_key_columns.inspect}.
|
22
|
+
MSG
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class NoCallableError < Error
|
27
|
+
def initialize(type)
|
28
|
+
super(<<~MSG.squish)
|
29
|
+
Can't find callable for #{type}!
|
30
|
+
Please initialize TableSync.#{type}_callable with the correct proc!
|
31
|
+
MSG
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
6
35
|
class UpsertError < Error
|
7
36
|
def initialize(data:, target_keys:, result:)
|
8
37
|
super("data: #{data.inspect}, target_keys: #{target_keys.inspect}, result: #{result.inspect}")
|
@@ -25,28 +54,6 @@ module TableSync
|
|
25
54
|
end
|
26
55
|
end
|
27
56
|
|
28
|
-
# @api public
|
29
|
-
# @since 2.2.0
|
30
|
-
PluginError = Class.new(Error)
|
31
|
-
|
32
|
-
# @api public
|
33
|
-
# @since 2.2.0
|
34
|
-
class UnregisteredPluginError < PluginError
|
35
|
-
# @param plugin_name [Any]
|
36
|
-
def initialize(plugin_name)
|
37
|
-
super("#{plugin_name} plugin is not registered")
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# @api public
|
42
|
-
# @since 2.2.0
|
43
|
-
class AlreadyRegisteredPluginError < PluginError
|
44
|
-
# @param plugin_name [Any]
|
45
|
-
def initialize(plugin_name)
|
46
|
-
super("#{plugin_name} plugin already exists")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
57
|
class InterfaceError < Error
|
51
58
|
def initialize(object, method_name, parameters, description)
|
52
59
|
parameters = parameters.map do |parameter|
|
@@ -54,7 +61,9 @@ module TableSync
|
|
54
61
|
|
55
62
|
case type
|
56
63
|
when :req
|
64
|
+
#:nocov:
|
57
65
|
name.to_s
|
66
|
+
#:nocov:
|
58
67
|
when :keyreq
|
59
68
|
"#{name}:"
|
60
69
|
when :block
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TableSync::Event
|
4
|
+
attr_reader :event
|
5
|
+
|
6
|
+
UPSERT_EVENTS = %i[create update].freeze
|
7
|
+
VALID_RESOLVED_EVENTS = %i[update destroy].freeze
|
8
|
+
VALID_RAW_EVENTS = %i[create update destroy].freeze
|
9
|
+
|
10
|
+
def initialize(event)
|
11
|
+
@event = event
|
12
|
+
|
13
|
+
validate!
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate!
|
17
|
+
raise TableSync::EventError.new(event) unless event.in?(VALID_RAW_EVENTS)
|
18
|
+
end
|
19
|
+
|
20
|
+
def resolve
|
21
|
+
destroy? ? :destroy : :update
|
22
|
+
end
|
23
|
+
|
24
|
+
def metadata
|
25
|
+
{ created: event == :create }
|
26
|
+
end
|
27
|
+
|
28
|
+
def destroy?
|
29
|
+
event == :destroy
|
30
|
+
end
|
31
|
+
|
32
|
+
def upsert?
|
33
|
+
event.in?(UPSERT_EVENTS)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::ORMAdapter
|
4
|
+
class ActiveRecord < Base
|
5
|
+
def find
|
6
|
+
@object = object_class.find_by(needle)
|
7
|
+
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def init
|
12
|
+
@object = object_class.new(object_data)
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def attributes
|
18
|
+
object.attributes.symbolize_keys
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.model_naming(object_class)
|
22
|
+
TableSync::NamingResolver::ActiveRecord.new(table_name: object_class.table_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::ORMAdapter
|
4
|
+
class Base
|
5
|
+
attr_reader :object, :object_class, :object_data
|
6
|
+
|
7
|
+
def initialize(object_class, object_data)
|
8
|
+
@object_class = object_class
|
9
|
+
@object_data = object_data.symbolize_keys
|
10
|
+
|
11
|
+
validate!
|
12
|
+
end
|
13
|
+
|
14
|
+
# VALIDATE
|
15
|
+
|
16
|
+
def validate!
|
17
|
+
if (primary_key_columns - object_data.keys).any?
|
18
|
+
raise TableSync::NoPrimaryKeyError.new(object_class, object_data, primary_key_columns)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# FIND OR INIT OBJECT
|
23
|
+
|
24
|
+
def init
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def find
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def needle
|
33
|
+
object_data.slice(*primary_key_columns)
|
34
|
+
end
|
35
|
+
|
36
|
+
# ATTRIBUTES
|
37
|
+
|
38
|
+
def attributes_for_update
|
39
|
+
if object.respond_to?(:attributes_for_sync)
|
40
|
+
object.attributes_for_sync
|
41
|
+
else
|
42
|
+
attributes
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes_for_destroy
|
47
|
+
if object.respond_to?(:attributes_for_destroy)
|
48
|
+
object.attributes_for_destroy
|
49
|
+
else
|
50
|
+
needle
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def attributes_for_routing_key
|
55
|
+
if object.respond_to?(:attributes_for_routing_key)
|
56
|
+
object.attributes_for_routing_key
|
57
|
+
else
|
58
|
+
attributes
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def attributes_for_headers
|
63
|
+
if object.respond_to?(:attributes_for_headers)
|
64
|
+
object.attributes_for_headers
|
65
|
+
else
|
66
|
+
attributes
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def primary_key_columns
|
71
|
+
Array.wrap(object_class.primary_key).map(&:to_sym)
|
72
|
+
end
|
73
|
+
|
74
|
+
# MISC
|
75
|
+
|
76
|
+
def empty?
|
77
|
+
object.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
# NOT IMPLEMENTED
|
81
|
+
|
82
|
+
# :nocov:
|
83
|
+
def attributes
|
84
|
+
raise NotImplementedError
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.model_naming
|
88
|
+
raise NotImplementedError
|
89
|
+
end
|
90
|
+
# :nocov:
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::ORMAdapter
|
4
|
+
class Sequel < Base
|
5
|
+
def attributes
|
6
|
+
object.values
|
7
|
+
end
|
8
|
+
|
9
|
+
def init
|
10
|
+
@object = object_class.new(object_data.except(*primary_key_columns))
|
11
|
+
|
12
|
+
@object.set_fields(needle, needle.keys)
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def find
|
18
|
+
@object = object_class.find(needle)
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.model_naming(object_class)
|
24
|
+
TableSync::NamingResolver::Sequel.new(
|
25
|
+
table_name: object_class.table_name, db: object_class.db,
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TableSync::Publishing::Batch
|
4
|
+
include Tainbox
|
5
|
+
|
6
|
+
attribute :object_class
|
7
|
+
attribute :original_attributes
|
8
|
+
|
9
|
+
attribute :routing_key
|
10
|
+
attribute :headers
|
11
|
+
|
12
|
+
attribute :event, default: :update
|
13
|
+
|
14
|
+
def publish_later
|
15
|
+
job.perform_later(job_attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
def publish_now
|
19
|
+
message.publish
|
20
|
+
end
|
21
|
+
|
22
|
+
def message
|
23
|
+
TableSync::Publishing::Message::Batch.new(attributes)
|
24
|
+
end
|
25
|
+
|
26
|
+
alias_method :publish_async, :publish_later
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# JOB
|
31
|
+
|
32
|
+
def job
|
33
|
+
if TableSync.batch_publishing_job_class_callable
|
34
|
+
TableSync.batch_publishing_job_class_callable.call
|
35
|
+
else
|
36
|
+
raise TableSync::NoCallableError.new("batch_publishing_job_class")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def job_attributes
|
41
|
+
attributes.merge(
|
42
|
+
original_attributes: serialized_original_attributes,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def serialized_original_attributes
|
47
|
+
original_attributes.map do |set_of_attributes|
|
48
|
+
TableSync::Publishing::Helpers::Attributes
|
49
|
+
.new(set_of_attributes)
|
50
|
+
.serialize
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::Publishing::Data
|
4
|
+
class Objects
|
5
|
+
attr_reader :objects, :event
|
6
|
+
|
7
|
+
def initialize(objects:, event:)
|
8
|
+
@objects = objects
|
9
|
+
@event = TableSync::Event.new(event)
|
10
|
+
end
|
11
|
+
|
12
|
+
def construct
|
13
|
+
{
|
14
|
+
model: model,
|
15
|
+
attributes: attributes_for_sync,
|
16
|
+
version: version,
|
17
|
+
event: event.resolve,
|
18
|
+
metadata: event.metadata,
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def model
|
25
|
+
if object_class.respond_to?(:table_sync_model_name)
|
26
|
+
object_class.table_sync_model_name
|
27
|
+
else
|
28
|
+
object_class.name
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def version
|
33
|
+
Time.current.to_f
|
34
|
+
end
|
35
|
+
|
36
|
+
def object_class
|
37
|
+
objects.first.object_class
|
38
|
+
end
|
39
|
+
|
40
|
+
def attributes_for_sync
|
41
|
+
objects.map do |object|
|
42
|
+
if event.destroy?
|
43
|
+
object.attributes_for_destroy
|
44
|
+
else
|
45
|
+
object.attributes_for_update
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::Publishing::Data
|
4
|
+
class Raw
|
5
|
+
attr_reader :object_class, :attributes_for_sync, :event
|
6
|
+
|
7
|
+
def initialize(object_class:, attributes_for_sync:, event:)
|
8
|
+
@object_class = object_class
|
9
|
+
@attributes_for_sync = attributes_for_sync
|
10
|
+
@event = TableSync::Event.new(event)
|
11
|
+
end
|
12
|
+
|
13
|
+
def construct
|
14
|
+
{
|
15
|
+
model: object_class,
|
16
|
+
attributes: attributes_for_sync,
|
17
|
+
version: version,
|
18
|
+
event: event.resolve,
|
19
|
+
metadata: event.metadata,
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def version
|
24
|
+
Time.current.to_f
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::Publishing::Helpers
|
4
|
+
class Attributes
|
5
|
+
BASE_SAFE_JSON_TYPES = [
|
6
|
+
NilClass,
|
7
|
+
String,
|
8
|
+
TrueClass,
|
9
|
+
FalseClass,
|
10
|
+
Numeric,
|
11
|
+
Symbol,
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# add custom seializables?
|
15
|
+
|
16
|
+
NOT_MAPPED = Object.new
|
17
|
+
|
18
|
+
attr_reader :attributes
|
19
|
+
|
20
|
+
def initialize(attributes)
|
21
|
+
@attributes = attributes.deep_symbolize_keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize
|
25
|
+
filter_safe_for_serialization(attributes)
|
26
|
+
end
|
27
|
+
|
28
|
+
def filter_safe_for_serialization(object)
|
29
|
+
case object
|
30
|
+
when Array
|
31
|
+
object.each_with_object([]) do |value, memo|
|
32
|
+
value = filter_safe_for_serialization(value)
|
33
|
+
memo << value if object_mapped?(value)
|
34
|
+
end
|
35
|
+
when Hash
|
36
|
+
object.each_with_object({}) do |(key, value), memo|
|
37
|
+
key = filter_safe_for_serialization(key)
|
38
|
+
value = filter_safe_hash_values(value)
|
39
|
+
memo[key] = value if object_mapped?(key) && object_mapped?(value)
|
40
|
+
end
|
41
|
+
when Float::INFINITY
|
42
|
+
NOT_MAPPED
|
43
|
+
when *BASE_SAFE_JSON_TYPES
|
44
|
+
object
|
45
|
+
else # rubocop:disable Lint/DuplicateBranch
|
46
|
+
NOT_MAPPED
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def filter_safe_hash_values(value)
|
51
|
+
case value
|
52
|
+
when Symbol
|
53
|
+
value.to_s # why?
|
54
|
+
else
|
55
|
+
filter_safe_for_serialization(value)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def object_mapped?(object)
|
60
|
+
object != NOT_MAPPED
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# CASES FOR DEBOUNCE
|
4
|
+
|
5
|
+
# Cached Sync Time -> CST - time last sync has occured or next sync will occur
|
6
|
+
# Next Sync Time -> NST - time next sync will occur
|
7
|
+
|
8
|
+
# 0
|
9
|
+
# Condition: debounce_time is zero.
|
10
|
+
# No debounce, sync right now.
|
11
|
+
# Result: NST -> Time.current
|
12
|
+
|
13
|
+
# 1
|
14
|
+
# Condition: CST is empty.
|
15
|
+
# There was no sync before. Can be synced right now.
|
16
|
+
# Result: NST -> Time.current
|
17
|
+
|
18
|
+
# 2
|
19
|
+
# Condition: CST =< Time.current.
|
20
|
+
# There was a sync before.
|
21
|
+
|
22
|
+
# 2.1
|
23
|
+
# Subcondition: CST + debounce_time <= Time.current
|
24
|
+
# Enough time passed for debounce condition to be satisfied.
|
25
|
+
# No need to wait. Can by synced right now.
|
26
|
+
# Result: NST -> Time.current
|
27
|
+
|
28
|
+
# 2.2
|
29
|
+
# Subcondition: CST + debounce_time > Time.current
|
30
|
+
# Debounce condition is not satisfied. Must wait till debounce period has passed.
|
31
|
+
# Will be synced after debounce period has passed.
|
32
|
+
# Result: NST -> CST + debounce_time
|
33
|
+
|
34
|
+
# 3
|
35
|
+
# Condition: CST > Time.current
|
36
|
+
# Sync job has already been enqueued in the future.
|
37
|
+
|
38
|
+
# 3.1
|
39
|
+
# Subcondition: event -> update | create
|
40
|
+
# No need to sync upsert event, since it has already enqueued sync in the future.
|
41
|
+
# It will sync fresh version anyway.
|
42
|
+
# NST -> Skip, no sync.
|
43
|
+
|
44
|
+
# 3.2
|
45
|
+
# Subcondition: event -> destroy
|
46
|
+
# In this case the already enqueued job must be upsert.
|
47
|
+
# Thus destructive sync has to send message after upsert sync.
|
48
|
+
# NST -> CST + debounce_time
|
49
|
+
|
50
|
+
module TableSync::Publishing::Helpers
|
51
|
+
class Debounce
|
52
|
+
include Memery
|
53
|
+
|
54
|
+
DEFAULT_TIME = 60
|
55
|
+
|
56
|
+
attr_reader :debounce_time, :object_class, :needle, :event
|
57
|
+
|
58
|
+
def initialize(object_class:, needle:, event:, debounce_time: nil)
|
59
|
+
@event = event
|
60
|
+
@debounce_time = debounce_time || DEFAULT_TIME
|
61
|
+
@object_class = object_class
|
62
|
+
@needle = needle
|
63
|
+
end
|
64
|
+
|
65
|
+
def skip?
|
66
|
+
true if sync_in_the_future? && upsert_event? # case 3.1
|
67
|
+
end
|
68
|
+
|
69
|
+
memoize def next_sync_time
|
70
|
+
return current_time if debounce_time.zero? # case 0
|
71
|
+
return current_time if no_sync_before? # case 1
|
72
|
+
|
73
|
+
return current_time if sync_in_the_past? && debounce_time_passed? # case 2.1
|
74
|
+
return debounced_sync_time if sync_in_the_past? && debounce_time_not_passed? # case 2.2
|
75
|
+
|
76
|
+
return debounced_sync_time if sync_in_the_future? && destroy_event? # case 3.2
|
77
|
+
end
|
78
|
+
|
79
|
+
# CASE 1
|
80
|
+
def no_sync_before?
|
81
|
+
cached_sync_time.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# CASE 2
|
85
|
+
def sync_in_the_past?
|
86
|
+
cached_sync_time <= current_time
|
87
|
+
end
|
88
|
+
|
89
|
+
def debounce_time_passed?
|
90
|
+
cached_sync_time + debounce_time.seconds >= current_time
|
91
|
+
end
|
92
|
+
|
93
|
+
def debounce_time_not_passed?
|
94
|
+
cached_sync_time + debounce_time.seconds < current_time
|
95
|
+
end
|
96
|
+
|
97
|
+
# CASE 3
|
98
|
+
def sync_in_the_future?
|
99
|
+
cached_sync_time && (cached_sync_time > current_time)
|
100
|
+
end
|
101
|
+
|
102
|
+
def destroy_event?
|
103
|
+
event == :destroy
|
104
|
+
end
|
105
|
+
|
106
|
+
def upsert_event?
|
107
|
+
!destroy_event?
|
108
|
+
end
|
109
|
+
|
110
|
+
# MISC
|
111
|
+
|
112
|
+
def debounced_sync_time
|
113
|
+
cached_sync_time + debounce_time.seconds
|
114
|
+
end
|
115
|
+
|
116
|
+
memoize def current_time
|
117
|
+
Time.current
|
118
|
+
end
|
119
|
+
|
120
|
+
# CACHE
|
121
|
+
|
122
|
+
memoize def cached_sync_time
|
123
|
+
Rails.cache.read(cache_key)
|
124
|
+
end
|
125
|
+
|
126
|
+
def cache_next_sync_time
|
127
|
+
Rails.cache.write(cache_key, next_sync_time)
|
128
|
+
end
|
129
|
+
|
130
|
+
def cache_key
|
131
|
+
"#{object_class}/#{needle.values.join}_table_sync_time".delete(" ")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::Publishing::Helpers
|
4
|
+
class Objects
|
5
|
+
attr_reader :object_class, :original_attributes, :event
|
6
|
+
|
7
|
+
def initialize(object_class:, original_attributes:, event:)
|
8
|
+
@event = TableSync::Event.new(event)
|
9
|
+
@object_class = object_class.constantize
|
10
|
+
@original_attributes = Array.wrap(original_attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def construct_list
|
14
|
+
if event.destroy?
|
15
|
+
init_objects
|
16
|
+
else
|
17
|
+
without_empty_objects(find_objects)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def without_empty_objects(objects)
|
24
|
+
objects.reject(&:empty?)
|
25
|
+
end
|
26
|
+
|
27
|
+
def init_objects
|
28
|
+
original_attributes.map do |attrs|
|
29
|
+
TableSync.publishing_adapter.new(object_class, attrs).init
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_objects
|
34
|
+
original_attributes.map do |attrs|
|
35
|
+
TableSync.publishing_adapter.new(object_class, attrs).find
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|