Class: DeprecationCollector::Storage::ActiveRecord

Inherits:
Base
  • Object
show all
Defined in:
lib/deprecation_collector/storage/active_record.rb

Overview

NB: this will not work in tests because of transactions, and may be affected by transactions of the app TODO: use separate db connection to mitigate this

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#disable, #enable, #enabled?, #support_disabling?

Constructor Details

#initialize(model: nil, mutex: nil, count: false, write_interval: 900, write_interval_jitter: 60, key_prefix: nil) ⇒ ActiveRecord

Returns a new instance of ActiveRecord.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/deprecation_collector/storage/active_record.rb', line 8

def initialize(model: nil, mutex: nil, count: false, write_interval: 900, write_interval_jitter: 60,
               key_prefix: nil)
  super
  raise "key prefix not supported in AR" if key_prefix && key_prefix != "deprecations"

  self.model = model if model
  @last_write_time = current_time
  @count = count
  @write_interval = write_interval
  @write_interval_jitter = write_interval_jitter
  # on cruby hash itself is threadsafe, but we need to prevent races
  @deprecations_mutex = mutex || Mutex.new
  @deprecations = {}
  @known_digests = Set.new
end

Instance Attribute Details

#key_prefix=(value) ⇒ Object (writeonly)

Sets the attribute key_prefix

Parameters:

  • value

    the value to set the attribute key_prefix to.



42
43
44
# File 'lib/deprecation_collector/storage/active_record.rb', line 42

def key_prefix=(value)
  @key_prefix = value
end

Instance Method Details

#cleanup(&_block) ⇒ Object



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/deprecation_collector/storage/active_record.rb', line 128

def cleanup(&_block)
  removed = total = 0

  model.find_in_batches do |batch|
    total += batch.size
    removed += delete(
      batch.select { |record| yield(record.data.deep_symbolize_keys) }.map(&:digest)
    )
  end
  "#{removed} removed, #{total - removed} left"
end

#clear(enable: false) ⇒ Object

rubocop:disable Lint/UnusedMethodArgument



52
53
54
55
56
# File 'lib/deprecation_collector/storage/active_record.rb', line 52

def clear(enable: false) # rubocop:disable Lint/UnusedMethodArgument
  model.delete_all
  @known_digests.clear
  @deprecations.clear
end

#delete(remove_digests) ⇒ Object



48
49
50
# File 'lib/deprecation_collector/storage/active_record.rb', line 48

def delete(remove_digests)
  model.where(digest: remove_digests).delete_all
end

#fetch_known_digestsObject



58
59
60
# File 'lib/deprecation_collector/storage/active_record.rb', line 58

def fetch_known_digests
  @known_digests.merge(model.pluck(:digest))
end

#flush(force: false) ⇒ Object



72
73
74
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
# File 'lib/deprecation_collector/storage/active_record.rb', line 72

def flush(force: false)
  return unless force || (current_time > @last_write_time + @write_interval)

  deprecations_to_flush = nil
  @deprecations_mutex.synchronize do
    deprecations_to_flush = @deprecations
    @deprecations = {}
    @last_write_time = current_time
    # checking in this section to prevent multiple parallel check requests
    return DeprecationCollector.instance.instance_variable_set(:@enabled, false) unless enabled?
  end

  # write_count_to_redis(deprecations_to_flush) if @count

  # make as few writes as possible, other workers may already have reported our warning
  fetch_known_digests
  deprecations_to_flush.reject! { |digest, _val| @known_digests.include?(digest) }
  return unless deprecations_to_flush.any?

  @known_digests.merge(deprecations_to_flush.keys)

  model.upsert_all(
    deprecations_to_flush.map do |key, deprecation|
      {
        digest: key, data: deprecation.as_json,
        created_at: timestamp_to_time(deprecation.first_timestamp),
        updated_at: timestamp_to_time(deprecation.first_timestamp)
      }
    end,
    unique_by: :digest # , update_only: %i[data updated_at] # rails 7
  )
end

#import(dump_hash) ⇒ Object



119
120
121
122
123
124
125
126
# File 'lib/deprecation_collector/storage/active_record.rb', line 119

def import(dump_hash)
  attrs = dump_hash.map do |key, deprecation|
    time = deprecation["first_timestamp"] || deprecation[:first_timestamp]
    time = time&.yield_self { |tme| timestamp_to_time(tme) } || current_time
    { digest: key, data: deprecation, created_at: time, updated_at: time }
  end
  model.upsert_all(attrs, unique_by: :digest) # , update_only: %i[data updated_at])
end

#modelObject



38
39
40
# File 'lib/deprecation_collector/storage/active_record.rb', line 38

def model
  @model ||= ::Deprecation
end

#model=(model) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/deprecation_collector/storage/active_record.rb', line 24

def model=(model)
  expected_class_methods = i[column_names where pluck delete_all upsert_all find_in_batches find_by]
  unless expected_class_methods.all? { |method_name| model.respond_to?(method_name) }
    raise ArgumentError, "model expected to be a AR-like class responding to #{expected_class_methods.join(', ')}"
  end

  expected_fields = %w[digest data notes created_at updated_at]
  unless expected_fields.all? { |column_name| model.column_names.include?(column_name) }
    raise ArgumentError, "model expected to be a AR-like class with fields #{expected_fields.join(', ')}"
  end

  @model = model
end

#read_eachObject



105
106
107
108
109
110
111
# File 'lib/deprecation_collector/storage/active_record.rb', line 105

def read_each
  model.find_in_batches do |batch| # this is find_each, but do not require it to be implemented
    batch.each do |record|
      yield(record.digest, record.data.to_json, record.data&.dig("count"), record.notes)
    end
  end
end

#read_one(digest) ⇒ Object



113
114
115
116
117
# File 'lib/deprecation_collector/storage/active_record.rb', line 113

def read_one(digest)
  return [nil] * 4 unless (record = model.find_by(digest: digest))

  [record.digest, record.data.to_json, record.data&.dig("count"), record.notes]
end

#store(deprecation) ⇒ Object



62
63
64
65
66
67
68
69
70
# File 'lib/deprecation_collector/storage/active_record.rb', line 62

def store(deprecation)
  fresh = !@deprecations.key?(deprecation.digest)
  @deprecations_mutex.synchronize do
    (@deprecations[deprecation.digest] ||= deprecation).touch
  end

  flush if current_time - @last_write_time > (@write_interval + rand(@write_interval_jitter))
  fresh
end

#unsent_deprecationsObject



44
45
46
# File 'lib/deprecation_collector/storage/active_record.rb', line 44

def unsent_deprecations
  @deprecations
end