Class: Plutonium::Wizard::Store::ActiveRecord

Inherits:
Base
  • Object
show all
Defined in:
lib/plutonium/wizard/store/active_record.rb

Overview

Shipped store, backed by the plutonium_wizard_sessions table via Plutonium::Wizard::Session.

Constant Summary collapse

ENCRYPTED_ENVELOPE_KEY =

A data blob encrypted at rest (§8.1, the wizard's encrypt_data opt-in) is stored as a SELF-DESCRIBING envelope INSIDE the jsonb column: { "_enc" => "<ciphertext>" }. The row therefore decrypts based on its own shape, independent of the wizard's CURRENT encrypt_data? (which may have been toggled after the row was written). Only data (the step field values) is encrypted; tracked_records holds record GIDs and stays in clear.

"_enc"

Instance Method Summary collapse

Instance Method Details

#clear(instance_key) ⇒ Object



62
63
64
# File 'lib/plutonium/wizard/store/active_record.rb', line 62

def clear(instance_key)
  Session.where(instance_key: instance_key).delete_all
end

#complete(instance_key) ⇒ Object



57
58
59
60
# File 'lib/plutonium/wizard/store/active_record.rb', line 57

def complete(instance_key)
  row = Session.find_by!(instance_key: instance_key)
  row.update!(status: "completed", completed_at: Time.current, data: {}, tracked_records: {}, visited: [])
end

#completed?(instance_key:) ⇒ Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/plutonium/wizard/store/active_record.rb', line 66

def completed?(instance_key:)
  Session.status_completed.where(instance_key: instance_key).exists?
end

#read(instance_key) ⇒ Object



16
17
18
19
# File 'lib/plutonium/wizard/store/active_record.rb', line 16

def read(instance_key)
  row = Session.find_by(instance_key: instance_key)
  row && to_state(row)
end

#write(instance_key, state, cleanup_after:, &merge) ⇒ Object

Upsert the run's state. Concurrency-safe (§6.2): two requests can write the same run (double-submit, two tabs, the first-step create race). A blind overwrite would let the later writer clobber the earlier one's staged data (last-writer-wins). Instead:

  • Create (no row yet): insert. If a concurrent create won the unique instance_key index (RecordNotUnique), fall through to the merge path.
  • Update (row exists): take a row lock, then compare the version the caller's state was read at against the row's CURRENT version. Equal → no concurrent writer, write state verbatim (honors this request's prunes/deletions). Differs (or the caller never read a version — it lost a create race) → a concurrent advance committed; call the merge block with the latest committed Plutonium::Wizard::State so the two sides are combined, and write the result. The lock serializes writers; the version check keeps the merge off the normal single-writer path.

merge is optional — a blockless caller keeps last-writer-wins (the store-contract callers that don't model concurrency). The runner always passes one.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/plutonium/wizard/store/active_record.rb', line 40

def write(instance_key, state, cleanup_after:, &merge)
  row = Session.find_or_initialize_by(instance_key: instance_key)

  if row.new_record?
    assign_row(row, state, cleanup_after)
    begin
      row.save!
      return to_state(row)
    rescue ::ActiveRecord::RecordNotUnique
      # Lost the create race — the row now exists. Re-fetch and merge.
      row = Session.find_by!(instance_key: instance_key)
    end
  end

  write_locked(row, state, cleanup_after, merge)
end