Class: ActiveSanction::Storage::Meta
- Inherits:
-
Object
- Object
- ActiveSanction::Storage::Meta
- Extended by:
- T::Sig
- Defined in:
- lib/active_sanction/storage/meta.rb
Overview
What a store can say about a stored list without reading the list.
store.(:ofac_sdn)
# => #<ActiveSanction::Storage::Meta ofac_sdn 19015 entities sha256:1f3b... 3h old>
Every field here is small and every field a snapshot holds besides these is not: OFAC's is 19,015 entities and tens of megabytes of JSON. The questions an operator and a sync actually ask -- when was this last fetched, how old is it now, how many records are on it, is it still the version we screened against in January -- are all answerable from these six values, so they are worth being able to answer separately.
That separation is what makes the two things downstream cheap:
sourcesin the CLI (#36) and the per-source summary in sync orchestration (#34) print an age per list. A store that had to deserialize every snapshot to print a table would make the cheapest command in the library the slowest.- Storage::FileSystem (#24) writes exactly this beside each snapshot as
meta.json, so#to_his that file's contents and.from_hreads it back. Base derives a Meta from the snapshot for adapters that have nothing cheaper; an adapter with a sidecar or a metadata row overrides#snapshot_metaand never opens the list.
checksum is the one that outlives the rest. A match result cites it
(#33), so "which list version cleared this customer" is answered by
comparing a stored meta against a checksum in an audit record -- without
loading either list.
Instances are frozen on construction and compare by value.
Instance Attribute Summary collapse
-
#checksum ⇒ String
readonly
The one field that outlives the rest: a match result cites it, so a stored meta answers "which list version cleared this customer".
- #fetched_at ⇒ Time readonly
- #record_count ⇒ Integer readonly
- #schema_version ⇒ Integer readonly
- #source ⇒ Symbol readonly
- #source_version ⇒ String? readonly
Class Method Summary collapse
-
.from_h(hash) ⇒ T.attached_class
Rebuilds from #to_h output, accepting string keys so a sidecar survives the round-trip through JSON.
-
.from_snapshot(snapshot) ⇒ T.attached_class
The metadata of a snapshot already in hand.
Instance Method Summary collapse
- #==(other) ⇒ Boolean (also: #eql?)
-
#age(now = Time.now) ⇒ Integer
How long ago this list was fetched, in seconds.
- #hash ⇒ Integer
- #initialize(source:, fetched_at:, checksum:, record_count:, schema_version: Snapshot::SCHEMA_VERSION, source_version: nil) ⇒ void constructor
- #inspect ⇒ String
-
#same_content?(other) ⇒ Boolean
Whether this is the same list content as another meta, or as a snapshot about to be written.
- #to_h ⇒ Hash{Symbol => T.untyped}
Constructor Details
#initialize(source:, fetched_at:, checksum:, record_count:, schema_version: Snapshot::SCHEMA_VERSION, source_version: nil) ⇒ void
98 99 100 101 102 103 104 105 106 107 |
# File 'lib/active_sanction/storage/meta.rb', line 98 def initialize(source:, fetched_at:, checksum:, record_count:, schema_version: Snapshot::SCHEMA_VERSION, source_version: nil) @source = T.let(symbol!(:source, source), Symbol) @fetched_at = T.let(time!(fetched_at), Time) @checksum = T.let(string!(:checksum, checksum), String) @record_count = T.let(count!(record_count), Integer) @schema_version = T.let(Integer(schema_version), Integer) @source_version = T.let(source_version.nil? ? nil : -source_version.to_s, T.nilable(String)) freeze end |
Instance Attribute Details
#checksum ⇒ String (readonly)
The one field that outlives the rest: a match result cites it, so a stored meta answers "which list version cleared this customer".
60 61 62 |
# File 'lib/active_sanction/storage/meta.rb', line 60 def checksum @checksum end |
#fetched_at ⇒ Time (readonly)
55 56 57 |
# File 'lib/active_sanction/storage/meta.rb', line 55 def fetched_at @fetched_at end |
#record_count ⇒ Integer (readonly)
63 64 65 |
# File 'lib/active_sanction/storage/meta.rb', line 63 def record_count @record_count end |
#schema_version ⇒ Integer (readonly)
66 67 68 |
# File 'lib/active_sanction/storage/meta.rb', line 66 def schema_version @schema_version end |
#source ⇒ Symbol (readonly)
52 53 54 |
# File 'lib/active_sanction/storage/meta.rb', line 52 def source @source end |
#source_version ⇒ String? (readonly)
69 70 71 |
# File 'lib/active_sanction/storage/meta.rb', line 69 def source_version @source_version end |
Class Method Details
.from_h(hash) ⇒ T.attached_class
Rebuilds from #to_h output, accepting string keys so a sidecar survives the round-trip through JSON.
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/active_sanction/storage/meta.rb', line 83 def self.from_h(hash) attributes = hash.to_h.transform_keys(&:to_sym) unknown = attributes.keys - MEMBERS raise InvalidArgument, "unknown Meta attribute(s): #{unknown.join(", ")}" if unknown.any? # `new(**hash)` past required keyword parameters is one of the few # things Sorbet cannot check statically. #initialize validates what # arrives, which is where a bad sidecar is caught. T.unsafe(self).new(**attributes) end |
.from_snapshot(snapshot) ⇒ T.attached_class
The metadata of a snapshot already in hand. What a store that keeps no
separate record of it answers #snapshot_meta with.
74 75 76 77 78 |
# File 'lib/active_sanction/storage/meta.rb', line 74 def self.from_snapshot(snapshot) new(source: snapshot.source, fetched_at: snapshot.fetched_at, checksum: snapshot.checksum, record_count: snapshot.record_count, schema_version: snapshot.schema_version, source_version: snapshot.source_version) end |
Instance Method Details
#==(other) ⇒ Boolean Also known as: eql?
153 154 155 156 157 |
# File 'lib/active_sanction/storage/meta.rb', line 153 def ==(other) return false unless other.instance_of?(self.class) to_h == other.to_h end |
#age(now = Time.now) ⇒ Integer
How long ago this list was fetched, in seconds. Sync (#34) reports it per source: a list that failed to refresh keeps its previous snapshot, which is the right call and only safe if the age of what is being screened against is visible.
Accurate to a second, and never fewer than the seconds that have
passed. fetched_at is stored to the second -- Snapshot#time!
truncates it, so that a stored snapshot reloads equal to the one that
was written -- so the instant of the fetch is known only to lie
somewhere inside the second this names. What comes back is therefore
the largest age consistent with what was recorded, which is the
direction a staleness measure has to err in: an answer that is at most
a second pessimistic is a report that nobody acts on, and one that is
optimistic is a list being screened against that is older than it
claims.
The practical consequence, and the reason it is written down: a list fetched microseconds ago reports 0 or 1, according to whether the run crossed a second boundary on its way here. Both mean "just fetched" -- see Sync::Result#age_in_words, which is what a summary table shows -- and nothing should assert on the exact number of a fresh sync, because that is a fact about the clock rather than about this library.
132 |
# File 'lib/active_sanction/storage/meta.rb', line 132 def age(now = Time.now) = now.to_i - fetched_at.to_i |
#hash ⇒ Integer
161 |
# File 'lib/active_sanction/storage/meta.rb', line 161 def hash = [self.class, to_h].hash |
#inspect ⇒ String
164 165 166 |
# File 'lib/active_sanction/storage/meta.rb', line 164 def inspect "#<#{self.class} #{source} #{record_count} entities #{checksum} fetched_at=#{fetched_at.iso8601}>" end |
#same_content?(other) ⇒ Boolean
Whether this is the same list content as another meta, or as a snapshot about to be written. Ignores when either was fetched, because a refetch of an unchanged list is not a new version of it.
138 |
# File 'lib/active_sanction/storage/meta.rb', line 138 def same_content?(other) = !other.nil? && other.checksum == checksum |
#to_h ⇒ Hash{Symbol => T.untyped}
141 142 143 144 145 146 147 148 149 150 |
# File 'lib/active_sanction/storage/meta.rb', line 141 def to_h { source: source, fetched_at: fetched_at.iso8601, checksum: checksum, record_count: record_count, schema_version: schema_version, source_version: source_version } end |