Class: Clickwrap::StatementState
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Clickwrap::StatementState
- Defined in:
- lib/clickwrap/models/statement_state.rb
Overview
The current-state projection: the answer to "does this person currently have X?" without walking the whole event history on every request.
This table is a cache of a computation over retained event payloads. Nothing
here is evidence — the evidence is in clickwrap_events. Before retention
disposes of a root payload it can be rebuilt from those events; afterward,
deleting this projection would try to recreate personal identity facts that
the reviewed disposition intentionally removed. CurrentState.rebuild_for!
therefore refuses that destructive operation when it can see such a root.
That separation lets this row be mutable, indexed, and fast without those
properties leaking into the evidence record.
The unique index guarantees one projection row per identity. Portable StatementIdentityLock rows serialize writers; the unique index alone would not stop two immutable capture events or decide which one is current.
Class Method Summary collapse
-
.identity_digest_for(attributes) ⇒ Object
The digest the unique index is taken over.
-
.identity_for(policy_key:, statement_key:, actor_reference:, tenant_key: nil, subject_key: nil, represented_party_reference: nil) ⇒ Object
The identity a unique index can enforce.
- .subject_key_for(subject) ⇒ Object
- .tenant_key_for(tenant) ⇒ Object
Instance Method Summary collapse
- #current_event ⇒ Object
- #expired?(at = Clickwrap.now) ⇒ Boolean
-
#failure_reason(at = Clickwrap.now) ⇒ Object
Why it does not, as one of the stable error symbols applications branch on.
-
#satisfies?(at = Clickwrap.now) ⇒ Boolean
Whether this projection currently satisfies a requirement.
- #to_s ⇒ Object
Class Method Details
.identity_digest_for(attributes) ⇒ Object
The digest the unique index is taken over. Canonicalized first, so the value depends on the five parts and not on the order a caller happened to build the hash in.
68 69 70 |
# File 'lib/clickwrap/models/statement_state.rb', line 68 def self.identity_digest_for(attributes) Digest.digest_canonical(attributes.transform_keys(&:to_s)) end |
.identity_for(policy_key:, statement_key:, actor_reference:, tenant_key: nil, subject_key: nil, represented_party_reference: nil) ⇒ Object
The identity a unique index can enforce. NULLs do not collide in a unique index on most adapters, so "no tenant" and "no subject" are the empty string rather than NULL — otherwise a policy with no subject would happily accumulate duplicate live grants.
51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/clickwrap/models/statement_state.rb', line 51 def self.identity_for(policy_key:, statement_key:, actor_reference:, tenant_key: nil, subject_key: nil, represented_party_reference: nil) attributes = { policy_key: policy_key.to_s, statement_key: statement_key.to_s, actor_reference: actor_reference.to_s, tenant_key: tenant_key.to_s, subject_key: subject_key.to_s, represented_party_reference: represented_party_reference.to_s } attributes.merge(identity_digest: identity_digest_for(attributes)) end |
Instance Method Details
#current_event ⇒ Object
75 |
# File 'lib/clickwrap/models/statement_state.rb', line 75 def current_event = Event.find_by(id: current_event_id) |
#expired?(at = Clickwrap.now) ⇒ Boolean
77 |
# File 'lib/clickwrap/models/statement_state.rb', line 77 def expired?(at = Clickwrap.now) = expires_at.present? && expires_at <= at |
#failure_reason(at = Clickwrap.now) ⇒ Object
Why it does not, as one of the stable error symbols applications branch on. Never an English string: an authorization decision should not depend on parsing a message.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/clickwrap/models/statement_state.rb', line 92 def failure_reason(at = Clickwrap.now) return nil if satisfies?(at) case state when "withdrawn" then :consent_withdrawn when "declined" then :declined when "superseded", "corrected" then :superseded when "revoked" then :revoked when "consumed" then :authorization_consumed when "exempted" then :exemption_not_accepted when "expired" then expiry_error else expired?(at) ? expiry_error : :no_evidence end end |
#satisfies?(at = Clickwrap.now) ⇒ Boolean
Whether this projection currently satisfies a requirement. Expiry is
evaluated live rather than trusted from the state column, because a
declaration expires on a clock, not on a background job having run.
82 83 84 85 86 87 |
# File 'lib/clickwrap/models/statement_state.rb', line 82 def satisfies?(at = Clickwrap.now) return false unless state == "active" return false if expired?(at) true end |
#to_s ⇒ Object
107 |
# File 'lib/clickwrap/models/statement_state.rb', line 107 def to_s = "#{kind} #{statement_key} for #{actor_reference} (#{state})" |