Class: Dragonfly::CascadeDataStore
- Inherits:
-
Object
- Object
- Dragonfly::CascadeDataStore
- Defined in:
- lib/dragonfly/cascade_data_store.rb
Overview
Dragonfly datastore that reads LOCAL-first and falls back to a CHAIN of
read-only datastores on a miss, writing the result back through
(self-healing cache).
Use case: development. Dev writes to secure_assets/development in the
Wasabi usc1 bucket, but blobs live in three eras/places:
secure_assets/development(usc1) — dev-written filessecure_assets/production(usc1) — prod-written since the migrationsecure_assets/productionin the OLD us-east-1heatwave-assets
bucket — everything uploaded before the bucket migration
A plain miss used to raise Dragonfly::Job::Fetch::NotFound and break every
dev flow touching those attachments (publication pages, studio seeding,
docling/translation pipelines). With the cascade, the first read pulls the
blob into the development prefix — subsequent reads are local.
Writes and destroys always go to the primary only: every fallback is
strictly read-through, never mutated.
Datastore contract (same as Dragonfly::S3DataStore / FileDataStore):
write(content, opts) → uid; read(uid) → [data, meta] or nil; destroy(uid);
url_for(uid, opts) — presigned URLs sign against the first prefix that
actually holds the blob.
Instance Attribute Summary collapse
-
#primary ⇒ Object
readonly
Returns the value of attribute primary.
Delegated Instance Attributes collapse
-
#destroy ⇒ Object
Alias for @primary#destroy.
Instance Method Summary collapse
-
#exists?(uid) ⇒ Boolean
Cheap existence check (head request, no download) against the PRIMARY store — use instead of read(uid) when only "already synced locally?" matters; read downloads the full blob just to answer that.
-
#fallback ⇒ Object?
First fallback — kept for callers written against the single-fallback API.
-
#initialize(primary:, fallback: nil, fallbacks: nil) ⇒ CascadeDataStore
constructor
A new instance of CascadeDataStore.
-
#read(uid) ⇒ Array?
The (data, meta) tuple, or nil when no store has the uid.
-
#url_for(uid, opts = {}) ⇒ String
Presigned URL for the blob.
-
#write(content, opts = {}) ⇒ String
The assigned uid.
Constructor Details
#initialize(primary:, fallback: nil, fallbacks: nil) ⇒ CascadeDataStore
Returns a new instance of CascadeDataStore.
31 32 33 34 35 |
# File 'lib/dragonfly/cascade_data_store.rb', line 31 def initialize(primary:, fallback: nil, fallbacks: nil) @primary = primary @fallbacks = Array(fallbacks || fallback) @head_cache = {} end |
Instance Attribute Details
#primary ⇒ Object (readonly)
Returns the value of attribute primary.
27 28 29 |
# File 'lib/dragonfly/cascade_data_store.rb', line 27 def primary @primary end |
Instance Method Details
#destroy ⇒ Object
Alias for @primary#destroy
71 |
# File 'lib/dragonfly/cascade_data_store.rb', line 71 delegate :destroy, to: :@primary |
#exists?(uid) ⇒ Boolean
Cheap existence check (head request, no download) against the PRIMARY
store — use instead of read(uid) when only "already synced locally?"
matters; read downloads the full blob just to answer that.
67 68 69 |
# File 'lib/dragonfly/cascade_data_store.rb', line 67 def exists?(uid) head?(@primary, uid) end |
#fallback ⇒ Object?
First fallback — kept for callers written against the single-fallback API.
39 |
# File 'lib/dragonfly/cascade_data_store.rb', line 39 def fallback = @fallbacks.first |
#read(uid) ⇒ Array?
Returns the (data, meta) tuple, or nil when no store has the uid.
50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/dragonfly/cascade_data_store.rb', line 50 def read(uid) result = @primary.read(uid) return result if result @fallbacks.each do |store| result = store.read(uid) next unless result write_through(uid, result) return result end nil end |
#url_for(uid, opts = {}) ⇒ String
Returns presigned URL for the blob.
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/dragonfly/cascade_data_store.rb', line 77 def url_for(uid, opts = {}) # Presigned URLs point straight at the bucket — cascade them too: sign # against the first prefix that actually has the blob (local dev prefix, # then each fallback in order). Head results are memoized per store+uid # (see head? below) — one round-trip per blob per process, not per link. return @primary.url_for(uid, opts) if head?(@primary, uid) store = @fallbacks.find { |s| head?(s, uid) } # No store holds the blob — sign against the PRIMARY (the canonical home # for new writes), not an arbitrary fallback. (store || @primary).url_for(uid, opts) end |
#write(content, opts = {}) ⇒ String
Returns the assigned uid.
45 46 47 |
# File 'lib/dragonfly/cascade_data_store.rb', line 45 def write(content, opts = {}) @primary.write(content, opts) end |