table_sync 2.0.0 → 4.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +26 -1
  4. data/.travis.yml +6 -6
  5. data/CHANGELOG.md +74 -0
  6. data/Gemfile.lock +262 -0
  7. data/LICENSE.md +1 -1
  8. data/README.md +4 -1
  9. data/docs/message_protocol.md +24 -0
  10. data/docs/notifications.md +45 -0
  11. data/docs/publishing.md +147 -0
  12. data/docs/receiving.md +341 -0
  13. data/lib/table_sync.rb +23 -33
  14. data/lib/table_sync/errors.rb +60 -22
  15. data/lib/table_sync/instrument.rb +2 -2
  16. data/lib/table_sync/publishing.rb +11 -0
  17. data/lib/table_sync/{base_publisher.rb → publishing/base_publisher.rb} +1 -1
  18. data/lib/table_sync/{batch_publisher.rb → publishing/batch_publisher.rb} +12 -13
  19. data/lib/table_sync/{orm_adapter → publishing/orm_adapter}/active_record.rb +4 -8
  20. data/lib/table_sync/{orm_adapter → publishing/orm_adapter}/sequel.rb +3 -7
  21. data/lib/table_sync/{publisher.rb → publishing/publisher.rb} +4 -4
  22. data/lib/table_sync/receiving.rb +14 -0
  23. data/lib/table_sync/receiving/config.rb +218 -0
  24. data/lib/table_sync/receiving/config_decorator.rb +27 -0
  25. data/lib/table_sync/receiving/dsl.rb +28 -0
  26. data/lib/table_sync/receiving/handler.rb +132 -0
  27. data/lib/table_sync/{model → receiving/model}/active_record.rb +44 -37
  28. data/lib/table_sync/receiving/model/sequel.rb +83 -0
  29. data/lib/table_sync/utils.rb +9 -0
  30. data/lib/table_sync/utils/interface_checker.rb +103 -0
  31. data/lib/table_sync/utils/proc_array.rb +17 -0
  32. data/lib/table_sync/utils/proc_keywords_resolver.rb +46 -0
  33. data/lib/table_sync/version.rb +1 -1
  34. data/table_sync.gemspec +5 -4
  35. metadata +51 -33
  36. data/docs/synopsis.md +0 -318
  37. data/lib/table_sync/config.rb +0 -105
  38. data/lib/table_sync/config/callback_registry.rb +0 -53
  39. data/lib/table_sync/config_decorator.rb +0 -38
  40. data/lib/table_sync/dsl.rb +0 -25
  41. data/lib/table_sync/event_actions.rb +0 -96
  42. data/lib/table_sync/event_actions/data_wrapper.rb +0 -7
  43. data/lib/table_sync/event_actions/data_wrapper/base.rb +0 -23
  44. data/lib/table_sync/event_actions/data_wrapper/destroy.rb +0 -19
  45. data/lib/table_sync/event_actions/data_wrapper/update.rb +0 -21
  46. data/lib/table_sync/model/sequel.rb +0 -88
  47. data/lib/table_sync/receiving_handler.rb +0 -76
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TableSync::Utils::ProcArray < Proc
4
+ def initialize(&block)
5
+ @array = []
6
+ super(&block)
7
+ end
8
+
9
+ def push(&block)
10
+ @array.push(block)
11
+ self
12
+ end
13
+
14
+ def call(*args, &block)
15
+ super(@array, args, &block)
16
+ end
17
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Problem:
4
+ #
5
+ # > fn = proc { |first| puts first }
6
+ # > fn.call(:first, :second, :third)
7
+ # first
8
+ #
9
+ # :second and :third was ignored. It's ok.
10
+ #
11
+ # > fn = proc { puts "test" }
12
+ # > fn.call(first: :first, second: :second, third: :third)
13
+ # test
14
+ #
15
+ # And it's ok.
16
+ #
17
+ # > fn = proc { |&block| block.call }
18
+ # > fn.call(first: :first, second: :second, third: :third) { puts "test" }
19
+ # test
20
+ #
21
+ # And this is ok too.
22
+ #
23
+ # > fn = proc { |first:| puts first }
24
+ # > fn.call(first: :first, second: :second, third: :third)
25
+ # ArgumentError (unknown keywords: :second, :third)
26
+ #
27
+ # ¯\_(ツ)_/¯
28
+ #
29
+ # ❤ Ruby ❤
30
+ #
31
+ # Next code solve this problem for procs without word arguments,
32
+ # only keywords and block.
33
+
34
+ module TableSync::Utils
35
+ module_function
36
+
37
+ def proc_keywords_resolver(&proc_for_wrap)
38
+ available_keywords = proc_for_wrap.parameters
39
+ .select { |type, _name| type == :keyreq }
40
+ .map { |_type, name| name }
41
+
42
+ proc do |keywords = {}, &block|
43
+ proc_for_wrap.call(**keywords.slice(*available_keywords), &block)
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TableSync
4
- VERSION = "2.0.0"
4
+ VERSION = "4.1.0"
5
5
  end
