table_sync 2.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
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 +70 -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} +4 -4
  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 +136 -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 -338
  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
@@ -1,61 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "memery"
4
+ require "self_data"
4
5
  require "rabbit_messaging"
5
6
  require "rabbit/event_handler" # NOTE: from rabbit_messaging"
6
7
  require "active_support/core_ext/object/blank"
7
8
  require "active_support/core_ext/numeric/time"
8
9
 
9
10
  module TableSync
10
- require_relative "./table_sync/version"
11
- require_relative "./table_sync/errors"
12
- require_relative "./table_sync/event_actions"
13
- require_relative "./table_sync/event_actions/data_wrapper"
14
- require_relative "./table_sync/config"
15
- require_relative "./table_sync/config/callback_registry"
16
- require_relative "./table_sync/config_decorator"
17
- require_relative "./table_sync/dsl"
18
- require_relative "./table_sync/receiving_handler"
19
- require_relative "./table_sync/base_publisher"
20
- require_relative "./table_sync/publisher"
21
- require_relative "./table_sync/batch_publisher"
22
- require_relative "./table_sync/orm_adapter/active_record"
23
- require_relative "./table_sync/orm_adapter/sequel"
24
- require_relative "./table_sync/model/active_record"
25
- require_relative "./table_sync/model/sequel"
26
- require_relative "./table_sync/instrument"
27
- require_relative "./table_sync/instrument_adapter/active_support"
28
- require_relative "./table_sync/naming_resolver/active_record"
29
- require_relative "./table_sync/naming_resolver/sequel"
11
+ require_relative "table_sync/utils"
12
+ require_relative "table_sync/version"
13
+ require_relative "table_sync/errors"
14
+ require_relative "table_sync/instrument"
15
+ require_relative "table_sync/instrument_adapter/active_support"
16
+ require_relative "table_sync/naming_resolver/active_record"
17
+ require_relative "table_sync/naming_resolver/sequel"
18
+ require_relative "table_sync/receiving"
19
+ require_relative "table_sync/publishing"
30
20
 
31
21
  class << self
32
- include Memery
33
-
34
22
  attr_accessor :publishing_job_class_callable
35
23
  attr_accessor :batch_publishing_job_class_callable
36
24
  attr_accessor :routing_key_callable
37
25
  attr_accessor :exchange_name
38
26
  attr_accessor :routing_metadata_callable
39
27
  attr_accessor :notifier
28
+ attr_reader :orm
29
+ attr_reader :publishing_adapter
30
+ attr_reader :receiving_model
40
31
 
41
- def sync(*args)
42
- orm.setup_sync(*args)
32
+ def sync(klass, **opts)
33
+ publishing_adapter.setup_sync(klass, opts)
43
34
  end
44
35
 
45
36
  def orm=(val)
46
- clear_memery_cache!
47
- @orm = val
48
- end
49
-
50
- memoize def orm
51
- case @orm
37
+ case val
52
38
  when :active_record
53
- ORMAdapter::ActiveRecord
39
+ @publishing_adapter = Publishing::ORMAdapter::ActiveRecord
40
+ @receiving_model = Receiving::Model::ActiveRecord
54
41
  when :sequel
55
- ORMAdapter::Sequel
42
+ @publishing_adapter = Publishing::ORMAdapter::Sequel
43
+ @receiving_model = Receiving::Model::Sequel
56
44
  else
57
- raise "ORM not supported: #{@orm.inspect}"
45
+ raise ORMNotSupported.new(val.inspect)
58
46
  end
47
+
48
+ @orm = val
59
49
  end
60
50
  end
61
51
  end
@@ -4,38 +4,76 @@ module TableSync
4
4
  Error = Class.new(StandardError)
5
5
 
6
6
  class UpsertError < Error
7
- def initialize(data:, target_keys:, version_key:, first_sync_time_key:, default_values:)
8
- super <<~MSG
9
- Upsert has changed more than 1 row;
10
- data: #{data.inspect}
11
- target_keys: #{target_keys.inspect}
12
- version_key: #{version_key.inspect}
13
- first_sync_time_key: #{first_sync_time_key.inspect}
14
- default_values: #{default_values.inspect}
7
+ def initialize(data:, target_keys:, result:)
8
+ super("data: #{data.inspect}, target_keys: #{target_keys.inspect}, result: #{result.inspect}")
9
+ end
10
+ end
11
+
12
+ class DestroyError < Error
13
+ def initialize(data:, target_keys:, result:)
14
+ super("data: #{data.inspect}, target_keys: #{target_keys.inspect}, result: #{result.inspect}")
15
+ end
16
+ end
17
+
18
+ class DataError < Error
19
+ def initialize(data, target_keys, description)
20
+ super(<<~MSG.squish)
21
+ #{description}
22
+ target_keys: #{target_keys}
23
+ data: #{data}
15
24
  MSG
