—
title: Using Attacher¶ ↑
This guide explains what is Shrine::Attacher
and how to use it.
Introduction¶ ↑
The attachment logic is handled by a Shrine::Attacher
object. The Shrine::Attachment
module simply provides a convenience layer around a Shrine::Attacher
object, which can be accessed via the #<name>_attacher
attribute.
class Photo include ImageUploader::Attachment(:image) end
photo = Photo.new photo.image_attacher #=> #<ImageUploader::Attacher>
We can also instantiate the same Shrine::Attacher
object directly:
attacher = ImageUploader::Attacher.from_model(photo, :image) attacher.file # called by `photo.image` attacher.url # called by `photo.image_url`
The {model
}, {entity
}, and {column
} plugins provide additional Shrine::Attacher
methods (such as Shrine::Attacher.from_model
we see above), but in this guide we’ll focus only on the core Shrine::Attacher
methods.
So, we’ll assume a Shrine::Attacher
object not backed by any model/entity:
attacher = Shrine::Attacher.new
Storage¶ ↑
By default, an Attacher
will use the :cache
storage as the temporary storage, and the :store
storage as the permanent storage.
Shrine.storages = { cache: Shrine::Storage::Memory.new, store: Shrine::Storage::Memory.new, }
attacher = Shrine::Attacher.new attacher.cache_key #=> :cache attacher.store_key #=> :store
We can also change the default storage:
attacher = Shrine::Attacher.new(cache: :other_cache, store: :other_store) attacher.cache_key #=> :other_cache attacher.store_key #=> :other_store
You can also change default attacher options on the Shrine::Attachment
module:
class Photo include ImageUploader::Attachment(:image, cache: :other_cache, store: :other_store) end
The Attacher#cache
and Attacher#store
methods will retrieve corresponding uploaders:
attacher.cache #=> #<MyUploader @storage_key=:cache> attacher.store #=> #<MyUploader @storage_key=:store>
Attaching¶ ↑
Attaching cached¶ ↑
For attaching files submitted via a web form, Attacher#assign
can be used:
attacher.assign(file)
If given a raw file, it will upload it to temporary storage:
attacher.assign(file) attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:cache ...>
If given cached file data (JSON or Hash), it will set the cached file:
attacher.assign('{"id":"asdf.jpg","storage":"cache","metadata":{...}}') attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:cache ...>
If given an empty string, it will no-op:
attacher.assign("") # no-op
If given nil
, it will clear the attached file:
attacher.file #=> <Shrine::UploadedFile> attacher.assign(nil) attacher.file #=> nil
This plays nicely with the recommended HTML form fields for the attachment. If you’re not using the hidden
form field (and therefore don’t need empty strings to be handled), you can also use Attacher#attach_cached
:
# uploads file to cache attacher.attach_cached(file) # sets cached file attacher.attach_cached('{"id":"asdf.jpg","storage":"cache","metadata":{...}}') attacher.attach_cached({ "id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... } }) attacher.attach_cached({ id: "asdf.jpg", storage: "cache", metadata: { ... } }) # unsets attached file attacher.attach_cached(nil)
Attaching stored¶ ↑
The Attacher#attach
method uploads a given file to permanent storage:
attacher.attach(file) attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:store ...>
This method is useful when attaching files from scripts, where validation doesn’t need to be performed, and where temporary storage can be skipped.
You can specify a different destination storage with the :storage
option:
attacher.attach(file, storage: :other_store) attacher.file #=> #<Shrine::UploadedFile id="asdf.jpg" storage=:other_store ...>
Any additional options passed to Attacher#attach
, Attacher#attach_cached
and Attacher#assign
are forwarded to the uploader:
attacher.attach(file, metadata: { "foo" => "bar" }) # adding metadata attacher.attach(file, upload_options: { acl: "private" }) # setting upload options attacher.attach(file, location: "path/to/file") # setting upload location
Uploading¶ ↑
If you want to upload a file to without attaching it, you can use Attacher#upload
:
attacher.upload(file) #=> #<Shrine::UploadedFile storage=:store ...> attacher.upload(file, :cache) #=> #<Shrine::UploadedFile storage=:cache ...> attacher.upload(file, :other_store) #=> #<Shrine::UploadedFile storage=:other_store ...>
This is useful if you want to attacher context such as :record
and :name
to be automatically passed to the uploader.
You can also pass additional options for Shrine#upload
:
attacher.upload(file, metadata: { "foo" => "bar" }) # adding metadata attacher.upload(file, upload_options: { acl: "private" }) # setting upload options attacher.upload(file, location: "path/to/file") # setting upload location
Changes¶ ↑
When a new file is attached, calling {Attacher#finalize
} will perform additional actions such as promotion and deleting any previous file. It will also trigger validation.
You can check whether a new file has been attached with Attacher#changed?
:
attacher.changed? #=> true
You can use Attacher#change
to attach an UploadedFile
object as is:
uploaded_file #=> #<Shrine::UploadedFile id="foo" ...> attacher.change(uploaded_file) attacher.file #=> #<Shrine::UploadedFile id="foo" ...> attacher.changed? #=> true
If you want to attach a file without triggering dirty tracking or validation, you can use Attacher#set
:
uploaded_file #=> #<Shrine::UploadedFile id="foo" ...> attacher.set(uploaded_file) attacher.file #=> #<Shrine::UploadedFile id="foo" ...> attacher.changed? #=> false
Finalizing¶ ↑
After the file is attached (with Attacher#assign
, Attacher#attach_cached
, or Attacher#attach
), and data has been validated, the attachment can be “finalized”:
attacher.finalize
The Attacher#finalize
method performs promoting and replacing. It also clears dirty tracking:
attacher.changed? #=> true attacher.finalize attacher.changed? #=> false
Promoting¶ ↑
Attacher#finalize
checks if the attached file has been uploaded to temporary storage, and in this case uploads it to permanent storage.
attacher.attach_cached(io) attacher.finalize # uploads attached file to permanent storage attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
Internally it calls Attacher#promote_cached
, which you can call directly if you want to pass any promote options:
attacher.file #=> #<Shrine::UploadedFile storage=:cache ...> attacher.promote_cached # uploads attached file to permanent storage if new and cached attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
You can also call Attacher#promote
if you want to upload attached file to permanent storage, regardless of whether it’s cached or newly attached:
attacher.promote
Any options passed to Attacher#promote_cached
or Attacher#promote
will be forwarded to Shrine#upload
.
Replacing¶ ↑
Attacher#finalize
also deletes the previous attached file if any:
previous_file = attacher.file attacher.attach(io) attacher.finalize previous_file.exists? #=> false
Internally it calls Attacher#destroy_previous
to do this:
attacher.destroy_previous
Retrieving¶ ↑
File¶ ↑
The Attacher#file
is used to retrieve the attached file:
attacher.file #=> #<Shrine::UploadedFile>
If no file is attached, Attacher#file
returns nil:
attacher.file #=> nil
If you want to assert a file is attached, you can use Attacher#file!
:
attacher.file! #~> Shrine::Error: no file is attached
Attached¶ ↑
You can also check whether a file is attached with Attacher#attached?
:
attacher.attached? # returns whether file is attached
If you want to check to which storage a file is uploaded to, you can use Attacher#cached?
and Attacher#stored?
:
attacher.attach(io) attacher.stored? #=> true (checks current file) attacher.stored?(attacher.file) #=> true (checks given file)
attacher.attach_cached(io) attacher.cached? #=> true (checks current file) attacher.cached?(attacher.file) #=> true (checks given file)
URL¶ ↑
The attached file URL can be retrieved with Attacher#url
:
attacher.url #=> "https://example.com/file.jpg"
If no file is attached, Attacher#url
returns nil
:
attacher.url #=> nil
Data¶ ↑
You can retrieve plain attached file data with Attacher#data
:
attacher.data #=> # { # "id" => "abc123.jpg", # "storage" => "store", # "metadata" => { # "size" => 223984, # "filename" => "nature.jpg", # "mime_type" => "image/jpeg", # } # }
This data can be stored somewhere, and later the attached file can be loaded from it:
# new attacher attacher = Shrine::Attacher.from_data(data) attacher.file #=> #<Shrine::UploadedFile> # existing attacher attacher.file #=> nil attacher.load_data(data) attacher.file #=> #<Shrine::UploadedFile>
Internally Attacher#uploaded_file
is used to convert uploaded file data into a Shrine::UploadedFile
object:
attacher.uploaded_file("id" => "...", "storage" => "...", "metadata" => { ... }) #=> #<Shrine::UploadedFile> attacher.uploaded_file(id: "...", storage: "...", metadata: { ... }) #=> #<Shrine::UploadedFile> attacher.uploaded_file('{"id":"...","storage":"...","metadata":{...}}') #=> #<Shrine::UploadedFile>
You will likely want to use a higher level abstraction for saving and loading this data, see {column
}, {entity
} and {model
} plugins for more details.
Deleting¶ ↑
The attached file can be deleted via Attacher#destroy_attached
:
attacher.destroy_attached
This will not delete cached files, to not interrupt any potential backgrounding that might be in process.
If you want to delete the attached file regardless of storage it’s uploaded to, you can use Attacher#destroy
:
attacher.destroy
Context¶ ↑
The Attacher#context
hash is automatically forwarded to the uploader on Attacher#upload
. When {model
} or {entity
} plugin is loaded, this will include :record
and :name
values:
attacher = Shrine::Attacher.from_model(photo, :image) attacher.context #=> { record: #<Photo>, name: :image }
You can add here any other parameters you want to forward to the uploader:
attacher.context[:foo] = "bar"
However, it’s generally better practice to pass uploader options directly to Attacher#assign
, Attacher#attach
, Attacher#promote
or any other method that’s calling Attacher#upload
.