Skip to content

feat: add Active Storage-backed Valkyrie storage adapter#7550

Closed
orangewolf wants to merge 2 commits into
samvera:mainfrom
orangewolf:feat/valkyrie-active-storage-adapter
Closed

feat: add Active Storage-backed Valkyrie storage adapter#7550
orangewolf wants to merge 2 commits into
samvera:mainfrom
orangewolf:feat/valkyrie-active-storage-adapter

Conversation

@orangewolf

Copy link
Copy Markdown
Member

Summary

Adds Hyrax::Storage::ActiveStorage — a Valkyrie StorageAdapter that stores repository files (PCDM files belonging to file sets, including versions) as ActiveStorage::Blob records. Part of the non-AF → Active Storage chain; stacked on #7547 (its commit appears in this diff until it merges). Independent of #7548/#7549 — this adapter serves the repository side, they serve the staging side.

With this adapter registered, which backend holds repository file bytes is decided by standard Rails configuration — config/storage.yml + config.active_storage.service, or a per-instance service_name:. Switching a Valkyrie app's repository files from local disk to S3 is that one config change; no bespoke Hyrax S3 configuration.

Design:

  • Versioning mirrors Valkyrie::Storage::VersionedDisk exactly, encoded in blob keys instead of paths: versions live at <key_prefix><resource_id>/v-<millis>-<filename>, and file identifiers circulate in the stable v-current- reference form — Hyrax::FileMetadata#file_identifier stays stable as versions arrive, Hyrax::VersioningService and the file-set Versions tab work unchanged (supports?(:versions) and :version_deletion are true).
  • Lazy IO: find_by returns a StreamFile whose blob IO downloads nothing until content is actually read; #disk_path consumers (FITS characterization, derivatives, downloads, riiif) keep working via local materialization — the same pattern as the Fedora adapter.
  • Blobs only, no ActiveStorage::Attachment rows — these are repository files, not app-level attachments (also avoids analyze jobs).
  • Multiple instances can target different key namespaces/services (e.g. a derivatives instance).
  • Registered in koppie as :active_storage_storage, selectable via VALKYRIE_STORAGE_ADAPTER=active_storage_storage; koppie's default adapter is unchanged (a later PR proposes the flip). ActiveFedora file-set storage is untouched, as throughout this chain.
  • Docs: registration + S3 example in documentation/developing-your-hyrax-based-app.md.

Dependencies

Tests

Run locally against the koppie (Valkyrie) test app — dockerized Postgres/Solr/Redis:

  • spec/services/hyrax/storage/active_storage_spec.rb — valkyrie's full 'a Valkyrie::StorageAdapter' shared compliance suite (upload/find_by/delete/checksums plus the complete upload_version / find_versions / version-deletion semantics), plus adapter-specific examples: stable current-reference ids, byte-identical round-trips, disk_path materialization, no download on lookup, prefix-scoped handles?, slash-containing filenames (collection-branding style), per-instance service_name, and an integration example through Hyrax::ValkyrieUpload + Hyrax::VersioningService (ingest → version on re-upload → find_versions == 2). Result: 23 examples, 0 failures, 1 skip.
  • The single skip is valkyrie's lsof file-descriptor-count shared example, which asserts strict fd equality and is environment-sensitive under the full Hyrax test stack (connection pools); the property it guards — no eager download on find_by — is covered by a dedicated example. The skip carries this explanation in the spec.

RuboCop clean on new files; git diff --check clean.

🤖 Generated with Claude Code

orangewolf and others added 2 commits July 17, 2026 00:27
Adds the canonical Rails create_active_storage_tables migration to the
.dassie and .koppie test applications and regenerates their schemas, and
teaches the hyrax:models generator to run active_storage:install so host
applications get the active_storage_* tables alongside Hyrax's own
migrations.

Both test apps already ship config/storage.yml and set
config.active_storage.service per environment; this fills in the missing
database tables so upcoming work can stage uploads and store repository
files through Active Storage, where switching local disk vs S3 is a
single Rails storage configuration change.

No behavior change: nothing in Hyrax attaches or reads Active Storage
data yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Hyrax::Storage::ActiveStorage is a Valkyrie StorageAdapter that stores
repository files as ActiveStorage::Blob records (no Attachment join
rows), so the configured Active Storage service — config/storage.yml and
config.active_storage.service, or a service_name: given to the adapter
instance — decides where the bytes live. Switching a Valkyrie app's
repository files between local disk and S3 becomes a Rails storage
configuration change; nothing else in Hyrax needs to know.

Versioning mirrors Valkyrie::Storage::VersionedDisk semantics, encoded
in blob keys instead of file paths: versions are stored under
<key_prefix><resource_id>/v-<timestamp>-<filename> and file identifiers
circulate as the stable v-current reference form, so
Hyrax::FileMetadata#file_identifier survives new versions and the file
set Versions tab works unchanged. find_by returns a StreamFile with a
lazy blob IO — nothing downloads until content is read, and #disk_path
consumers (characterization, derivatives, downloads, riiif) keep
working through local materialization.

The adapter is registered in koppie as :active_storage_storage
(selectable via VALKYRIE_STORAGE_ADAPTER); the default adapter is
unchanged. Documentation covers registration and the S3 switch.

ActiveFedora file-set storage is untouched, as throughout this chain.

Tests: spec/services/hyrax/storage/active_storage_spec.rb runs
valkyrie's full 'a Valkyrie::StorageAdapter' shared compliance examples
(including upload_version/find_versions/version deletion semantics)
plus adapter-specific coverage: stable current-reference ids, content
round-trips, disk_path materialization, lazy lookup (no download on
find_by), prefix-scoped handles?, slash-containing filenames
(branding-style), per-instance service_name, and an integration example
through Hyrax::ValkyrieUpload + Hyrax::VersioningService — 23 examples,
0 failures, 1 documented skip (the shared lsof fd-count example, which
is environment-sensitive under the full Hyrax stack; its property is
covered by an adapter-specific example). Run locally against the koppie
test app. One valkyrie shared example exercises Fedora-style adapters'
WebMock toggling and lsof, noted in the spec.

Depends on the Active Storage enablement PR for test-app tables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Test Results

    1 files   -     16      1 suites   - 16   0s ⏱️ - 3h 19m 30s
1 923 tests  -  6 034  1 923 ✅  -  5 720  0 💤  - 307  0 ❌  - 7 
1 923 runs   - 25 267  1 923 ✅  - 24 661  0 💤  - 599  0 ❌  - 7 

Results for commit 84dc0ad. ± Comparison against base commit 16ddae4.

@orangewolf

Copy link
Copy Markdown
Member Author

Superseded by #7556 — same change moved to an in-repo branch (active-storage-for-all/valkyrie-storage-adapter) and retitled.

@orangewolf orangewolf closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant