Module: Ruact::ServerFunctions::SnapshotWriter
- Defined in:
- lib/ruact/server_functions/snapshot_writer.rb
Overview
Atomic, byte-aware file writer used by both the JSON snapshot bridge and the Ruby-side TypeScript codegen. Two responsibilities:
-
Write-if-changed: compare the SHA-256 of the desired bytes with the on-disk bytes; if equal, no-op. This is the leg of the dev-reload pitfall mitigation (Story 8.0a pitfall #1) — paired with the payload-only fingerprint inside ‘Snapshot.generate!`, it guarantees that a request without registry changes produces zero writes.
-
**Atomic publication**: write to a tmpfile in the same directory, then rename. Readers (the Vite-plugin chokidar watcher) never observe a half-written file.
Parent directories are created as needed.
Class Method Summary collapse
-
.write_if_changed!(path:, content:) ⇒ Boolean
True if the file was written; false if unchanged.
Class Method Details
.write_if_changed!(path:, content:) ⇒ Boolean
Returns true if the file was written; false if unchanged.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/ruact/server_functions/snapshot_writer.rb', line 28 def write_if_changed!(path:, content:) # rubocop:disable Naming/PredicateMethod path = path.to_s # TOCTOU-safe read: catch `ENOENT` from `File.read` rather than # gating on `File.exist?` — between the stat and the read the file # may be removed by another process (e.g. `rails tmp:clear`). existing = begin File.read(path) rescue StandardError nil end return false if existing == content dir = File.dirname(path) ensure_writable!(dir) # Random suffix so two same-process writes of identical content do # not race over the same temp filename (e.g. JSON-snapshot writer # + Ruby TS codegen running back-to-back inside the rake task on # an unchanged registry — the digest-prefix-only tmp name was # deterministic, which collided). tmp = "#{path}.tmp.#{Process.pid}.#{SecureRandom.hex(8)}" File.binwrite(tmp, content) File.rename(tmp, path) true end |