Class: Clickwrap::PendingReceipt

Inherits:
Object
  • Object
show all
Defined in:
lib/clickwrap/pending_receipt.rb

Overview

What capture_and! yields to the protected action, before anything has committed.

It carries the one thing the domain row legitimately needs — a stable event_id to reference — and deliberately withholds everything else. Export and verification are unavailable because there is nothing yet to export or verify: the transaction may still roll back, and an object that happily serialized itself into a receipt at this point would be describing evidence that might never exist.

If the transaction does roll back, this object becomes invalid rather than continuing to look like a committed record.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(event, wait_for_outer_transaction: false) ⇒ PendingReceipt

Returns a new instance of PendingReceipt.



19
20
21
22
23
24
# File 'lib/clickwrap/pending_receipt.rb', line 19

def initialize(event, wait_for_outer_transaction: false)
  @event = event
  @wait_for_outer_transaction = wait_for_outer_transaction
  @committed = false
  @rolled_back = false
end

Instance Attribute Details

#eventObject (readonly)

Returns the value of attribute event.



17
18
19
# File 'lib/clickwrap/pending_receipt.rb', line 17

def event
  @event
end

Instance Method Details

#actor_referenceObject



29
# File 'lib/clickwrap/pending_receipt.rb', line 29

def actor_reference = event.actor_reference

#answer_for(statement_key) ⇒ Object

Read the exact answer that this pending event accepted for one statement. Protected domain work often needs it before commit: for example, an optional consent can enable one preference while an unselected consent must leave that preference disabled. Reaching through pending_receipt.event.statements at every call site is both noisy and dangerously easy to get wrong (an offered-but-unselected option is not a grant).

Unknown statement keys fail loudly so a typo cannot silently turn into a false boolean at a consequential boundary.



46
47
48
# File 'lib/clickwrap/pending_receipt.rb', line 46

def answer_for(statement_key)
  statement_for(statement_key)&.answer
end

#answered?(statement_key) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/clickwrap/pending_receipt.rb', line 50

def answered?(statement_key)
  statement_for(statement_key)&.answered? || false
end

#committed?Boolean

Returns:

  • (Boolean)


65
66
67
68
# File 'lib/clickwrap/pending_receipt.rb', line 65

def committed?
  refresh_commit_state_if_possible!
  @committed
end

#declined?(statement_key) ⇒ Boolean

Returns:

  • (Boolean)


61
62
63
# File 'lib/clickwrap/pending_receipt.rb', line 61

def declined?(statement_key)
  answered?(statement_key) && statement_for(statement_key).action == "declined"
end

#event_idObject



26
# File 'lib/clickwrap/pending_receipt.rb', line 26

def event_id = event.id

#exportObject



101
# File 'lib/clickwrap/pending_receipt.rb', line 101

def export(*) = refuse(:export)

#granted?(statement_key) ⇒ Boolean

Consent-oriented convenience predicates. They inspect BOTH the recorded action and whether the person actually answered, so these methods never reinterpret silence as a grant or decline.

Returns:

  • (Boolean)


57
58
59
# File 'lib/clickwrap/pending_receipt.rb', line 57

def granted?(statement_key)
  answered?(statement_key) && statement_for(statement_key).action == "granted"
end

#inspectObject



103
104
105
106
107
108
109
110
# File 'lib/clickwrap/pending_receipt.rb', line 103

def inspect
  state = if committed? then "committed"
          elsif rolled_back? then "rolled back"
          else "uncommitted"
          end

  "#<Clickwrap::PendingReceipt #{event_id} (#{state})>"
end

#mark_committed!Object

Internal transaction callbacks. Public only because the Event callback invokes them; downstream code gains no authority from calling them—the receipt lookup still requires the event to exist after commit.



117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/clickwrap/pending_receipt.rb', line 117

def mark_committed!
  # A non-joinable outer transaction (most visibly Rails' transactional
  # test wrapper) can make a record-level after_commit fire at a savepoint.
  # That is not durable finality. Leave the handle pending until no database
  # transaction is open and the finalized row can be observed afresh.
  return self if @wait_for_outer_transaction && ::ActiveRecord::Base.connection.transaction_open?

  return self if @committed

  @committed = true
  @rolled_back = false
  run_durable_commit_callbacks
  self
end

#mark_rolled_back!Object



132
133
134
135
136
137
# File 'lib/clickwrap/pending_receipt.rb', line 132

def mark_rolled_back!
  @committed = false
  @rolled_back = true
  @durable_commit_callbacks = []
  self
end

#policy_keyObject



27
# File 'lib/clickwrap/pending_receipt.rb', line 27

def policy_key = event.policy_key

#receiptObject

Once the real outermost transaction commits, this same object becomes a truthful handle to the finalized receipt. This matters when Clickwrap joins a host-owned transaction: the capture method has to return before that outer transaction does, so returning a Receipt there would claim a commit that has not happened yet.



92
93
94
95
96
# File 'lib/clickwrap/pending_receipt.rb', line 92

def receipt
  refuse(:receipt) unless committed?

  Receipt.find(event_id)
end

#recorded_at_by_serverObject



28
# File 'lib/clickwrap/pending_receipt.rb', line 28

def recorded_at_by_server = event.recorded_at_by_server

#rolled_back?Boolean

Returns:

  • (Boolean)


70
# File 'lib/clickwrap/pending_receipt.rb', line 70

def rolled_back? = @rolled_back

#statementsObject



32
33
34
# File 'lib/clickwrap/pending_receipt.rb', line 32

def statements
  event.statements.to_h { |statement| [statement.statement_key, statement.action] }
end

#subject_fingerprintObject



30
# File 'lib/clickwrap/pending_receipt.rb', line 30

def subject_fingerprint = event.subject_fingerprint

#to_canonical_jsonObject



98
# File 'lib/clickwrap/pending_receipt.rb', line 98

def to_canonical_json = refuse(:to_canonical_json)

#to_htmlObject



99
# File 'lib/clickwrap/pending_receipt.rb', line 99

def to_html = refuse(:to_html)

#to_sObject



112
# File 'lib/clickwrap/pending_receipt.rb', line 112

def to_s = event_id

#verifyObject



100
# File 'lib/clickwrap/pending_receipt.rb', line 100

def verify = refuse(:verify)

#when_durably_committed(&callback) ⇒ Object

Registers work that is only truthful after the real outer transaction commits. Clickwrap's controller adapters use this to clear a registration flow id without clearing it after a savepoint that later rolls back.

Raises:

  • (ArgumentError)


75
76
77
78
79
80
81
82
83
84
85
# File 'lib/clickwrap/pending_receipt.rb', line 75

def when_durably_committed(&callback)
  raise ArgumentError, "when_durably_committed needs a block" unless callback

  if committed?
    run_durable_commit_callback(callback)
  elsif !rolled_back?
    (@durable_commit_callbacks ||= []) << callback
  end

  self
end