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
.