@@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "table_sync/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.required_ruby_version = ">= 2.3.8"
8
+ spec.required_ruby_version = ">= 2.5.6"
9
9
 
10
10
  spec.name = "table_sync"
11
11
  spec.version = TableSync::VERSION
@@ -29,14 +29,15 @@ Gem::Specification.new do |spec|
29
29
  spec.add_runtime_dependency "memery"
30
30
  spec.add_runtime_dependency "rabbit_messaging", "~> 0.3"
31
31
  spec.add_runtime_dependency "rails"
32
+ spec.add_runtime_dependency "self_data"
32
33
 
33
34
  spec.add_development_dependency "coveralls", "~> 0.8"
34
35
  spec.add_development_dependency "rspec", "~> 3.8"
35
- spec.add_development_dependency "rubocop-config-umbrellio", "~> 0.74"
36
+ spec.add_development_dependency "rubocop-config-umbrellio"
36
37
  spec.add_development_dependency "simplecov", "~> 0.16"
37
38
 
38
- spec.add_development_dependency "activejob", ">= 4.2.11"
39
- spec.add_development_dependency "activerecord"
39
+ spec.add_development_dependency "activejob", ">= 6.0"
40
+ spec.add_development_dependency "activerecord", ">= 6.0"
40
41
  spec.add_development_dependency "pg", "~> 0.18"
41
42
  spec.add_development_dependency "sequel"
42
43
  spec.add_development_dependency "timecop"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: table_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Umbrellio
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-06 00:00:00.000000000 Z
11
+ date: 2020-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: memery
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: self_data
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: coveralls
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -84,16 +98,16 @@ dependencies:
84
98
  name: rubocop-config-umbrellio
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
- - - "~>"
101
+ - - ">="
88
102
  - !ruby/object:Gem::Version
89
- version: '0.74'
103
+ version: '0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
- - - "~>"
108
+ - - ">="
95
109
  - !ruby/object:Gem::Version
96
- version: '0.74'
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: simplecov
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -114,28 +128,28 @@ dependencies:
114
128
  requirements:
115
129
  - - ">="
116
130
  - !ruby/object:Gem::Version
117
- version: 4.2.11
131
+ version: '6.0'
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
- version: 4.2.11
138
+ version: '6.0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: activerecord
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - ">="
130
144
  - !ruby/object:Gem::Version
131
- version: '0'
145
+ version: '6.0'
132
146
  type: :development
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
150
  - - ">="
137
151
  - !ruby/object:Gem::Version
138
- version: '0'
152
+ version: '6.0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: pg
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -248,35 +262,39 @@ files:
248
262
  - ".travis.yml"
249
263
  - CHANGELOG.md
250
264
  - Gemfile
265
+ - Gemfile.lock
251
266
  - LICENSE.md
252
267
  - README.md
253
268
  - Rakefile
254
269
  - bin/console
255
270
  - bin/setup
256
- - docs/synopsis.md
271
+ - docs/message_protocol.md
272
+ - docs/notifications.md
273
+ - docs/publishing.md
274
+ - docs/receiving.md
257
275
  - lib/table_sync.rb
258
- - lib/table_sync/base_publisher.rb
259
- - lib/table_sync/batch_publisher.rb
260
- - lib/table_sync/config.rb
261
- - lib/table_sync/config/callback_registry.rb
262
- - lib/table_sync/config_decorator.rb
263
- - lib/table_sync/dsl.rb
264
276
  - lib/table_sync/errors.rb
