table_sync 5.1.0 → 6.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 +4 -4
- data/CHANGELOG.md +42 -0
- data/Gemfile.lock +3 -3
- data/README.md +3 -0
- 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 -105
- 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 +10 -6
- 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/version.rb +1 -1
- data/lib/table_sync.rb +31 -8
- metadata +28 -7
- data/lib/table_sync/publishing/base_publisher.rb +0 -110
- 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/publishing.md
CHANGED
@@ -1,133 +1,63 @@
|
|
1
|
-
# Publishing
|
1
|
+
# Publishing
|
2
2
|
|
3
|
-
|
3
|
+
TableSync can be used to send data using RabbitMQ.
|
4
4
|
|
5
|
-
|
5
|
+
You can do in two ways. Automatic and manual.
|
6
|
+
Each one has its own pros and cons.
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
```ruby
|
10
|
-
class SomeModel < Sequel::Model
|
11
|
-
TableSync.sync(self, { if: -> (*) { some_code } })
|
12
|
-
end
|
13
|
-
```
|
14
|
-
|
15
|
-
#### #attributes_for_sync
|
16
|
-
|
17
|
-
Models can implement `#attributes_for_sync` to override which attributes are published. If not present, all attributes are published
|
8
|
+
Automatic is used to publish changes in realtime, as soon as the tracked entity changes.
|
9
|
+
Usually syncs one entity at a time.
|
18
10
|
|
19
|
-
|
11
|
+
Manual allows to sync a lot of entities per message.
|
12
|
+
But demands greater amount of work and data preparation.
|
20
13
|
|
21
|
-
|
14
|
+
## Automatic
|
22
15
|
|
23
|
-
|
16
|
+
Include `TableSync.sync(self)` into a Sequel or ActiveRecord model.
|
24
17
|
|
25
|
-
|
18
|
+
Options:
|
26
19
|
|
27
|
-
|
20
|
+
- `if:` and `unless:` - Runs given proc in the scope of an instance. Skips sync on `false` for `if:` and on `true` for `unless:`.
|
21
|
+
- `on:` - specify events (`create`, `update`, `destroy`) to trigger sync on. Triggered for all of them without this option.
|
22
|
+
- `debounce_time` - min time period allowed between synchronizations.
|
28
23
|
|
29
|
-
|
24
|
+
Functioning `Rails.cache` is required.
|
30
25
|
|
31
|
-
|
26
|
+
How it works:
|
32
27
|
|
33
|
-
|
28
|
+
- `TableSync.sync(self)` - registers new callbacks (for `create`, `update`, `destroy`) for ActiveRecord model, and defines `after_create`, `after_update` and `after_destroy` callback methods for Sequel model.
|
34
29
|
|
35
|
-
|
36
|
-
|
37
|
-
- `TableSync.publishing_job_class_callable` is a callable which should resolve to a ActiveJob subclass that calls TableSync back to actually publish changes (required)
|
30
|
+
- Callbacks call `TableSync::Publishing::Single#publish_later` with given options and object attributes. It enqueues a job which then publishes a message.
|
38
31
|
|
39
32
|
Example:
|
40
33
|
|
41
34
|
```ruby
|
42
|
-
class
|
43
|
-
|
44
|
-
TableSync::Publishing::Publisher.new(*args).publish_now
|
45
|
-
end
|
35
|
+
class SomeModel < Sequel::Model
|
36
|
+
TableSync.sync(self, { if: -> (*) { some_code }, unless: -> (*) { some_code }, on: [:create, :update] })
|
46
37
|
end
|
47
|
-
```
|
48
|
-
|
49
|
-
- `TableSync.batch_publishing_job_class_callable` is a callable which should resolve to a ActiveJob subclass that calls TableSync batch publisher back to actually publish changes (required for batch publisher)
|
50
|
-
|
51
|
-
- `TableSync.routing_key_callable` is a callable which resolves which routing key to use when publishing changes. It receives object class and published attributes (required)
|
52
|
-
|
53
|
-
Example:
|
54
|
-
|
55
|
-
```ruby
|
56
|
-
TableSync.routing_key_callable = -> (klass, attributes) { klass.gsub('::', '_').tableize }
|
57
|
-
```
|
58
|
-
|
59
|
-
- `TableSync.routing_metadata_callable` is a callable that adds RabbitMQ headers which can be used in routing (optional). It receives object class and published attributes. One possible way of using it is defining a headers exchange and routing rules based on key-value pairs (which correspond to sent headers)
|
60
38
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
TableSync.routing_metadata_callable = -> (klass, attributes) { attributes.slice("project_id") }
|
39
|
+
class SomeOtherModel < Sequel::Model
|
40
|
+
TableSync.sync(self)
|
41
|
+
end
|
65
42
|
```
|
66
43
|
|
67
|
-
|
68
|
-
|
69
|
-
- `TableSync.notifier` is a module that provides publish and recieve notifications.
|
70
|
-
|
71
|
-
# Manual publishing
|
72
|
-
|
73
|
-
`TableSync::Publishing::Publisher.new(object_class, original_attributes, confirm: true, state: :updated, debounce_time: 45)`
|
74
|
-
where state is one of `:created / :updated / :destroyed` and `confirm` is Rabbit's confirm delivery flag and optional param `debounce_time` determines debounce time in seconds, 1 minute by default.
|
75
|
-
|
76
|
-
# Manual publishing with batches
|
77
|
-
|
78
|
-
You can use `TableSync::Publishing::BatchPublisher` to publish changes in batches (array of hashes in `attributes`).
|
79
|
-
|
80
|
-
When using `TableSync::Publishing::BatchPublisher`,` TableSync.routing_key_callable` is called as follows: `TableSync.routing_key_callable.call(klass, {})`, i.e. empty hash is passed instead of attributes. And `TableSync.routing_metadata_callable` is not called at all: metadata is set to empty hash.
|
44
|
+
## Manual
|
81
45
|
|
82
|
-
|
83
|
-
|
84
|
-
`options` consists of:
|
85
|
-
- `confirm`, which is a flag for RabbitMQ, `true` by default
|
86
|
-
- `routing_key`, which is a custom key used (if given) to override one from `TableSync.routing_key_callable`, `nil` by default
|
87
|
-
- `push_original_attributes` (default value is `false`), if this option is set to `true`,
|
88
|
-
original_attributes_array will be pushed to Rabbit instead of fetching records from database and sending their mapped attributes.
|
89
|
-
- `headers`, which is an option for custom headers (can be used for headers exchanges routes), `nil` by default
|
90
|
-
- `event`, which is an option for event specification (`:destroy` or `:update`), `:update` by default
|
46
|
+
Directly call one of the publishers. It's the best if you need to sync a lot of data.
|
47
|
+
This way you don't even need for the changes to occur.
|
91
48
|
|
92
49
|
Example:
|
93
50
|
|
94
51
|
```ruby
|
95
|
-
TableSync::Publishing::
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
push_original_attributes: true,
|
101
|
-
headers: { key: :value },
|
102
|
-
event: :destroy,
|
103
|
-
)
|
52
|
+
TableSync::Publishing::Batch.new(
|
53
|
+
object_class: "User",
|
54
|
+
original_attributes: [{ id: 1 }, { id: 2 }],
|
55
|
+
event: :update,
|
56
|
+
).publish_now
|
104
57
|
```
|
105
58
|
|
106
|
-
|
107
|
-
|
108
|
-
С помощью класса `TableSync::Publishing::BatchPublisher` вы можете опубликовать изменения батчами (массивом в `attributes`).
|
109
|
-
|
110
|
-
При использовании `TableSync::Publishing::BatchPublisher`, `TableSync.routing_key_callable` вызывается следующим образом: `TableSync.routing_key_callable.call(klass, {})`, то есть вместо аттрибутов передается пустой хэш. А `TableSync.routing_metadata_callable` не вызывается вовсе: в метадате устанавливается пустой хэш.
|
111
|
-
|
112
|
-
`TableSync::Publishing::BatchPublisher.new(object_class, original_attributes_array, **options)`, где `original_attributes_array` - массив с аттрибутами публикуемых объектов и `options`- это хэш с дополнительными опциями.
|
113
|
-
|
114
|
-
`options` состоит из:
|
115
|
-
- `confirm`, флаг для RabbitMQ, по умолчанию - `true`
|
116
|
-
- `routing_key`, ключ, который (если указан) замещает ключ, получаемый из `TableSync.routing_key_callable`, по умолчанию - `nil`
|
117
|
-
- `push_original_attributes` (значение по умолчанию `false`), если для этой опции задано значение true, в Rabbit будут отправлены original_attributes_array, вместо получения значений записей из базы непосредственно перед отправкой.
|
118
|
-
- `headers`, опция для задания headers (можно использовать для задания маршрутов в headers exchange'ах), `nil` по умолчанию
|
119
|
-
- `event`, опция для указания типа события (`:destroy` или `:update`), `:update` по умолчанию
|
59
|
+
## Read More
|
120
60
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
TableSync::Publishing::BatchPublisher.new(
|
125
|
-
"SomeClass",
|
126
|
-
[{ id: 1 }, { id: 2 }],
|
127
|
-
confirm: false,
|
128
|
-
routing_key: "custom_routing_key",
|
129
|
-
push_original_attributes: true,
|
130
|
-
headers: { key: :value },
|
131
|
-
event: :destroy,
|
132
|
-
)
|
133
|
-
```
|
61
|
+
- [Publishers](publishing/publishers.md)
|
62
|
+
- [Configuration](publishing/configuration.md)
|
63
|
+
- [Manual Sync (examples)](publishing/manual.md)
|
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
|