16
25
  end
17
26
  end
18
27
 
19
- class UndefinedConfig < Error
20
- def initialize(model)
21
- super("Config not defined for model; model: #{model.inspect}")
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")
22
38
  end
23
39
  end
24
40
 
25
- class DestroyError < Error
26
- def initialize(data)
27
- super("Destroy has changed more than 1 row; data: #{data.inspect}")
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")
28
47
  end
29
48
  end
30
49
 
31
- class UnprovidedEventTargetKeysError < Error
32
- # @param target_keys [Array<Symbol,String>]
33
- # @param target_attributes [Hash<Symbol|String,Any>]
34
- def initialize(target_keys, target_attributes)
35
- super(<<~MSG.squish)
36
- Some target keys not found in received attributes!
37
- (Expects: #{target_keys}, Actual: #{target_attributes.keys})
38
- MSG
50
+ class InterfaceError < Error
51
+ def initialize(object, method_name, parameters, description)
52
+ parameters = parameters.map do |parameter|
53
+ type, name = parameter
54
+
55
+ case type
56
+ when :req
57
+ name.to_s
58
+ when :keyreq
59
+ "#{name}:"
60
+ when :block
61
+ "&#{name}"
62
+ end
63
+ end
64
+
65
+ signature = "#{method_name}(#{parameters.join(", ")})"
66
+
67
+ super("#{object} has to implement method `#{signature}`\n#{description}")
68
+ end
69
+ end
70
+
71
+ UndefinedEvent = Class.new(Error)
72
+ ORMNotSupported = Class.new(Error)
73
+
74
+ class WrongOptionValue < Error
75
+ def initialize(model, option, value)
76
+ super("TableSync config for #{model.inspect} can't contain #{value.inspect} as #{option}")
39
77
  end
40
78
  end
41
79
  end
@@ -3,7 +3,7 @@
3
3
  module TableSync::Instrument
4
4
  module_function
5
5
 
6
- def notify(*args)
7
- TableSync.notifier&.notify(*args)
6
+ def notify(**args)
7
+ TableSync.notifier&.notify(**args)
8
8
  end
9
9
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSync
4
+ module Publishing
5
+ require_relative "publishing/base_publisher"
6
+ require_relative "publishing/publisher"
7
+ require_relative "publishing/batch_publisher"
8
+ require_relative "publishing/orm_adapter/active_record"
9
+ require_relative "publishing/orm_adapter/sequel"
10
+ end
11
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class TableSync::BasePublisher
3
+ class TableSync::Publishing::BasePublisher
4
4
  include Memery
5
5
 
6
6
  BASE_SAFE_JSON_TYPES = [NilClass, String, TrueClass, FalseClass, Numeric, Symbol].freeze
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class TableSync::BatchPublisher < TableSync::BasePublisher
3
+ class TableSync::Publishing::BatchPublisher < TableSync::Publishing::BasePublisher
4
4
  def initialize(object_class, original_attributes_array, **options)
5
5
  @original_attributes_array = original_attributes_array.map do |hash|
6
6
  filter_safe_for_serialization(hash.deep_symbolize_keys)
@@ -22,7 +22,7 @@ class TableSync::BatchPublisher < TableSync::BasePublisher
22
22
  return unless need_publish?
23
23
  Rabbit.publish(params)
24
24
 
25
- model_naming = TableSync.orm.model_naming(object_class)
25
+ model_naming = TableSync.publishing_adapter.model_naming(object_class)
26
26
  TableSync::Instrument.notify table: model_naming.table, schema: model_naming.schema,
27
27
  event: event,
28
28
  count: publishing_data[:attributes].size, direction: :publish
@@ -41,7 +41,7 @@ class TableSync::BatchPublisher < TableSync::BasePublisher
41
41
  end
42
42
 
43
43
  memoize def objects
44
- needles.map { |needle| TableSync.orm.find(object_class, needle) }.compact
44
+ needles.map { |needle| TableSync.publishing_adapter.find(object_class, needle) }.compact
45
45
  end
46
46
 
47
47
  def job_callable
@@ -90,7 +90,7 @@ class TableSync::BatchPublisher < TableSync::BasePublisher
90
90
  if attributes_for_sync_defined?
91
91
  object.attributes_for_sync
92
92
  else
93
- TableSync.orm.attributes(object)
93
+ TableSync.publishing_adapter.attributes(object)
94
94
  end
95
95
  end
96
96
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module TableSync::ORMAdapter
3
+ module TableSync::Publishing::ORMAdapter
4
4
  module ActiveRecord
5
5
  module_function
6
6
 
7
- def model
8
- ::TableSync::Model::ActiveRecord
9
- end
10
-
11
7
  def model_naming(object)
12
8
  ::TableSync::NamingResolver::ActiveRecord.new(table_name: object.table_name)
13
9
  end
@@ -20,14 +16,14 @@ module TableSync::ORMAdapter
20
16
  object.attributes
21
17
  end
22
18
 
23
- def setup_sync(klass, **opts)
19
+ def setup_sync(klass, opts)
24
20
  debounce_time = opts.delete(:debounce_time)
25
21
 
26
22
  klass.instance_exec do
27
23
  { create: :created, update: :updated, destroy: :destroyed }.each do |event, state|
28
24
  after_commit(on: event, **opts) do
29
- TableSync::Publisher.new(self.class.name, attributes,
30
- state: state, debounce_time: debounce_time).publish
25
+ TableSync::Publishing::Publisher.new(self.class.name, attributes,
26
+ state: state, debounce_time: debounce_time).publish
31
27
  end
32
28
  end
33
29
  end
@@ -1,13 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module TableSync::ORMAdapter
3
+ module TableSync::Publishing::ORMAdapter
4
4
  module Sequel
5
5
  module_function
6
6
 
7
- def model
8
- ::TableSync::Model::Sequel
9
- end
10
-
11
7
  def model_naming(object)
12
8
  ::TableSync::NamingResolver::Sequel.new(table_name: object.table_name, db: object.db)
13
9
  end
@@ -20,7 +16,7 @@ module TableSync::ORMAdapter
20
16
  object.values
21
17
  end
22
18
 
23
- def setup_sync(klass, **opts)
19
+ def setup_sync(klass, opts)
24
20
  if_predicate = to_predicate(opts.delete(:if), true)
25
21
  unless_predicate = to_predicate(opts.delete(:unless), false)
26
22
  debounce_time = opts.delete(:debounce_time)
@@ -44,7 +40,7 @@ module TableSync::ORMAdapter
44
40
  klass.send(:define_method, :"after_#{event}") do
45
41
  if instance_eval(&if_predicate) && !instance_eval(&unless_predicate)
46
42
  db.after_commit do
47
- TableSync::Publisher.new(
43
+ TableSync::Publishing::Publisher.new(
48
44
  self.class.name,
49
45
  values,
50
46
  state: state,
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class TableSync::Publisher < TableSync::BasePublisher
3
+ class TableSync::Publishing::Publisher < TableSync::Publishing::BasePublisher
4
4
  DEBOUNCE_TIME = 1.minute
5
5
 
6
6
  # 'original_attributes' are not published, they are used to resolve the routing key
@@ -34,7 +34,7 @@ class TableSync::Publisher < TableSync::BasePublisher
34
34
  return if !object && !destroyed?
35
35
 
36
36
  Rabbit.publish(params)
37
- model_naming = TableSync.orm.model_naming(object_class)
37
+ model_naming = TableSync.publishing_adapter.model_naming(object_class)
38
38
  TableSync::Instrument.notify table: model_naming.table, schema: model_naming.schema,
39
39
  event: event, direction: :publish
40
40
  end
@@ -95,12 +95,12 @@ class TableSync::Publisher < TableSync::BasePublisher
95
95
  elsif attributes_for_sync_defined?
96
96
  object.attributes_for_sync
97
97
  else
98
- TableSync.orm.attributes(object)
98
+ TableSync.publishing_adapter.attributes(object)
99
99
  end
100
100
  end
101
101
 
102
102
  memoize def object
103
- TableSync.orm.find(object_class, needle)
103
+ TableSync.publishing_adapter.find(object_class, needle)
104
104
  end
105
105
 
106
106
  def event
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSync
4
+ module Receiving
5
+ AVAILABLE_EVENTS = [:update, :destroy].freeze
6
+
7
+ require_relative "receiving/config"
8
+ require_relative "receiving/config_decorator"
9
+ require_relative "receiving/dsl"
10
+ require_relative "receiving/handler"
11
+ require_relative "receiving/model/active_record"
12
+ require_relative "receiving/model/sequel"
13
+ end
14
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TableSync::Receiving
4
+ class Config
5
+ attr_reader :model, :events
6
+
7
+ def initialize(model:, events: AVAILABLE_EVENTS)
8
+ @model = model
9
+
10
+ @events = [events].flatten.map(&:to_sym)
11
+
12
+ unless @events.all? { |event| AVAILABLE_EVENTS.include?(event) }
13
+ raise TableSync::UndefinedEvent.new(events)
14
+ end
15
+
16
+ self.class.default_values_for_options.each do |ivar, default_value_generator|
17
+ instance_variable_set(ivar, default_value_generator.call(self))
18
+ end
19
+ end
20
+
21
+ class << self
22
+ attr_reader :default_values_for_options
23
+
24
+ # In a configs this options are requested as they are
25
+ # config.option - get value
26
+ # config.option(args) - set static value
27
+ # config.option { ... } - set proc as value
28
+ #
29
+ # In `Receiving::Handler` or `Receiving::EventActions` this options are requested
30
+ # through `Receiving::ConfigDecorator#method_missing` which always executes `config.option`
31
+
32
+ def add_option(name, value_setter_wrapper:, value_as_proc_setter_wrapper:, default:)
33
+ ivar = "@#{name}".to_sym
34
+
35
+ @default_values_for_options ||= {}
36
+ @default_values_for_options[ivar] = default
37
+
38
+ define_method(name) do |*value, &value_as_proc|
39
+ return instance_variable_get(ivar) if value.empty? && value_as_proc.nil?
40
+
41
+ value = value.first if value.size == 1
42
+
43
+ if value_as_proc.present?
44
+ new_value = TableSync::Utils.proc_keywords_resolver(&value_as_proc)
45
+ setter_wrapper = value_as_proc_setter_wrapper
46
+ else
47
+ new_value = value
48
+ setter_wrapper = value_setter_wrapper
49
+ end
50
+
51
+ old_value = instance_variable_get(ivar)
52
+ result_value = instance_exec(name, new_value, old_value, &setter_wrapper)
53
+ instance_variable_set(ivar, result_value)
54
+ end
55
+ end
56
+ end
57
+
58
+ def allow_event?(name)
59
+ events.include?(name)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Helpers:
65
+
66
+ column_key = proc do |option_name, new_value|
67
+ unless model.columns.include?(new_value)
68
+ raise TableSync::WrongOptionValue.new(model, option_name, new_value)
69
+ end
70
+ new_value
71
+ end
72
+
73
+ exactly_symbol = proc do |option_name, new_value|
74
+ unless new_value.is_a? Symbol
75
+ raise TableSync::WrongOptionValue.new(model, option_name, new_value)
76
+ end
77
+ new_value
78
+ end
79
+
80
+ to_array = proc do |block|
81
+ proc do |option_name, new_value|
82
+ new_value = [new_value].flatten
83
+ new_value.map { |value| instance_exec(option_name, value, &block) }
84
+ end
85
+ end
86
+
87
+ exactly_not_array = proc do |block|
88
+ proc do |option_name, new_value|
89
+ if new_value.is_a? Array
90
+ raise TableSync::WrongOptionValue.new(model, option_name, new_value)
91
+ end
92
+ instance_exec(option_name, new_value, &block)
93
+ end
94
+ end
95
+
96
+ exactly_hash = proc do |block_for_keys, block_for_values|
97
+ proc do |option_name, new_value|
98
+ unless new_value.is_a? Hash
99
+ raise TableSync::WrongOptionValue.new(model, option_name, new_value)
100
+ end
101
+
102
+ new_value.to_a.map do |key, value|
103
+ [
104
+ instance_exec("#{option_name} keys", key, &block_for_keys),
105
+ instance_exec("#{option_name} values", value, &block_for_values),
106
+ ]
107
+ end.to_h
108
+ end
109
+ end
110
+
111
+ any_value = proc do |_option_name, new_value|
112
+ new_value
113
+ end
114
+
115
+ raise_error = proc do |option_name, new_value|
116
+ raise TableSync::WrongOptionValue.new(model, option_name, new_value)
117
+ end
118
+
119
+ exactly_boolean = proc do |option_name, new_value|
120
+ unless new_value.is_a?(TrueClass) || new_value.is_a?(FalseClass)
121
+ raise TableSync::WrongOptionValue.new(model, option_name, new_value)
122
+ end
123
+ new_value
124
+ end
125
+
126
+ allow_false = proc do |block|
127
+ proc do |option_name, new_value|
128
+ next false if new_value.is_a? FalseClass
129
+ instance_exec(option_name, new_value, &block)
130
+ end
131
+ end
132
+
133
+ proc_result = proc do |block|
134
+ proc do |option_name, new_value|
135
+ proc do |*args, &b|
136
+ result = new_value.call(*args, &b)
137
+ instance_exec(option_name, result, &block)
138
+ end
139
+ end
140
+ end
141
+
142
+ accumulate_procs = proc do |block|
143
+ proc do |option_name, new_value, old_value|
144
+ old_value.push(&instance_exec(option_name, new_value, &block))
145
+ end
146
+ end
147
+
148
+ # Options:
149
+
150
+ # rubocop:disable Layout/ArgumentAlignment
151
+
152
+ TableSync::Receiving::Config.add_option :only,
153
+ value_setter_wrapper: to_array[column_key],
154
+ value_as_proc_setter_wrapper: proc_result[to_array[column_key]],
155
+ default: proc { |config| config.model.columns }
156
+
157
+ TableSync::Receiving::Config.add_option :target_keys,
158
+ value_setter_wrapper: to_array[column_key],
159
+ value_as_proc_setter_wrapper: proc_result[to_array[column_key]],
160
+ default: proc { |config| Array.wrap(config.model.primary_keys) }
161
+
162
+ TableSync::Receiving::Config.add_option :rest_key,
163
+ value_setter_wrapper: exactly_not_array[allow_false[column_key]],
164
+ value_as_proc_setter_wrapper: proc_result[exactly_not_array[allow_false[column_key]]],
165
+ default: proc { :rest }
166
+
167
+ TableSync::Receiving::Config.add_option :version_key,
168
+ value_setter_wrapper: exactly_not_array[column_key],
169
+ value_as_proc_setter_wrapper: proc_result[exactly_not_array[column_key]],
170
+ default: proc { :version }
171
+
172
+ TableSync::Receiving::Config.add_option :except,
173
+ value_setter_wrapper: to_array[exactly_symbol],
174
+ value_as_proc_setter_wrapper: proc_result[to_array[exactly_symbol]],
175
+ default: proc { [] }
176
+
177
+ TableSync::Receiving::Config.add_option :mapping_overrides,
178
+ value_setter_wrapper: exactly_hash[exactly_symbol, exactly_symbol],
179
+ value_as_proc_setter_wrapper: proc_result[exactly_hash[exactly_symbol, exactly_symbol]],
180
+ default: proc { {} }
181
+
182
+ TableSync::Receiving::Config.add_option :additional_data,
183
+ value_setter_wrapper: exactly_hash[exactly_symbol, any_value],
184
+ value_as_proc_setter_wrapper: proc_result[exactly_hash[exactly_symbol, any_value]],
185
+ default: proc { {} }
186
+
187
+ TableSync::Receiving::Config.add_option :default_values,
188
+ value_setter_wrapper: exactly_hash[exactly_symbol, any_value],
189
+ value_as_proc_setter_wrapper: proc_result[exactly_hash[exactly_symbol, any_value]],
190
+ default: proc { {} }
191
+
192
+ TableSync::Receiving::Config.add_option :skip,
193
+ value_setter_wrapper: exactly_boolean,
194
+ value_as_proc_setter_wrapper: proc_result[exactly_boolean],
195
+ default: proc { false }
196
+
197
+ TableSync::Receiving::Config.add_option :wrap_receiving,
198
+ value_setter_wrapper: raise_error,
199
+ value_as_proc_setter_wrapper: any_value,
200
+ default: proc { proc { |&block| block.call } }
201
+
202
+ %i[
203
+ before_update
204
+ after_commit_on_update
205
+ before_destroy
206
+ after_commit_on_destroy
207
+ ].each do |option|
208
+ TableSync::Receiving::Config.add_option option,
209
+ value_setter_wrapper: raise_error,
210
+ value_as_proc_setter_wrapper: accumulate_procs[any_value],
211
+ default: (proc do |_config|
212
+ TableSync::Utils::ProcArray.new do |array, args, &block|
213
+ array.map { |pr| pr.call(*args, &block) }
214
+ end
215
+ end)
216
+ end
217
+
218
+ # rubocop:enable Layout/ArgumentAlignment