table_sync 0.0.0 → 1.4.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/.gitignore +1 -0
- data/.rspec +3 -0
- data/.travis.yml +10 -0
- data/README.md +3 -1
- data/Rakefile +2 -0
- data/docs/synopsis.md +246 -0
- data/lib/table_sync/base_publisher.rb +103 -0
- data/lib/table_sync/batch_publisher.rb +102 -0
- data/lib/table_sync/config/callback_registry.rb +53 -0
- data/lib/table_sync/config.rb +84 -0
- data/lib/table_sync/config_decorator.rb +35 -0
- data/lib/table_sync/dsl.rb +25 -0
- data/lib/table_sync/errors.rb +30 -0
- data/lib/table_sync/event_actions.rb +60 -0
- data/lib/table_sync/model/active_record.rb +110 -0
- data/lib/table_sync/model/sequel.rb +76 -0
- data/lib/table_sync/orm_adapter/active_record.rb +29 -0
- data/lib/table_sync/orm_adapter/sequel.rb +50 -0
- data/lib/table_sync/publisher.rb +123 -0
- data/lib/table_sync/receiving_handler.rb +76 -0
- data/lib/table_sync/version.rb +1 -1
- data/lib/table_sync.rb +51 -1
- data/table_sync.gemspec +15 -2
- metadata +148 -4
@@ -0,0 +1,25 @@
|
|
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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync
|
4
|
+
Error = Class.new(StandardError)
|
5
|
+
|
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}
|
15
|
+
MSG
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class DestroyError < Error
|
20
|
+
def initialize(data)
|
21
|
+
super("Destroy has changed more than 1 row; data: #{data.inspect}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class UndefinedConfig < Error
|
26
|
+
def initialize(model)
|
27
|
+
super("Config not defined for model; model: #{model.inspect}")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync
|
4
|
+
module EventActions
|
5
|
+
def update(data) # rubocop:disable Metrics/MethodLength
|
6
|
+
model.transaction do
|
7
|
+
args = {
|
8
|
+
data: data,
|
9
|
+
target_keys: target_keys,
|
10
|
+
version_key: version_key,
|
11
|
+
first_sync_time_key: first_sync_time_key,
|
12
|
+
default_values: default_values,
|
13
|
+
}
|
14
|
+
|
15
|
+
@config.callback_registry.get_callbacks(kind: :before_commit, event: :update).each do |cb|
|
16
|
+
cb[data.values.flatten]
|
17
|
+
end
|
18
|
+
|
19
|
+
results = data.reduce([]) do |upserts, (part_model, part_data)|
|
20
|
+
upserts + part_model.upsert(**args, data: part_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
return if results.empty?
|
24
|
+
raise TableSync::UpsertError.new(**args) unless correct_keys?(results)
|
25
|
+
|
26
|
+
@config.model.after_commit do
|
27
|
+
@config.callback_registry.get_callbacks(kind: :after_commit, event: :update).each do |cb|
|
28
|
+
cb[results]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def destroy(data)
|
35
|
+
attributes = data.first || {}
|
36
|
+
target_attributes = attributes.select { |key| target_keys.include?(key) }
|
37
|
+
|
38
|
+
model.transaction do
|
39
|
+
@config.callback_registry.get_callbacks(kind: :before_commit, event: :destroy).each do |cb|
|
40
|
+
cb[attributes]
|
41
|
+
end
|
42
|
+
|
43
|
+
results = model.destroy(target_attributes)
|
44
|
+
|
45
|
+
return if results.empty?
|
46
|
+
raise TableSync::DestroyError.new(target_attributes) if results.size != 1
|
47
|
+
|
48
|
+
@config.model.after_commit do
|
49
|
+
@config.callback_registry.get_callbacks(kind: :after_commit, event: :destroy).each do |cb|
|
50
|
+
cb[results]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def correct_keys?(query_results)
|
57
|
+
query_results.uniq { |d| d.slice(*target_keys) }.size == query_results.size
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::Model
|
4
|
+
class ActiveRecord
|
5
|
+
class AfterCommitWrap
|
6
|
+
def initialize(&block)
|
7
|
+
@callback = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def committed!(*)
|
11
|
+
@callback.call
|
12
|
+
end
|
13
|
+
|
14
|
+
def before_committed!(*); end
|
15
|
+
|
16
|
+
def rolledback!(*); end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(table_name)
|
20
|
+
@raw_model = Class.new(::ActiveRecord::Base) do
|
21
|
+
self.table_name = table_name
|
22
|
+
self.inheritance_column = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def columns
|
27
|
+
raw_model.column_names.map(&:to_sym)
|
28
|
+
end
|
29
|
+
|
30
|
+
def primary_keys
|
31
|
+
db.execute(<<~SQL).column_values(0).map(&:to_sym)
|
32
|
+
SELECT kcu.column_name
|
33
|
+
FROM INFORMATION_SCHEMA.TABLES t
|
34
|
+
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc
|
35
|
+
ON tc.table_catalog = t.table_catalog
|
36
|
+
AND tc.table_schema = t.table_schema
|
37
|
+
AND tc.table_name = t.table_name
|
38
|
+
AND tc.constraint_type = 'PRIMARY KEY'
|
39
|
+
LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
|
40
|
+
ON kcu.table_catalog = tc.table_catalog
|
41
|
+
AND kcu.table_schema = tc.table_schema
|
42
|
+
AND kcu.table_name = tc.table_name
|
43
|
+
AND kcu.constraint_name = tc.constraint_name
|
44
|
+
WHERE
|
45
|
+
t.table_schema NOT IN ('pg_catalog', 'information_schema')
|
46
|
+
AND t.table_schema = '#{table_info[:schema]}'
|
47
|
+
AND t.table_name = '#{table_info[:name]}'
|
48
|
+
ORDER BY
|
49
|
+
kcu.ordinal_position
|
50
|
+
SQL
|
51
|
+
end
|
52
|
+
|
53
|
+
def upsert(data:, target_keys:, version_key:, first_sync_time_key:, default_values:)
|
54
|
+
data = Array.wrap(data)
|
55
|
+
|
56
|
+
transaction do
|
57
|
+
data.map do |datum|
|
58
|
+
conditions = datum.select { |k| target_keys.include?(k) }
|
59
|
+
|
60
|
+
row = raw_model.lock("FOR NO KEY UPDATE").find_by(conditions)
|
61
|
+
if row
|
62
|
+
next if datum[version_key] <= row[version_key]
|
63
|
+
|
64
|
+
row.update!(datum)
|
65
|
+
else
|
66
|
+
create_data = datum.merge(default_values)
|
67
|
+
create_data[first_sync_time_key] = Time.current if first_sync_time_key
|
68
|
+
row = raw_model.create!(create_data)
|
69
|
+
end
|
70
|
+
|
71
|
+
row_to_hash(row)
|
72
|
+
end.compact
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def destroy(data)
|
77
|
+
transaction do
|
78
|
+
row = raw_model.lock("FOR UPDATE").find_by(data)&.destroy!
|
79
|
+
[row_to_hash(row)]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def transaction(&block)
|
84
|
+
::ActiveRecord::Base.transaction(&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def after_commit(&block)
|
88
|
+
db.add_transaction_record(AfterCommitWrap.new(&block))
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
attr_reader :raw_model
|
94
|
+
|
95
|
+
def table_info
|
96
|
+
keys = raw_model.table_name.split(".")
|
97
|
+
name = keys[-1]
|
98
|
+
schema = keys[-2] || "public"
|
99
|
+
{ schema: schema, name: name }
|
100
|
+
end
|
101
|
+
|
102
|
+
def db
|
103
|
+
@raw_model.connection
|
104
|
+
end
|
105
|
+
|
106
|
+
def row_to_hash(row)
|
107
|
+
row.attributes.each_with_object({}) { |(k, v), o| o[k.to_sym] = v }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::Model
|
4
|
+
class Sequel
|
5
|
+
def initialize(table_name)
|
6
|
+
@raw_model = Class.new(::Sequel::Model(table_name)).tap(&:unrestrict_primary_key)
|
7
|
+
end
|
8
|
+
|
9
|
+
def columns
|
10
|
+
dataset.columns
|
11
|
+
end
|
12
|
+
|
13
|
+
def primary_keys
|
14
|
+
[raw_model.primary_key].flatten
|
15
|
+
end
|
16
|
+
|
17
|
+
def upsert(data:, target_keys:, version_key:, first_sync_time_key:, default_values:)
|
18
|
+
data = Array.wrap(data)
|
19
|
+
qualified_version = ::Sequel.qualify(table_name, version_key)
|
20
|
+
version_condition = ::Sequel.function(:coalesce, qualified_version, 0) <
|
21
|
+
::Sequel.qualify(:excluded, version_key)
|
22
|
+
|
23
|
+
upd_spec = update_spec(data.first.keys - target_keys)
|
24
|
+
data.map! { |d| d.merge(default_values) }
|
25
|
+
|
26
|
+
insert_data = type_cast(data)
|
27
|
+
if first_sync_time_key
|
28
|
+
insert_data.each { |datum| datum[first_sync_time_key] = Time.current }
|
29
|
+
end
|
30
|
+
|
31
|
+
dataset.returning
|
32
|
+
.insert_conflict(
|
33
|
+
target: target_keys,
|
34
|
+
update: upd_spec,
|
35
|
+
update_where: version_condition,
|
36
|
+
)
|
37
|
+
.multi_insert(insert_data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy(data)
|
41
|
+
dataset.returning.where(data).delete
|
42
|
+
end
|
43
|
+
|
44
|
+
def transaction(&block)
|
45
|
+
db.transaction(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def after_commit(&block)
|
49
|
+
db.after_commit(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
attr_reader :raw_model
|
55
|
+
|
56
|
+
def table_name
|
57
|
+
raw_model.table_name
|
58
|
+
end
|
59
|
+
|
60
|
+
def dataset
|
61
|
+
raw_model.dataset
|
62
|
+
end
|
63
|
+
|
64
|
+
def db
|
65
|
+
dataset.db
|
66
|
+
end
|
67
|
+
|
68
|
+
def type_cast(data)
|
69
|
+
data.map { |d| raw_model.new(d).values.keep_if { |k| d.key?(k) } }
|
70
|
+
end
|
71
|
+
|
72
|
+
def update_spec(keys)
|
73
|
+
keys.map { |key| [key, ::Sequel[:excluded][key]] }.to_h
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::ORMAdapter
|
4
|
+
module ActiveRecord
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def model
|
8
|
+
::TableSync::Model::ActiveRecord
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(dataset, conditions)
|
12
|
+
dataset.find_by(conditions)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attributes(object)
|
16
|
+
object.attributes
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_sync(klass, **opts)
|
20
|
+
klass.instance_exec do
|
21
|
+
{ create: :created, update: :updated, destroy: :destroyed }.each do |event, state|
|
22
|
+
after_commit(on: event, **opts) do
|
23
|
+
TableSync::Publisher.new(self.class.name, attributes, state: state).publish
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TableSync::ORMAdapter
|
4
|
+
module Sequel
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def model
|
8
|
+
::TableSync::Model::Sequel
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(dataset, conditions)
|
12
|
+
dataset.find(conditions)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attributes(object)
|
16
|
+
object.values
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_sync(klass, **opts)
|
20
|
+
if_predicate = to_predicate(opts.delete(:if), true)
|
21
|
+
unless_predicate = to_predicate(opts.delete(:unless), false)
|
22
|
+
raise "Only :if and :unless options are currently supported for Sequel hooks" if opts.any?
|
23
|
+
|
24
|
+
{ create: :created, update: :updated }.each do |event, state|
|
25
|
+
klass.send(:define_method, :"after_#{event}") do
|
26
|
+
if instance_eval(&if_predicate) && !instance_eval(&unless_predicate)
|
27
|
+
db.after_commit do
|
28
|
+
TableSync::Publisher.new(self.class.name, values, state: state).publish
|
29
|
+
end
|
30
|
+
end
|
31
|
+
super()
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
klass.send(:define_method, :after_destroy) do
|
36
|
+
# publish anyway
|
37
|
+
db.after_commit do
|
38
|
+
TableSync::Publisher.new(self.class.name, values, state: :destroyed).publish
|
39
|
+
end
|
40
|
+
super()
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_predicate(val, default)
|
45
|
+
return val.to_proc if val.respond_to?(:to_proc)
|
46
|
+
|
47
|
+
-> (*) { default }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TableSync::Publisher < TableSync::BasePublisher
|
4
|
+
DEBOUNCE_TIME = 1.minute
|
5
|
+
|
6
|
+
# 'original_attributes' are not published, they are used to resolve the routing key
|
7
|
+
def initialize(object_class, original_attributes, destroyed: nil, confirm: true, state: :updated)
|
8
|
+
@object_class = object_class.constantize
|
9
|
+
@original_attributes = filter_safe_for_serialization(original_attributes.deep_symbolize_keys)
|
10
|
+
@confirm = confirm
|
11
|
+
|
12
|
+
if destroyed.nil?
|
13
|
+
@state = validate_state(state)
|
14
|
+
else
|
15
|
+
# TODO Legacy job support, remove
|
16
|
+
@state = destroyed ? :destroyed : :updated
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def publish
|
21
|
+
return enqueue_job if destroyed?
|
22
|
+
|
23
|
+
sync_time = Rails.cache.read(cache_key) || current_time - DEBOUNCE_TIME - 1.second
|
24
|
+
return if sync_time > current_time
|
25
|
+
|
26
|
+
next_sync_time = sync_time + DEBOUNCE_TIME
|
27
|
+
next_sync_time <= current_time ? enqueue_job : enqueue_job(next_sync_time)
|
28
|
+
end
|
29
|
+
|
30
|
+
def publish_now
|
31
|
+
# Update request and object does not exist -> skip publishing
|
32
|
+
return if !object && !destroyed?
|
33
|
+
|
34
|
+
Rabbit.publish(params)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :original_attributes
|
40
|
+
attr_reader :state
|
41
|
+
|
42
|
+
def attrs_for_callables
|
43
|
+
original_attributes
|
44
|
+
end
|
45
|
+
|
46
|
+
def attrs_for_routing_key
|
47
|
+
return object.attrs_for_routing_key if attrs_for_routing_key_defined?
|
48
|
+
attrs_for_callables
|
49
|
+
end
|
50
|
+
|
51
|
+
def attrs_for_metadata
|
52
|
+
return object.attrs_for_metadata if attrs_for_metadata_defined?
|
53
|
+
attrs_for_callables
|
54
|
+
end
|
55
|
+
|
56
|
+
def job_callable
|
57
|
+
TableSync.publishing_job_class_callable
|
58
|
+
end
|
59
|
+
|
60
|
+
def job_callable_error_message
|
61
|
+
"Can't publish, set TableSync.publishing_job_class_callable"
|
62
|
+
end
|
63
|
+
|
64
|
+
def enqueue_job(perform_at = current_time)
|
65
|
+
job = job_class.set(wait_until: perform_at)
|
66
|
+
job.perform_later(object_class.name, original_attributes, state: state.to_s, confirm: confirm?)
|
67
|
+
Rails.cache.write(cache_key, perform_at)
|
68
|
+
end
|
69
|
+
|
70
|
+
def routing_key
|
71
|
+
resolve_routing_key
|
72
|
+
end
|
73
|
+
|
74
|
+
def publishing_data
|
75
|
+
{
|
76
|
+
**super,
|
77
|
+
event: (destroyed? ? :destroy : :update),
|
78
|
+
metadata: { created: created? },
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def attributes_for_sync
|
83
|
+
if destroyed?
|
84
|
+
if object_class.respond_to?(:table_sync_destroy_attributes)
|
85
|
+
object_class.table_sync_destroy_attributes(original_attributes)
|
86
|
+
else
|
87
|
+
needle
|
88
|
+
end
|
89
|
+
elsif attributes_for_sync_defined?
|
90
|
+
object.attributes_for_sync
|
91
|
+
else
|
92
|
+
TableSync.orm.attributes(object)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
memoize def object
|
97
|
+
TableSync.orm.find(object_class, needle)
|
98
|
+
end
|
99
|
+
|
100
|
+
def needle
|
101
|
+
original_attributes.slice(*primary_keys)
|
102
|
+
end
|
103
|
+
|
104
|
+
def cache_key
|
105
|
+
"#{object_class}/#{needle}_table_sync_time".delete(" ")
|
106
|
+
end
|
107
|
+
|
108
|
+
def destroyed?
|
109
|
+
state == :destroyed
|
110
|
+
end
|
111
|
+
|
112
|
+
def created?
|
113
|
+
state == :created
|
114
|
+
end
|
115
|
+
|
116
|
+
def validate_state(state)
|
117
|
+
if %i[created updated destroyed].include?(state&.to_sym)
|
118
|
+
state.to_sym
|
119
|
+
else
|
120
|
+
raise "Unknown state: #{state.inspect}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TableSync::ReceivingHandler < Rabbit::EventHandler
|
4
|
+
extend TableSync::DSL
|
5
|
+
|
6
|
+
attribute :event
|
7
|
+
attribute :model
|
8
|
+
attribute :version
|
9
|
+
|
10
|
+
def call
|
11
|
+
raise TableSync::UndefinedConfig.new(model) if configs.blank?
|
12
|
+
|
13
|
+
configs.each do |config|
|
14
|
+
next unless config.allow_event?(event)
|
15
|
+
|
16
|
+
data = processed_data(config)
|
17
|
+
next if data.empty?
|
18
|
+
|
19
|
+
case event
|
20
|
+
when :update
|
21
|
+
config.model.transaction do
|
22
|
+
config.update(data)
|
23
|
+
end
|
24
|
+
when :destroy
|
25
|
+
config.destroy(data.values.first)
|
26
|
+
else
|
27
|
+
raise "Unknown event: #{event}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def data=(data)
|
35
|
+
@data = data[:attributes]
|
36
|
+
end
|
37
|
+
|
38
|
+
def event=(name)
|
39
|
+
super(name.to_sym)
|
40
|
+
end
|
41
|
+
|
42
|
+
def model=(name)
|
43
|
+
super(name.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def configs
|
47
|
+
@configs ||= self.class.configs[model]
|
48
|
+
&.map { |c| ::TableSync::ConfigDecorator.new(c, self) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def processed_data(config)
|
52
|
+
parts = config.partitions&.transform_keys { |k| config.model.class.new(k) } ||
|
53
|
+
{ config.model => Array.wrap(data) }
|
54
|
+
|
55
|
+
parts.transform_values! do |data_part|
|
56
|
+
data_part.map do |row|
|
57
|
+
original_row_for_data = row.dup
|
58
|
+
row = row.dup
|
59
|
+
|
60
|
+
config.mapping_overrides.each do |before, after|
|
61
|
+
row[after] = row.delete(before)
|
62
|
+
end
|
63
|
+
|
64
|
+
only = config.only
|
65
|
+
row, missed = row.partition { |key, _| key.in?(only) }.map(&:to_h)
|
66
|
+
|
67
|
+
row.deep_merge!(config.rest_key => missed) if config.rest_key
|
68
|
+
row[config.version_key] = version
|
69
|
+
|
70
|
+
row.merge!(config.additional_data(original_row_for_data))
|
71
|
+
|
72
|
+
row unless config.skip(original_row_for_data)
|
73
|
+
end.compact.presence
|
74
|
+
end.compact
|
75
|
+
end
|
76
|
+
end
|
data/lib/table_sync/version.rb
CHANGED
data/lib/table_sync.rb
CHANGED
@@ -1,5 +1,55 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "memery"
|
4
|
+
require "rabbit_messaging"
|
5
|
+
require "rabbit/event_handler" # NOTE: from rabbit_messaging"
|
6
|
+
require "active_support/core_ext/object/blank"
|
7
|
+
require "active_support/core_ext/numeric/time"
|
8
|
+
|
3
9
|
module TableSync
|
4
|
-
|
10
|
+
require_relative "./table_sync/version"
|
11
|
+
require_relative "./table_sync/errors"
|
12
|
+
require_relative "./table_sync/event_actions"
|
13
|
+
require_relative "./table_sync/config"
|
14
|
+
require_relative "./table_sync/config/callback_registry"
|
15
|
+
require_relative "./table_sync/config_decorator"
|
16
|
+
require_relative "./table_sync/dsl"
|
17
|
+
require_relative "./table_sync/receiving_handler"
|
18
|
+
require_relative "./table_sync/base_publisher"
|
19
|
+
require_relative "./table_sync/publisher"
|
20
|
+
require_relative "./table_sync/batch_publisher"
|
21
|
+
require_relative "./table_sync/orm_adapter/active_record"
|
22
|
+
require_relative "./table_sync/orm_adapter/sequel"
|
23
|
+
require_relative "./table_sync/model/active_record"
|
24
|
+
require_relative "./table_sync/model/sequel"
|
25
|
+
|
26
|
+
class << self
|
27
|
+
include Memery
|
28
|
+
|
29
|
+
attr_accessor :publishing_job_class_callable
|
30
|
+
attr_accessor :batch_publishing_job_class_callable
|
31
|
+
attr_accessor :routing_key_callable
|
32
|
+
attr_accessor :exchange_name
|
33
|
+
attr_accessor :routing_metadata_callable
|
34
|
+
|
35
|
+
def sync(*args)
|
36
|
+
orm.setup_sync(*args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def orm=(val)
|
40
|
+
clear_memery_cache!
|
41
|
+
@orm = val
|
42
|
+
end
|
43
|
+
|
44
|
+
memoize def orm
|
45
|
+
case @orm
|
46
|
+
when :active_record
|
47
|
+
ORMAdapter::ActiveRecord
|
48
|
+
when :sequel
|
49
|
+
ORMAdapter::Sequel
|
50
|
+
else
|
51
|
+
raise "ORM not supported: #{@orm.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
5
55
|
end
|
data/table_sync.gemspec
CHANGED
@@ -11,8 +11,10 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.version = TableSync::VERSION
|
12
12
|
spec.authors = ["Umbrellio"]
|
13
13
|
spec.email = ["oss@umbrellio.biz"]
|
14
|
-
spec.summary = "
|
15
|
-
|
14
|
+
spec.summary = "DB Table synchronization between microservices " \
|
15
|
+
"based on Model's event system and RabbitMQ messaging"
|
16
|
+
spec.description = "DB Table synchronization between microservices " \
|
17
|
+
"based on Model's event system and RabbitMQ messaging"
|
16
18
|
spec.homepage = "https://github.com/umbrellio/table_sync"
|
17
19
|
spec.license = "MIT"
|
18
20
|
|
@@ -24,12 +26,23 @@ Gem::Specification.new do |spec|
|
|
24
26
|
f.match(%r{^(test|spec|features)/})
|
25
27
|
end
|
26
28
|
|
29
|
+
spec.add_runtime_dependency "memery"
|
30
|
+
spec.add_runtime_dependency "rabbit_messaging", "~> 0.3"
|
31
|
+
spec.add_runtime_dependency "rails"
|
32
|
+
|
27
33
|
spec.add_development_dependency "coveralls", "~> 0.8"
|
28
34
|
spec.add_development_dependency "rspec", "~> 3.8"
|
29
35
|
spec.add_development_dependency "rubocop-config-umbrellio", "~> 0.70"
|
30
36
|
spec.add_development_dependency "simplecov", "~> 0.16"
|
31
37
|
|
38
|
+
spec.add_development_dependency "activejob"
|
39
|
+
spec.add_development_dependency "activerecord"
|
40
|
+
spec.add_development_dependency "pg", "~> 0.18"
|
41
|
+
spec.add_development_dependency "sequel"
|
42
|
+
spec.add_development_dependency "timecop"
|
43
|
+
|
32
44
|
spec.add_development_dependency "bundler"
|
45
|
+
spec.add_development_dependency "bundler-audit"
|
33
46
|
spec.add_development_dependency "pry"
|
34
47
|
spec.add_development_dependency "rake"
|
35
48
|
end
|