Module: CloseEncounters
- Defined in:
- lib/close_encounters.rb,
lib/close_encounters/engine.rb,
lib/close_encounters/version.rb,
lib/close_encounters/middleware.rb,
lib/close_encounters/adapters/net_http.rb,
app/models/close_encounters/participant_event.rb,
app/models/close_encounters/application_record.rb,
app/models/close_encounters/participant_service.rb
Defined Under Namespace
Modules: Adapters Classes: ApplicationRecord, Configuration, Engine, Middleware, ParticipantEvent, ParticipantService
Constant Summary collapse
- VERIFIED_SIGNATURE =
"ok"- UNVERIFIED_SIGNATURE =
"unverified"- VERSION =
"0.3.0"
Class Method Summary collapse
-
.auto_contact! ⇒ Object
Enable automatic contact recording in the Rack Middleware.
-
.auto_contact? ⇒ Boolean
Determine if contacts with third party services should be recorded automatically using the Rack Middleware.
- .configuration ⇒ Object
- .configure {|configuration| ... } ⇒ Object
-
.contact(name, status:, response:) ⇒ Object
Record a contact with a third party service if the status has changed.
-
.deprecator ⇒ Object
Deprecator for CloseEncounters.
-
.ensure_service(name, connection_info: {}) ⇒ Object
Ensure that a participant service exists.
-
.record(name, response, adapter:, verifier: nil) ⇒ Object
Record the outcome of an HTTP request to a service straight from the client's response object, using an adapter to read the status and body.
-
.scan(name, status:, response:, verifier:) ⇒ Object
(also: verify)
Record a verification of a contact with a third party service where the verification is a callable which must also respond to to_s.
-
.signature_for(result) ⇒ Object
Normalize a verifier return value into a stable signature string.
-
.signature_for_event(event) ⇒ Object
Read the signature off a previously stored event, falling back to the legacy verified flag for events created before signatures existed.
-
.status(name) ⇒ Integer
Get the status of the most recent contact with a third party service.
-
.verify_scan_statuses ⇒ Object
Get the statuses that should be verified.
Class Method Details
.auto_contact! ⇒ Object
Enable automatic contact recording in the Rack Middleware
184 185 186 |
# File 'lib/close_encounters.rb', line 184 def auto_contact! configuration.auto_contact = true end |
.auto_contact? ⇒ Boolean
Determine if contacts with third party services should be recorded automatically using the Rack Middleware
Set the CLOSE_ENCOUNTERS_AUTO_CONTACT environment variable to enable this feature or call CloseEncounters.auto_contact! in an initializer
176 177 178 179 180 181 |
# File 'lib/close_encounters.rb', line 176 def auto_contact? # If auto_contact is explicitly set, use that value return configuration.auto_contact unless configuration.auto_contact.nil? # Otherwise check the environment variable !!ENV["CLOSE_ENCOUNTERS_AUTO_CONTACT"] end |
.configuration ⇒ Object
23 24 25 |
# File 'lib/close_encounters.rb', line 23 def self.configuration @configuration ||= Configuration.new end |
.configure {|configuration| ... } ⇒ Object
33 34 35 |
# File 'lib/close_encounters.rb', line 33 def self.configure yield(configuration) end |
.contact(name, status:, response:) ⇒ Object
Record a contact with a third party service if the status has changed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/close_encounters.rb', line 42 def contact(name, status:, response:) service = ParticipantService.find_by!(name:) status = status.to_i # Ensure status is always an integer created = nil # Use a transaction with a lock to prevent race conditions service.with_lock do unless service.events.newest.pick(:status) == status created = service.events.create!(status: status, response:) end end # Instrument after the transaction commits so subscribers see a persisted event. instrument(name, created) if created created end |
.deprecator ⇒ Object
Deprecator for CloseEncounters. Registered with the host app in the engine so warnings flow through the app's configured deprecation behavior.
29 30 31 |
# File 'lib/close_encounters.rb', line 29 def self.deprecator @deprecator ||= ActiveSupport::Deprecation.new("a future release", "CloseEncounters") end |
.ensure_service(name, connection_info: {}) ⇒ Object
Ensure that a participant service exists
203 204 205 206 207 |
# File 'lib/close_encounters.rb', line 203 def ensure_service(name, connection_info: {}) ParticipantService.find_or_create_by!(name: name) do |service| service.connection_info = connection_info unless service.connection_info.present? end end |
.record(name, response, adapter:, verifier: nil) ⇒ Object
Record the outcome of an HTTP request to a service straight from the client's response object, using an adapter to read the status and body.
An adapter is any object responding to #status(response) and #body(response); CloseEncounters::Adapters::NetHTTP ships for Net::HTTP. Delegates to #scan when a verifier is given, otherwise #contact.
CloseEncounters.record("SomeService", response, adapter: Adapters::NetHTTP)
CloseEncounters.record("SomeService", response, adapter: Adapters::NetHTTP, verifier: my_verifier)
73 74 75 76 77 78 79 80 81 |
# File 'lib/close_encounters.rb', line 73 def record(name, response, adapter:, verifier: nil) status = adapter.status(response) body = adapter.body(response) if verifier scan(name, status:, response: body, verifier:) else contact(name, status:, response: body) end end |
.scan(name, status:, response:, verifier:) ⇒ Object Also known as: verify
Record a verification of a contact with a third party service where the verification is a callable which must also respond to to_s.
The verifier may return:
* true / :ok — response is verified
* false / nil — response failed verification (generic)
* any other value — failed verification with a distinguishing
"signature" (e.g. "missing:user.email").
Stable signatures let scan suppress repeated
identical failures while still recording when
the failure mode changes meaningfully.
Creates a new event if:
- The status has changed from the last recorded status, OR
- The status is in the verify_scan list AND the verification signature differs from the last recorded signature.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/close_encounters.rb', line 123 def scan(name, status:, response:, verifier:) service = ParticipantService.find_by!(name:) status = status.to_i # Ensure status is always an integer created = nil service.with_lock do last_event = service.events.newest.first last_status = last_event&.status signature = signature_for(verifier.call(response)) verified = (signature == VERIFIED_SIGNATURE) last_signature = signature_for_event(last_event) = {verified:, signature:, verification: verifier.to_s} if last_status != status created = service.events.create!(status:, response:, metadata:) elsif verify_scan_statuses.include?(status) && last_signature != signature created = service.events.create!(status:, response:, metadata:) end end instrument(name, created) if created created end |
.signature_for(result) ⇒ Object
Normalize a verifier return value into a stable signature string.
150 151 152 153 154 155 156 |
# File 'lib/close_encounters.rb', line 150 def signature_for(result) case result when true, :ok then VERIFIED_SIGNATURE when false, nil then UNVERIFIED_SIGNATURE else result.to_s end end |
.signature_for_event(event) ⇒ Object
Read the signature off a previously stored event, falling back to the legacy verified flag for events created before signatures existed.
160 161 162 163 164 165 |
# File 'lib/close_encounters.rb', line 160 def signature_for_event(event) return nil unless event stored = event..to_h["signature"] return stored if stored event.verified? ? VERIFIED_SIGNATURE : UNVERIFIED_SIGNATURE end |
.status(name) ⇒ Integer
Get the status of the most recent contact with a third party service
195 196 197 |
# File 'lib/close_encounters.rb', line 195 def status(name) ParticipantService.find_by!(name: name).events.newest.pick(:status) end |
.verify_scan_statuses ⇒ Object
Get the statuses that should be verified
189 |
# File 'lib/close_encounters.rb', line 189 def verify_scan_statuses = configuration.verify_scan_statuses |