Class: ExampleRecordDestroyer

Inherits:
Struct
  • Object
show all
Defined in:
lib/sq/dbsync/example_record_destroyer.rb

Overview

An example class that can reconstruct deletes from an audit log. We use the audit table as a proxy, though this is not written to in the same transaction as the destroy so it may arrive some time later.

A faux-table is added to the sync times metadata “record_deletes” to make this process resilient to replication failures in either table.

This is an example implementation, you will need to modify it to suit your purposes.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#audit_tableObject

Returns the value of attribute audit_table

Returns:

  • (Object)

    the current value of audit_table



10
11
12
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 10

def audit_table
  @audit_table
end

#dbObject

Returns the value of attribute db

Returns:

  • (Object)

    the current value of db



10
11
12
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 10

def db
  @db
end

#other_tableObject

Returns the value of attribute other_table

Returns:

  • (Object)

    the current value of other_table



10
11
12
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 10

def other_table
  @other_table
end

#registryObject

Returns the value of attribute registry

Returns:

  • (Object)

    the current value of registry



10
11
12
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 10

def registry
  @registry
end

Class Method Details

.run(*args) ⇒ Object



14
15
16
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 14

def self.run(*args)
  new(*args).run
end

Instance Method Details

#extract_deletes(audit_logs) ⇒ Object



41
42
43
44
45
46
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 41

def extract_deletes(audit_logs)
  audit_logs.
    group_by {|x| x[:target_id] }.
    select {|_, xs| last_value_set(xs) == 'false' }.
    keys
end

#last_sync_time(table) ⇒ Object



63
64
65
66
67
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 63

def last_sync_time(table)
  record = registry.get(table)

  (record || {}).fetch(:last_synced_at, nil)
end

#last_value_set(xs) ⇒ Object

updated_at is not distinct, so use id column as a tie-break.



70
71
72
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 70

def last_value_set(xs)
  xs.sort_by {|y| [y[:updated_at], y[:id]] }.last[:new_value]
end

#meta_tableObject



74
75
76
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 74

def meta_table
  :"#{other_table}_deletes"
end

#runObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 18

def run
  max = last_sync_time(audit_table)

  if max
    user_ids = extract_deletes(unprocessed_audit_logs(max))

    # This conditional should not be required, but MySQL cannot optimize the
    # impossible where clause correctly and instead scans the table.
    if user_ids.any?
      db[other_table].filter(
        user_id: user_ids
      ).delete
    end

    # last_row_at calculation isn't correct but we don't use it.
    registry.set!(meta_table,
      last_synced_at:       max,
      last_row_at:          max,
      last_batch_synced_at: nil
    )
  end
end

#unprocessed_audit_logs(max) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/sq/dbsync/example_record_destroyer.rb', line 48

def unprocessed_audit_logs(max)

  query = db[audit_table].
    select(:target_id, :new_value, :updated_at).
    filter('updated_at <= ?', max).
    filter(action_name: %w(delete))

  min = last_sync_time(meta_table)
  if min
    query = query.filter('updated_at > ?', min)
  end

  query.to_a
end