Active Storage¶
Active Storage attaches files to model records through a pluggable storage service. Blob and attachment records hold the metadata, a service stores the bytes, and signed URLs serve them back. Persistence is delegated through a repository so the web-side glue stays independent of any one ORM.
Blobs¶
A MVC::Keayl::Storage::Blob records a stored file: its storage key, original
filename, content-type, byte-size, and a checksum. Blob.build derives
the size and checksum from the data:
1 2 3 4 5 6 7 8 9 10 11 12 | |
checksum-for($data) and generate-key() are available on their own when you
need them.
Services¶
A service stores and retrieves bytes by key. MVC::Keayl::Storage::Service
defines the interface: upload, download, delete, exist, and url.
1 2 3 4 5 6 7 8 9 | |
DiskService writes under a configured root, sharding files by a digest of the
key. ExternalService wraps a client object shaped like an S3 or GCS adapter
(upload, download, delete, exist, url). MirrorService writes and
deletes through a primary service and a list of mirrors, reading from the
primary:
1 | |
Attaching to records¶
A model includes MVC::Keayl::Storage::Attachable and declares its attachments.
has-one-attached gives a single attachment, has-many-attached a collection.
1 2 3 4 5 6 7 | |
Configure the default service and repository once at boot:
1 2 3 | |
Attach from an uploaded file, a raw IO hash, an existing blob, or a signed blob id:
1 2 3 4 5 6 7 8 9 | |
Attaching to a has-one-attached again replaces the existing attachment.
has-many-attached appends:
1 2 3 4 5 6 7 | |
Repository¶
A repository persists blob and attachment records.
MVC::Keayl::Storage::Repository defines the interface, and MemoryRepository
is an in-process implementation. An ORM-backed repository implements the same
methods (create-blob, find-blob, attachments-for, and the rest) and is
passed to set-storage-repository.
Serving and URLs¶
Blobs are served through signed URLs. blob-serving-path builds a path carrying
a signed blob id; :proxy selects streaming over redirecting, and expires-in
sets an expiry:
1 2 3 4 5 6 7 8 | |
Two controllers serve the routes. RedirectController verifies the signed id
and redirects to the service URL. ProxyController verifies the signed id,
downloads the bytes, and streams them with the blob's content type and a
content disposition (inline by default, attachment when the disposition
param asks for it).
A signed id only verifies for its purpose and before its expiry, so tampered or
stale URLs return 404.
Variants and direct upload¶
MVC::Keayl::Storage::Variant transforms an image blob and caches the result as
a separate object keyed by a digest of the transformations. A variant is
processed lazily on first request:
1 2 3 4 5 6 7 | |
The transformer is pluggable. IdentityTransformer passes the bytes through;
CallableTransformer wraps a block; a production transformer shells out to an
image library. Set the default with set-storage-transformer.
Direct upload lets a client send bytes straight to storage.
DirectUploadsController#create records a blob from the submitted metadata and
returns its signed id plus an upload URL:
1 2 3 4 5 | |
The client then PUTs the bytes to that URL, which DiskController#update
verifies and stores. The file-field form helper wires the browser side:
1 2 | |