265
- - lib/table_sync/event_actions.rb
266
- - lib/table_sync/event_actions/data_wrapper.rb
267
- - lib/table_sync/event_actions/data_wrapper/base.rb
268
- - lib/table_sync/event_actions/data_wrapper/destroy.rb
269
- - lib/table_sync/event_actions/data_wrapper/update.rb
270
277
  - lib/table_sync/instrument.rb
271
278
  - lib/table_sync/instrument_adapter/active_support.rb
272
- - lib/table_sync/model/active_record.rb
273
- - lib/table_sync/model/sequel.rb
274
279
  - lib/table_sync/naming_resolver/active_record.rb
275
280
  - lib/table_sync/naming_resolver/sequel.rb
276
- - lib/table_sync/orm_adapter/active_record.rb
277
- - lib/table_sync/orm_adapter/sequel.rb
278
- - lib/table_sync/publisher.rb
279
- - lib/table_sync/receiving_handler.rb
281
+ - lib/table_sync/publishing.rb
282
+ - lib/table_sync/publishing/base_publisher.rb
283
+ - lib/table_sync/publishing/batch_publisher.rb
284
+ - lib/table_sync/publishing/orm_adapter/active_record.rb
285
+ - lib/table_sync/publishing/orm_adapter/sequel.rb
286
+ - lib/table_sync/publishing/publisher.rb
287
+ - lib/table_sync/receiving.rb
288
+ - lib/table_sync/receiving/config.rb
289
+ - lib/table_sync/receiving/config_decorator.rb
290
+ - lib/table_sync/receiving/dsl.rb
291
+ - lib/table_sync/receiving/handler.rb
292
+ - lib/table_sync/receiving/model/active_record.rb
293
+ - lib/table_sync/receiving/model/sequel.rb
294
+ - lib/table_sync/utils.rb
295
+ - lib/table_sync/utils/interface_checker.rb
296
+ - lib/table_sync/utils/proc_array.rb
297
+ - lib/table_sync/utils/proc_keywords_resolver.rb
280
298
  - lib/table_sync/version.rb
281
299
  - log/.keep
282
300
  - table_sync.gemspec
@@ -284,7 +302,7 @@ homepage: https://github.com/umbrellio/table_sync
284
302
  licenses:
285
303
  - MIT
286
304
  metadata: {}
287
- post_install_message:
305
+ post_install_message:
288
306
  rdoc_options: []
289
307
  require_paths:
290
308
  - lib
@@ -292,15 +310,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
292
310
  requirements:
293
311
  - - ">="
294
312
  - !ruby/object:Gem::Version
295
- version: 2.3.8
313
+ version: 2.5.6
296
314
  required_rubygems_version: !ruby/object:Gem::Requirement
297
315
  requirements:
298
316
  - - ">="
299
317
  - !ruby/object:Gem::Version
300
318
  version: '0'
301
319
  requirements: []
302
- rubygems_version: 3.1.2
303
- signing_key:
320
+ rubygems_version: 3.2.0.rc.1
321
+ signing_key:
304
322
  specification_version: 4
305
323
  summary: DB Table synchronization between microservices based on Model's event system
306
324
  and RabbitMQ messaging
