Class: Draisine::Auditor

Inherits:
Object
  • Object
show all
Defined in:
lib/draisine/auditor.rb,
lib/draisine/auditor/result.rb

Defined Under Namespace

Classes: Discrepancy, Result

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(partition) ⇒ Auditor

Returns a new instance of Auditor.



25
26
27
28
29
30
# File 'lib/draisine/auditor.rb', line 25

def initialize(partition)
  @partition = partition
  @model_class = partition.model_class
  @start_date = partition.start_date
  @end_date = partition.end_date
end

Instance Attribute Details

#end_dateObject (readonly)

Returns the value of attribute end_date.



24
25
26
# File 'lib/draisine/auditor.rb', line 24

def end_date
  @end_date
end

#model_classObject (readonly)

Returns the value of attribute model_class.



24
25
26
# File 'lib/draisine/auditor.rb', line 24

def model_class
  @model_class
end

#partitionObject (readonly)

Returns the value of attribute partition.



24
25
26
# File 'lib/draisine/auditor.rb', line 24

def partition
  @partition
end

#resultObject (readonly)

Returns the value of attribute result.



24
25
26
# File 'lib/draisine/auditor.rb', line 24

def result
  @result
end

#start_dateObject (readonly)

Returns the value of attribute start_date.



24
25
26
# File 'lib/draisine/auditor.rb', line 24

def start_date
  @start_date
end

Class Method Details

.partition(model_class:, start_date:, end_date:, partition_size: 100, mechanism: :default) ⇒ Object



15
16
17
18
19
20
21
22
# File 'lib/draisine/auditor.rb', line 15

def self.partition(model_class:, start_date:, end_date:, partition_size: 100, mechanism: :default)
  Partitioner.partition(
    model_class: model_class,
    start_date: start_date,
    end_date: end_date,
    partition_size: partition_size,
    mechanism: mechanism)
end

.run(model_class:, start_date: Time.current.beginning_of_day, end_date: Time.current, mechanism: :default) ⇒ Object



5
6
7
8
9
# File 'lib/draisine/auditor.rb', line 5

def self.run(model_class:, start_date: Time.current.beginning_of_day, end_date: Time.current, mechanism: :default)
  # TODO: instead of using one huge partition, combine multiple results into one
  partitions = partition(model_class: model_class, start_date: start_date, end_date: end_date, partition_size: 10**12, mechanism: mechanism)
  run_partition(partitions.first)
end

.run_partition(partition) ⇒ Object



11
12
13
# File 'lib/draisine/auditor.rb', line 11

def self.run_partition(partition)
  new(partition).run
end

Instance Method Details

#check_deletesObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/draisine/auditor.rb', line 60

def check_deletes
  return unless partition.deleted_ids.present?

  ghost_models = model_class.where(salesforce_id: partition.deleted_ids).all
  ghost_models.each do |ghost_model|
    result.discrepancy(
      type: :remote_delete_kept_locally,
      salesforce_type: salesforce_object_name,
      salesforce_id: ghost_model.salesforce_id,
      local_id: ghost_model.id,
      local_type: ghost_model.class.name,
      local_attributes: ghost_model.attributes)
  end
end

#check_modificationsObject



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/draisine/auditor.rb', line 75

def check_modifications
  updated_ids = partition.updated_ids
  return unless updated_ids.present?

  local_records = model_class.where(salesforce_id: updated_ids).to_a
  remote_records = client.fetch_multiple(salesforce_object_name, updated_ids)

  local_records_map = build_map(local_records) {|record| record.salesforce_id }
  remote_records_map = build_map(remote_records) {|record| record.Id }

  missing_ids = updated_ids - local_records_map.keys
  missing_ids.each do |id|
    result.discrepancy(
      type: :remote_record_missing_locally,
      salesforce_type: salesforce_object_name,
      salesforce_id: id,
      remote_attributes: remote_records_map.fetch(id, {}))
  end

  attr_list = model_class.salesforce_audited_attributes
  local_records_map.each do |salesforce_id, local_record|
    remote_record = remote_records_map[salesforce_id]
    next unless remote_record
    conflict_detector = ConflictDetector.new(local_record, remote_record, attr_list)

    if conflict_detector.conflict?
      result.discrepancy(
        type: :mismatching_records,
        salesforce_type: salesforce_object_name,
        salesforce_id: salesforce_id,
        local_id: local_record.id,
        local_type: local_record.class.name,
        local_attributes: local_record.salesforce_attributes,
        remote_attributes: remote_record.attributes,
        diff_keys: conflict_detector.diff.diff_keys)
    end
  end
end

#check_unpersisted_recordsObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/draisine/auditor.rb', line 45

def check_unpersisted_records
  return unless partition.unpersisted_ids.present?

  bad_records = model_class.where(id: partition.unpersisted_ids)
  bad_records.each do |record|
    result.discrepancy(
      type: :local_record_without_salesforce_id,
      salesforce_type: salesforce_object_name,
      salesforce_id: nil,
      local_id: record.id,
      local_type: record.class.name,
      local_attributes: record.attributes)
  end
end

#runObject



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/draisine/auditor.rb', line 32

def run
  @result = Result.new

  check_unpersisted_records
  check_deletes
  check_modifications

  result.calculate_result!
rescue => e
  result.error!(e)
  raise
end