table_sync 2.3.0 → 4.0.0

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/.rubocop.yml +21 -0
  3. data/CHANGELOG.md +40 -0
  4. data/Gemfile.lock +82 -77
  5. data/README.md +4 -2
  6. data/docs/message_protocol.md +24 -0
  7. data/docs/notifications.md +45 -0
  8. data/docs/publishing.md +147 -0
  9. data/docs/receiving.md +341 -0
  10. data/lib/table_sync.rb +16 -31
  11. data/lib/table_sync/errors.rb +39 -23
  12. data/lib/table_sync/publishing.rb +11 -0
  13. data/lib/table_sync/{base_publisher.rb → publishing/base_publisher.rb} +1 -1
  14. data/lib/table_sync/{batch_publisher.rb → publishing/batch_publisher.rb} +4 -4
  15. data/lib/table_sync/{orm_adapter → publishing/orm_adapter}/active_record.rb +3 -7
  16. data/lib/table_sync/{orm_adapter → publishing/orm_adapter}/sequel.rb +2 -6
  17. data/lib/table_sync/{publisher.rb → publishing/publisher.rb} +4 -4
  18. data/lib/table_sync/receiving.rb +14 -0
  19. data/lib/table_sync/receiving/config.rb +218 -0
  20. data/lib/table_sync/receiving/config_decorator.rb +27 -0
  21. data/lib/table_sync/receiving/dsl.rb +28 -0
  22. data/lib/table_sync/receiving/handler.rb +131 -0
  23. data/lib/table_sync/{model → receiving/model}/active_record.rb +36 -22
  24. data/lib/table_sync/{model → receiving/model}/sequel.rb +13 -8
  25. data/lib/table_sync/utils.rb +9 -0
  26. data/lib/table_sync/utils/interface_checker.rb +97 -0
  27. data/lib/table_sync/utils/proc_array.rb +17 -0
  28. data/lib/table_sync/utils/proc_keywords_resolver.rb +46 -0
  29. data/lib/table_sync/version.rb +1 -1
  30. data/table_sync.gemspec +2 -1
  31. metadata +42 -30
  32. data/docs/development.md +0 -43
  33. data/docs/synopsis.md +0 -336
  34. data/lib/table_sync/config.rb +0 -105
  35. data/lib/table_sync/config/callback_registry.rb +0 -53
  36. data/lib/table_sync/config_decorator.rb +0 -38
  37. data/lib/table_sync/dsl.rb +0 -25
  38. data/lib/table_sync/event_actions.rb +0 -96
  39. data/lib/table_sync/event_actions/data_wrapper.rb +0 -7
  40. data/lib/table_sync/event_actions/data_wrapper/base.rb +0 -23
  41. data/lib/table_sync/event_actions/data_wrapper/destroy.rb +0 -19
  42. data/lib/table_sync/event_actions/data_wrapper/update.rb +0 -21
  43. data/lib/table_sync/plugins.rb +0 -72
  44. data/lib/table_sync/plugins/abstract.rb +0 -55
  45. data/lib/table_sync/plugins/access_mixin.rb +0 -49
  46. data/lib/table_sync/plugins/registry.rb +0 -153
  47. data/lib/table_sync/receiving_handler.rb +0 -76
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class TableSync::Config::CallbackRegistry
4
- CALLBACK_KINDS = %i[after_commit before_commit].freeze
5
- EVENTS = %i[create update destroy].freeze
6
-
7
- InvalidCallbackKindError = Class.new(ArgumentError)
8
- InvalidEventError = Class.new(ArgumentError)
9
-
10
- def initialize
11
- @callbacks = CALLBACK_KINDS.map { |event| [event, make_event_hash] }.to_h
12
- end
13
-
14
- def register_callback(callback, kind:, event:)
15
- validate_callback_kind!(kind)
16
- validate_event!(event)
17
-
18
- callbacks.fetch(kind)[event] << callback
19
- end
20
-
21
- def get_callbacks(kind:, event:)
22
- validate_callback_kind!(kind)
23
- validate_event!(event)
24
-
25
- callbacks.fetch(kind).fetch(event, [])
26
- end
27
-
28
- private
29
-
30
- attr_reader :callbacks
31
-
32
- def make_event_hash
33
- Hash.new { |hsh, key| hsh[key] = [] }
34
- end
35
-
36
- def validate_callback_kind!(kind)
37
- unless CALLBACK_KINDS.include?(kind)
38
- raise(
39
- InvalidCallbackKindError,
40
- "Invalid callback kind: #{kind.inspect}. Valid kinds are #{CALLBACK_KINDS.inspect}",
41
- )
42
- end
43
- end
44
-
45
- def validate_event!(event)
46
- unless EVENTS.include?(event)
47
- raise(
48
- InvalidEventError,
49
- "Invalid event: #{event.inspect}. Valid events are #{EVENTS.inspect}",
50
- )
51
- end
52
- end
53
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableSync
4
- # rubocop:disable Style/MissingRespondToMissing, Style/MethodMissingSuper
5
- class ConfigDecorator
6
- include ::TableSync::EventActions
7
- extend Forwardable
8
-
9
- def_delegators :@config, :on_destroy, :wrap_receiving
10
-
11
- def initialize(config, handler)
12
- @config = config
13
- @handler = handler
14
- end
15
-
16
- def method_missing(name, *args)
17
- value = @config.send(name)
18
-
19
- if value.is_a?(Proc)
20
- value.call(
21
- event: @handler.event,
22
- model: @handler.model,
23
- version: @handler.version,
24
- project_id: @handler.project_id,
25
- data: @handler.data,
26
- current_row: args.first,
27
- )
28
- else
29
- value
30
- end
31
- end
32
-
33
- def allow_event?(event)
34
- @config.allow_event?(event)
35
- end
36
- end
37
- # rubocop:enable Style/MissingRespondToMissing, Style/MethodMissingSuper
38
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableSync
4
- module DSL
5
- def inherited(klass)
6
- klass.instance_variable_set(:@configs, configs.deep_dup)
7
- super
8
- end
9
-
10
- def configs
11
- @configs ||= Hash.new { |hash, key| hash[key] = [] }
12
- end
13
-
14
- def receive(source, to_table:, events: nil, &block)
15
- config = ::TableSync::Config.new(
16
- model: TableSync.orm.model.new(to_table),
17
- events: events,
18
- )
19
-
20
- config.instance_exec(&block) if block
21
-
22
- configs[source.to_s] << config
23
- end
24
- end
25
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableSync
4
- module EventActions
5
- def update(data)
6
- data.each_value do |attribute_set|
7
- attribute_set.each do |attributes|
8
- prevent_incomplete_event!(attributes)
9
- end
10
- end
11
-
12
- with_wrapping(DataWrapper::Update.new(data)) do
13
- process_upsert(data)
14
- end
15
- end
16
-
17
- def destroy(data)
18
- attributes = data.first || {}
19
- target_attributes = attributes.select { |key, _value| target_keys.include?(key) }
20
- prevent_incomplete_event!(target_attributes)
21
-
22
- with_wrapping(DataWrapper::Destroy.new(attributes)) do
23
- process_destroy(attributes, target_attributes)
24
- end
25
- end
26
-
27
- def process_upsert(data) # rubocop:disable Metrics/MethodLength
28
- model.transaction do
29
- args = {
30
- data: data,
31
- target_keys: target_keys,
32
- version_key: version_key,
33
- first_sync_time_key: first_sync_time_key,
34
- default_values: default_values,
35
- }
36
-
37
- @config.callback_registry.get_callbacks(kind: :before_commit, event: :update).each do |cb|
38
- cb[data.values.flatten]
39
- end
40
-
41
- results = data.reduce([]) do |upserts, (part_model, part_data)|
42
- upserts + part_model.upsert(**args, data: part_data)
43
- end
44
-
45
- return if results.empty?
46
- raise TableSync::UpsertError.new(**args) unless expected_update_result?(results)
47
-
48
- @config.model.after_commit do
49
- @config.callback_registry.get_callbacks(kind: :after_commit, event: :update).each do |cb|
50
- cb[results]
51
- end
52
- end
53
- end
54
- end
55
-
56
- def process_destroy(attributes, target_attributes)
57
- model.transaction do
58
- @config.callback_registry.get_callbacks(kind: :before_commit, event: :destroy).each do |cb|
59
- cb[attributes]
60
- end
61
-
62
- if on_destroy
63
- results = on_destroy.call(attributes: attributes, target_keys: target_keys)
64
- else
65
- results = model.destroy(target_attributes)
66
- return if results.empty?
67
- raise TableSync::DestroyError.new(target_attributes) if results.size != 1
68
- end
69
-
70
- @config.model.after_commit do
71
- @config.callback_registry.get_callbacks(kind: :after_commit, event: :destroy).each do |cb|
72
- cb[results]
73
- end
74
- end
75
- end
76
- end
77
-
78
- def with_wrapping(data = {}, &block)
79
- if @config.wrap_receiving
80
- @config.wrap_receiving.call(data, block)
81
- else
82
- yield
83
- end
84
- end
85
-
86
- def expected_update_result?(query_results)
87
- query_results.uniq { |d| d.slice(*target_keys) }.size == query_results.size
88
- end
89
-
90
- def prevent_incomplete_event!(attributes)
91
- unless target_keys.all?(&attributes.keys.method(:include?))
92
- raise TableSync::UnprovidedEventTargetKeysError.new(target_keys, attributes)
93
- end
94
- end
95
- end
96
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TableSync::EventActions::DataWrapper
4
- require_relative "data_wrapper/base"
5
- require_relative "data_wrapper/destroy"
6
- require_relative "data_wrapper/update"
7
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class TableSync::EventActions::DataWrapper::Base
4
- include Enumerable
5
-
6
- attr_reader :event_data
7
-
8
- def initialize(event_data)
9
- @event_data = event_data
10
- end
11
-
12
- def type
13
- raise NoMethodError # NOTE: for clarity
14
- end
15
-
16
- def destroy?
17
- false
18
- end
19
-
20
- def update?
21
- false
22
- end
23
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class TableSync::EventActions::DataWrapper::Destroy < TableSync::EventActions::DataWrapper::Base
4
- def type
5
- :destroy
6
- end
7
-
8
- def each
9
- yield(event_data)
10
- end
11
-
12
- def destroy?
13
- true
14
- end
15
-
16
- def update?
17
- false
18
- end
19
- end
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class TableSync::EventActions::DataWrapper::Update < TableSync::EventActions::DataWrapper::Base
4
- def type
5
- :update
6
- end
7
-
8
- def each
9
- event_data.each_pair do |model_klass, changed_models_attrs|
10
- yield([model_klass, changed_models_attrs])
11
- end
12
- end
13
-
14
- def destroy?
15
- false
16
- end
17
-
18
- def update?
19
- true
20
- end
21
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api public
4
- # @since 2.2.0
5
- module TableSync::Plugins
6
- require_relative "plugins/registry"
7
- require_relative "plugins/access_mixin"
8
- require_relative "plugins/abstract"
9
-
10
- # @since 2.2.0
11
- @plugin_registry = Registry.new
12
- # @since 2.2.0
13
- @access_lock = Mutex.new
14
-
15
- class << self
16
- # @param plugin_name [Symbol, String]
17
- # @return [void]
18
- #
19
- # @api public
20
- # @since 2.2.0
21
- def load(plugin_name)
22
- thread_safe { plugin_registry[plugin_name].load! }
23
- end
24
-
25
- # @return [Array<String>]
26
- #
27
- # @api public
28
- # @since 2.2.0
29
- def loaded_plugins
30
- thread_safe { plugin_registry.loaded.keys }
31
- end
32
-
33
- # @return [Array<String>]
34
- #
35
- # @api public
36
- # @since 2.2.0
37
- def names
38
- thread_safe { plugin_registry.names }
39
- end
40
-
41
- # @param plugin_name [Symbol, String]
42
- # @return [void]
43
- #
44
- # @api private
45
- # @since 2.2.0
46
- def register_plugin(plugin_name, plugin_module)
47
- thread_safe { plugin_registry[plugin_name] = plugin_module }
48
- end
49
-
50
- private
51
-
52
- # @return [TableSync::Plugins::Registry]
53
- #
54
- # @api private
55
- # @since 2.2.0
56
- attr_reader :plugin_registry
57
-
58
- # @return [Mutex]
59
- #
60
- # @api private
61
- # @since 2.2.0
62
- attr_reader :access_lock
63
-
64
- # @return [void]
65
- #
66
- # @api private
67
- # @since 2.2.0
68
- def thread_safe
69
- access_lock.synchronize { yield if block_given? }
70
- end
71
- end
72
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- # @since 2.2.0
5
- class TableSync::Plugins::Abstract
6
- class << self
7
- # @param child_klass [Class]
8
- # @return [void]
9
- #
10
- # @api private
11
- # @since 2.2.0
12
- def inherited(child_klass)
13
- child_klass.instance_variable_set(:@__loaded__, false)
14
- child_klass.instance_variable_set(:@__lock__, Mutex.new)
15
- super
16
- end
17
-
18
- # @return [void]
19
- #
20
- # @api private
21
- # @since 2.2.0
22
- def load!
23
- __thread_safe__ do
24
- unless @__loaded__
25
- @__loaded__ = true
26
- install!
27
- end
28
- end
29
- end
30
-
31
- # @return [Boolean]
32
- #
33
- # @api private
34
- # @since 2.2.0
35
- def loaded?
36
- __thread_safe__ { @__loaded__ }
37
- end
38
-
39
- private
40
-
41
- # @return [void]
42
- #
43
- # @api private
44
- # @since 2.2.0
45
- def install!; end
46
-
47
- # @return [Any]
48
- #
49
- # @api private
50
- # @since 2.2.0
51
- def __thread_safe__
52
- @__lock__.synchronize { yield }
53
- end
54
- end
55
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # @api private
4
- # @since 2.2.0
5
- module TableSync::Plugins::AccessMixin
6
- # @param plugin_name [Symbol, String]
7
- # @return [void]
8
- #
9
- # @see TableSync::Plugins
10
- #
11
- # @api public
12
- # @since 2.2.0
13
- def plugin(plugin_name)
14
- TableSync::Plugins.load(plugin_name)
15
- end
16
- alias_method :enable, :plugin
17
- alias_method :load, :plugin
18
-
19
- # @return [Array<String>]
20
- #
21
- # @see TableSync::Plugins
22
- #
23
- # @api public
24
- # @since 2.2.0
25
- def plugins
26
- TableSync::Plugins.names
27
- end
28
-
29
- # @return [Hash<String,Class<TableSync::Plugins::Abstract>>]
30
- #
31
- # @api private
32
- # @since 2.2.0
33
- def loaded_plugins
34
- TableSync::Plugins.loaded_plugins
35
- end
36
- alias_method :enabled_plugins, :loaded_plugins
37
-
38
- # @param plugin_name [String, Symbol]
39
- # @param plugin_klass [Class<TableSync::Plugins::Abstract>]
40
- # @return [void]
41
- #
42
- # @see TableSync::Plugins
43
- #
44
- # @api public
45
- # @since 2.2.0
46
- def register_plugin(plugin_name, plugin_klass)
47
- TableSync::Plugins.register_plugin(plugin_name, plugin_klass)
48
- end
49
- end