@@ -1,318 +0,0 @@
1
- # TableSync
2
-
3
- Table synchronization via RabbitMQ
4
-
5
- # Publishing changes
6
-
7
- Include `TableSync.sync(self)` into a Sequel or ActiveRecord model. `:if` and `:unless` are
8
- supported for Sequel and ActiveRecord
9
-
10
- Functioning `Rails.cache` is required
11
-
12
- Example:
13
-
14
- ```ruby
15
- class SomeModel < Sequel::Model
16
- TableSync.sync(self, { if: -> (*) { some_code } })
17
- end
18
- ```
19
-
20
- #### #attributes_for_sync
21
-
22
- Models can implement `#attributes_for_sync` to override which attributes are published. If not
23
- present, all attributes are published
24
-
25
- #### #attrs_for_routing_key
26
-
27
- Models can implement `#attrs_for_routing_key` to override which attributes are given to routing_key_callable. If not present, default attributes are given
28
-
29
- #### #attrs_for_metadata
30
-
31
- Models can implement `#attrs_for_metadata` to override which attributes are given to metadata_callable. If not present, default attributes are given
32
-
33
- #### .table_sync_model_name
34
-
35
- Models can implement `.table_sync_model_name` class method to override the model name used for
36
- publishing events. Default is model class name
37
-
38
- #### .table_sync_destroy_attributes(original_attributes)
39
-
40
- Models can implement `.table_sync_destroy_attributes` class method to override the attributes
41
- used for publishing destroy events. Default is object's primary key
42
-
43
- ## Configuration
44
-
45
- - `TableSync.publishing_job_class_callable` is a callable which should resolve to a ActiveJob
46
- subclass that calls TableSync back to actually publish changes (required)
47
-
48
- Example:
49
-
50
- ```ruby
51
- class TableSync::Job < ActiveJob::Base
52
- def perform(*args)
53
- TableSync::Publisher.new(*args).publish_now
54
- end
55
- end
56
- ```
57
-
58
- - `TableSync.batch_publishing_job_class_callable` is a callable which should resolve to a ActiveJob
59
- subclass that calls TableSync batch publisher back to actually publish changes (required for batch publisher)
60
-
61
- - `TableSync.routing_key_callable` is a callable which resolves which routing key to use when
62
- publishing changes. It receives object class and attributes (required)
63
-
64
- Example:
65
-
66
- ```ruby
67
- TableSync.routing_key_callable = -> (klass, attributes) { klass.gsub('::', '_').tableize }
68
- ```
69
-
70
- - `TableSync.routing_metadata_callable` is a callable that adds RabbitMQ headers which can be
71
- used in routing (optional). One possible way of using it is defining a headers exchange and
72
- routing rules based on key-value pairs (which correspond to sent headers)
73
-
74
- Example:
75
-
76
- ```ruby
77
- TableSync.routing_metadata_callable = -> (klass, attributes) { attributes.slice("project_id") }
78
- ```
79
-
80
- - `TableSync.exchange_name` defines the exchange name used for publishing (optional, falls back
81
- to default Rabbit gem configuration).
82
-
83
- - `TableSync.notifier` is a module that provides publish and recieve notifications.
84
-
85
- # Manual publishing
86
-
87
- `TableSync::Publisher.new(object_class, original_attributes, confirm: true, state: :updated, debounce_time: 45)`
88
- 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.
89
-
90
- # Manual publishing with batches
91
-
92
- You can use `TableSync::BatchPublisher` to publish changes in batches (array of hashes in `attributes`).
93
- For now, only the following changes in the table can be published: `create` and` update`.
94
-
95
- When using `TableSync::BatchPublisher`,` TableSync.routing_key_callable` is called as follows:
96
- `TableSync.routing_key_callable.call(klass, {})`, i.e. empty hash is passed instead of attributes.
97
- And `TableSync.routing_metadata_callable` is not called at all: header value is set to empty hash.
98
-
99
- `TableSync::BatchPublisher.new(object_class, original_attributes_array, **options)`, where `original_attributes_array` is an array with hash of attributes of published objects and `options` is a hash of options.
100
-
101
- `options` consists of:
102
- - `confirm`, which is a flag for RabbitMQ, `true` by default
103
- - `routing_key`, which is a custom key used (if given) to override one from `TableSync.routing_key_callable`, `nil` by default
104
- - `push_original_attributes` (default value is `false`), if this option is set to `true`,
105
- original_attributes_array will be pushed to Rabbit instead of fetching records from database and sending their mapped attributes.
106
-
107
- Example:
108
-
109
- ```ruby
110
- TableSync::BatchPublisher.new("SomeClass", [{ id: 1 }, { id: 2 }], confirm: false, routing_key: "custom_routing_key")
111
- ```
112
-
113
- # Manual publishing with batches (Russian)
114
-
115
- С помощью класса `TableSync::BatchPublisher` вы можете опубликовать изменения батчами (массивом в `attributes`).
116
- Пока можно публиковать только следующие изменения в таблице: `создание записи` и `обновление записи`.
117
-
118
- При использовании `TableSync::BatchPublisher`, `TableSync.routing_key_callable` вызывается следующим образом:
119
- `TableSync.routing_key_callable.call(klass, {})`, то есть вместо аттрибутов передается пустой хэш.
120
- А `TableSync.routing_metadata_callable` не вызывается вовсе: в хидерах устанавливается пустой хэш.
121
-
122
- `TableSync::BatchPublisher.new(object_class, original_attributes_array, **options)`, где `original_attributes_array` - массив с аттрибутами публикуемых объектов и `options`- это хэш с дополнительными опциями.
123
-
124
- `options` состоит из:
125
- - `confirm`, флаг для RabbitMQ, по умолчанию - `true`
126
- - `routing_key`, ключ, который (если указан) замещает ключ, получаемый из `TableSync.routing_key_callable`, по умолчанию - `nil`
127
- - `push_original_attributes` (значение по умолчанию `false`), если для этой опции задано значение true, в Rabbit будут отправлены original_attributes_array, вместо получения значений записей из базы непосредственно перед отправкой.
128
-
129
- Example:
130
-
131
- ```ruby
132
- TableSync::BatchPublisher.new("SomeClass", [{ id: 1 }, { id: 2 }], confirm: false, routing_key: "custom_routing_key")
133
- ```
134
-
135
- # Receiving changes
136
-
137
- Naming convention for receiving handlers is `Rabbit::Handler::GROUP_ID::TableSync`,
138
- where `GROUP_ID` represents first part of source exchange name.
139
- Define handler class inherited from `TableSync::ReceivingHandler`
140
- and named according to described convention.
141
- You should use DSL inside the class.
142
- Suppose we will synchronize models {Project, News, User} project {MainProject}, then:
143
-
144
- ```ruby
145
- class Rabbit::Handler::MainProject::TableSync < TableSync::ReceivingHandler
146
- queue_as :custom_queue
147
-
148
- receive "Project", to_table: :projects
149
-
150
- receive "News", to_table: :news, events: :update do
151
- after_commit on: :update do
152
- NewsCache.reload
153
- end
154
- end
155
-
156
- receive "User", to_table: :clients, events: %i[update destroy] do
157
- mapping_overrides email: :project_user_email, id: :project_user_id
158
-
159
- only :project_user_email, :project_user_id
160
- target_keys :project_id, :project_user_id
161
- rest_key :project_user_rest
162
- version_key :project_user_version
163
-
164
- additional_data do |project_id:|
165
- { project_id: project_id }
166
- end
167
-
168
- default_values do
169
- { created_at: Time.current }
170
- end
171
- end
172
-
173
- receive "User", to_table: :users do
174
- rest_key nil
175
- end
176
- end
177
- ```
178
-
179
- ### Handler class (`Rabbit::Handler::MainProject::TableSync`)
180
-
181
- In this case:
182
- - `TableSync` - RabbitMQ event type.
183
- - `MainProject` - event source.
184
- - `Rabbit::Handler` - module for our handlers of events from RabbitMQ (there might be others)
185
-
186
- Method `queue_as` allow you to set custom queue.
187
-
188
- ### Recieving handler batch processing
189
-
190
- Receiving handler supports array of attributes in a single update event. Corresponding
191
- upsert-style logic in ActiveRecord and Sequel orm handlers is provided.
192
-
193
- ### Config DSL
194
- ```ruby
195
- receive source, to_table:, [events:, &block]
196
- ```
197
-
198
- The method receives following arguments
199
- - `source` - string, name of source model (required)
200
- - `to_table` - destination_table hash (required)
201
- - `events` - array of supported events (optional)
202
- - `block` - configuration block (optional)
203
-
204
- This method implements logic of mapping `source` to `to_table` and allows customizing the event handling logic with provided block.
205
- You can use one `source` for a lot of `to_table`.
206
-
207
- The following options are available inside the block:
208
- - `on_destroy` - defines a custom logic and behavior for `destroy` event:
209
- - definition:
210
- ```ruby
211
- on_destroy do |attributes:, target_keys:|
212
- # your code here
213
- end
214
- ```
215
- - `target_keys:` - primary keys or unique keys;
216
- - `attributes:` - received model attributes;
217
- - `only` - whitelist for receiving attributes
218
- - `skip` - return truthy value to skip the row
219
- - `target_keys` - primary keys or unique keys
220
- - `rest_key` - name of jsonb column for attributes which are not included in the whitelist. You can set the `rest_key(false)` or `rest_key(nil)` if you won't need the rest data.
221
- - `version_key` - name of version column
222
- - `first_sync_time_key` - name of the column where the time of first record synchronization should be stored. Disabled by default.
223
- - `mapping_overrides` - map for overriding receiving columns
224
- - `additional_data` - additional data for insert or update (e.g. `project_id`)
225
- - `default_values` - values for insert if a row is not found
226
- - `partitions` - proc that is used to obtain partitioned data to support table partitioning. Must return a hash which
227
- keys are names of partitions of partitioned table and values - arrays of attributes to be inserted into particular
228
- partition `{ measurements_2018_01: [ { attrs }, ... ], measurements_2018_02: [ { attrs }, ... ], ...}`.
229
- While the proc is called inside an upsert transaction it is suitable place for creating partitions for new data.
230
- Note that transaction of proc is a TableSynk.orm transaction.
231
- ```ruby
232
- partitions do |data:|
233
- data.group_by { |d| "measurements_#{d[:time].year}_#{d[:time].month}" }
234
- .tap { |data| data.keys.each { |table| DB.run("CREATE TABLE IF NOT EXISTS #{table} PARTITION OF measurements") } }
235
- end
236
- ```
237
- - `wrap_reciving` - proc that is used to wrap the receiving logic by custom block of code. Receives `data` and `receiving` attributes
238
- (received event data and receiving logic proc respectively). `receiving.call` runs receiving process (you should use it manually).
239
- - example (concurrent receiving):
240
- ```ruby
241
- wrap_receiving do |data, receiving|
242
- Locking.acquire("some-lock-key") { receiving.call }
243
- end
244
- ```
245
- - `data` attribute:
246
- - for `destroy` event - an instance of `TableSync::EventActions::DataWrapper::Destroy`;
247
- - for `update` event - an instance of `TableSync::EventActions::DataWrapper::Update`;
248
- - `#event_data` - raw recevied event data:
249
- - for `destroy` event - simple `Hash`;
250
- - for `update` event - `Hash` with `Hash<ModelKlass, Array<Hash<Symbol, Object>>>` signature;
251
- - `#destroy?` / `#update?` - corresponding predicates;
252
- - `#type` - indicates a type of data (`:destroy` and `:update` respectively);
253
- - `#each` - iterates over `#event_data` elements (acts like an iteration over an array of elements);
254
-
255
- Each of options can receive static value or code block which will be called for each event with the following arguments:
256
- - `event` - type of event (`:update` or `:destroy`)
257
- - `model` - source model (`Project`, `News`, `User` in example)
258
- - `version` - version of the data
259
- - `project_id` - id of project which is used in RabbitMQ
260
- - `data` - raw data from event (before applying `mapping_overrides`, `only`, etc.)
261
-
262
- Also, the `additional_data`, `skip` has a `current_row` field, which gives you a hash of all parameters of the current row (useful when receiving changes in batches).
263
-
264
- Block can receive any number of parameters from the list.
265
-
266
- ### Callbacks
267
- You can set callbacks like this:
268
- ```ruby
269
- before_commit on: event, &block
270
- after_commit on: event, &block
271
- ```
272
- TableSync performs this callbacks after transaction commit as to avoid side effects. Block receives array of record attributes.
273
-
274
- ### Notifications
275
-
276
- #### ActiveSupport adapter
277
-
278
- You can use an already existing ActiveSupport adapter:
279
- ```ruby
280
- TableSync.notifier = TableSync::InstrumentAdapter::ActiveSupport
281
- ```
282
-
283
- This instrumentation API is provided by Active Support. It allows to subscribe to notifications:
284
-
285
- ```ruby
286
- ActiveSupport::Notifications.subscribe(/tablesync/) do |name, start, finish, id, payload|
287
- # do something
288
- end
289
- ```
290
-
291
- Types of events available:
292
- `"tablesync.receive.update"`, `"tablesync.receive.destroy"`, `"tablesync.publish.update"`
293
- and `"tablesync.publish.destroy"`.
294
-
295
- You have access to the payload, which contains `event`, `direction`, `table`, `schema` and `count`.
296
-
297
- ```
298
- {
299
- :event => :update, # one of update / destroy
300
- :direction => :publish, # one of publish / receive
301
- :table => "users",
302
- :schema => "public",
303
- :count => 1
304
- }
305
- ```
306
-
307
- See more at https://guides.rubyonrails.org/active_support_instrumentation.html
308
-
309
-
310
- #### Custom adapters
311
-
312
- You can also create a custom adapter. It is expected to respond to the following method:
313
-
314
- ```ruby
315
- def notify(table:, event:, direction:, count:)
316
- # processes data about table_sync event
317
- end
318
- ```