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)
endphoto = 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.newStorage
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 #=> :storeWe 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_storeYou can also change default attacher options on the Shrine::Attachment
module:
class Photo
include ImageUploader::Attachment(:image, cache: :other_cache, store: :other_store)
endThe 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-opIf given nil, it will clear the attached file:
attacher.file #=> <Shrine::UploadedFile>
attacher.assign(nil)
attacher.file #=> nilThis 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 locationUploading
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 locationChanges
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? #=> trueYou 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? #=> trueIf 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? #=> falseFinalizing
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.finalizeThe Attacher#finalize method performs promoting and
replacing. It also clears dirty tracking:
attacher.changed? #=> true
attacher.finalize
attacher.changed? #=> falsePromoting
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.promoteAny 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? #=> falseInternally it calls Attacher#destroy_previous to do this:
attacher.destroy_previousRetrieving
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 #=> nilIf you want to assert a file is attached, you can use Attacher#file!:
attacher.file! #~> Shrine::Error: no file is attachedAttached
You can also check whether a file is attached with Attacher#attached?:
attacher.attached? # returns whether file is attachedIf 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 #=> nilData
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_attachedThis 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.destroyContext
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.