module Shrine::Attacher::InstanceMethods

Attributes

context[R]

Returns options that are automatically forwarded to the uploader. Can be modified with additional data.

file[R]

Returns the attached uploaded file.

Public Class Methods

new(file: nil, cache: :cache, store: :store) click to toggle source

Initializes the attached file, temporary and permanent storage.

# File lib/shrine/attacher.rb, line 41
def initialize(file: nil, cache: :cache, store: :store)
  @file     = file
  @cache    = cache
  @store    = store
  @context  = {}
  @previous = nil
end

Public Instance Methods

assign(value, **options) click to toggle source

Calls attach_cached, but skips if value is an empty string (this is useful when the uploaded file comes from form fields). Forwards any additional options to attach_cached.

attacher.assign(File.open(...))
attacher.assign(File.open(...), metadata: { "foo" => "bar" })
attacher.assign('{"id":"...","storage":"cache","metadata":{...}}')
attacher.assign({ "id" => "...", "storage" => "cache", "metadata" => {} })

# ignores the assignment when a blank string is given
attacher.assign("")
# File lib/shrine/attacher.rb, line 70
def assign(value, **options)
  return if value == "" # skip empty hidden field

  if value.is_a?(Hash) || value.is_a?(String)
    return if uploaded_file(value) == file # skip assignment for current file
  end

  attach_cached(value, **options)
end
attach(io, storage: store_key, **options) click to toggle source

Uploads given IO object and changes the uploaded file.

# uploads the file to permanent storage
attacher.attach(io)

# uploads the file to specified storage
attacher.attach(io, storage: :other_store)

# forwards additional options to the uploader
attacher.attach(io, upload_options: { acl: "public-read" }, metadata: { "foo" => "bar" })

# removes the attachment
attacher.attach(nil)
# File lib/shrine/attacher.rb, line 116
def attach(io, storage: store_key, **options)
  file = upload(io, storage, **options) if io

  change(file)
end
attach_cached(value, **options) click to toggle source

Sets an existing cached file, or uploads an IO object to temporary storage and sets it via attach. Forwards any additional options to attach.

# upload file to temporary storage and set the uploaded file.
attacher.attach_cached(File.open(...))

# foward additional options to the uploader
attacher.attach_cached(File.open(...), metadata: { "foo" => "bar" })

# sets an existing cached file from JSON data
attacher.attach_cached('{"id":"...","storage":"cache","metadata":{...}}')

# sets an existing cached file from Hash data
attacher.attach_cached({ "id" => "...", "storage" => "cache", "metadata" => {} })
# File lib/shrine/attacher.rb, line 95
def attach_cached(value, **options)
  if value.is_a?(String) || value.is_a?(Hash)
    change(cached(value, **options))
  else
    attach(value, storage: cache_key, action: :cache, **options)
  end
end
attached?() click to toggle source

Returns whether a file is attached.

attacher.attach(io)
attacher.attached? #=> true

attacher.attach(nil)
attacher.attached? #=> false
# File lib/shrine/attacher.rb, line 272
def attached?
  !!file
end
cache() click to toggle source

Returns the uploader that is used for the temporary storage.

# File lib/shrine/attacher.rb, line 55
def cache; shrine_class.new(cache_key); end
cache_key() click to toggle source

Returns the temporary storage identifier.

# File lib/shrine/attacher.rb, line 50
def cache_key; @cache.to_sym; end
cached?(file = self.file) click to toggle source

Returns whether the file is uploaded to temporary storage.

attacher.cached?       # checks current file
attacher.cached?(file) # checks given file
# File lib/shrine/attacher.rb, line 280
def cached?(file = self.file)
  uploaded?(file, cache_key)
end
change(file) click to toggle source

Sets the uploaded file with dirty tracking, and runs validations.

attacher.change(uploaded_file)
attacher.file #=> #<Shrine::UploadedFile>
attacher.changed? #=> true
# File lib/shrine/attacher.rb, line 219
def change(file)
  @previous = dup if change?(file)
  set(file)
end
changed?() click to toggle source

Returns whether the attachment has changed.

attacher.changed? #=> false
attacher.attach(file)
attacher.changed? #=> true
# File lib/shrine/attacher.rb, line 261
def changed?
  !!@previous
end
data() click to toggle source

Generates serializable data for the attachment.

attacher.data #=> { "id" => "...", "storage" => "...", "metadata": { ... } }
# File lib/shrine/attacher.rb, line 295
def data
  file&.data
end
destroy() click to toggle source

Destroys the attachment.

attacher.file.exists? #=> true
attacher.destroy
attacher.file.exists? #=> false
# File lib/shrine/attacher.rb, line 210
def destroy
  file&.delete
end
destroy_attached() click to toggle source

Destroys the attached file if it exists and is uploaded to permanent storage.

attacher.file.exists? #=> true
attacher.destroy_attached
attacher.file.exists? #=> false
# File lib/shrine/attacher.rb, line 201
def destroy_attached
  destroy if destroy?
end
destroy_previous() click to toggle source

If a new file was attached, deletes previously attached file if any.

previous_file = attacher.file
attacher.attach(file)
attacher.destroy_previous
previous_file.exists? #=> false
# File lib/shrine/attacher.rb, line 191
def destroy_previous
  @previous.destroy_attached if changed?
end
file!() click to toggle source

Returns attached file or raises an exception if no file is attached.

# File lib/shrine/attacher.rb, line 321
def file!
  file or fail Error, "no file is attached"
end
file=(file) click to toggle source

Saves the given uploaded file to an instance variable.

attacher.file = uploaded_file
attacher.file #=> #<Shrine::UploadedFile>
# File lib/shrine/attacher.rb, line 312
def file=(file)
  unless file.is_a?(Shrine::UploadedFile) || file.nil?
    fail ArgumentError, "expected file to be a Shrine::UploadedFile or nil, got #{file.inspect}"
  end

  @file = file
end
finalize() click to toggle source

Deletes any previous file and promotes newly attached cached file. It also clears any dirty tracking.

# promoting cached file
attacher.assign(io)
attacher.cached? #=> true
attacher.finalize
attacher.stored?

# deleting previous file
previous_file = attacher.file
previous_file.exists? #=> true
attacher.assign(io)
attacher.finalize
previous_file.exists? #=> false

# clearing dirty tracking
attacher.assign(io)
attacher.changed? #=> true
attacher.finalize
attacher.changed? #=> false
# File lib/shrine/attacher.rb, line 143
def finalize
  destroy_previous
  promote_cached
  @previous = nil
end
get() click to toggle source

Returns the attached file.

# when a file is attached
attacher.get #=> #<Shrine::UploadedFile>

# when no file is attached
attacher.get #=> nil
# File lib/shrine/attacher.rb, line 240
def get
  file
end
load_data(data) click to toggle source

Loads the uploaded file from data generated by ‘Attacher#data`.

attacher.file #=> nil
attacher.load_data({ "id" => "...", "storage" => "...", "metadata" => { ... } })
attacher.file #=> #<Shrine::UploadedFile>
# File lib/shrine/attacher.rb, line 304
def load_data(data)
  @file = data && uploaded_file(data)
end
promote(storage: store_key, **options) click to toggle source

Uploads current file to permanent storage and sets the stored file.

attacher.cached? #=> true
attacher.promote
attacher.stored? #=> true
# File lib/shrine/attacher.rb, line 170
def promote(storage: store_key, **options)
  set upload(file, storage, action: :store, **options)
end
promote_cached(**options) click to toggle source

If a new cached file has been attached, uploads it to permanent storage. Any additional options are forwarded to promote.

attacher.assign(io)
attacher.cached? #=> true
attacher.promote_cached
attacher.stored? #=> true
# File lib/shrine/attacher.rb, line 161
def promote_cached(**options)
  promote(**options) if promote?
end
save() click to toggle source

Plugins can override this if they want something to be done in a “before save” callback.

# File lib/shrine/attacher.rb, line 151
def save
end
set(file) click to toggle source

Sets the uploaded file.

attacher.set(uploaded_file)
attacher.file #=> #<Shrine::UploadedFile>
attacher.changed? #=> false
# File lib/shrine/attacher.rb, line 229
def set(file)
  self.file = file
end
shrine_class() click to toggle source

Returns the Shrine class that this attacher’s class is namespaced under.

# File lib/shrine/attacher.rb, line 338
def shrine_class
  self.class.shrine_class
end
store() click to toggle source

Returns the uploader that is used for the permanent storage.

# File lib/shrine/attacher.rb, line 57
def store; shrine_class.new(store_key); end
store_key() click to toggle source

Returns the permanent storage identifier.

# File lib/shrine/attacher.rb, line 52
def store_key; @store.to_sym; end
stored?(file = self.file) click to toggle source

Returns whether the file is uploaded to permanent storage.

attacher.stored?       # checks current file
attacher.stored?(file) # checks given file
# File lib/shrine/attacher.rb, line 288
def stored?(file = self.file)
  uploaded?(file, store_key)
end
upload(io, storage = store_key, **options) click to toggle source

Delegates to ‘Shrine.upload`, passing the context.

# upload file to specified storage
attacher.upload(io, :store) #=> #<Shrine::UploadedFile>

# pass additional options for the uploader
attacher.upload(io, :store, metadata: { "foo" => "bar" })
# File lib/shrine/attacher.rb, line 181
def upload(io, storage = store_key, **options)
  shrine_class.upload(io, storage, **context, **options)
end
uploaded_file(value) click to toggle source

Converts JSON or Hash data into a Shrine::UploadedFile object.

attacher.uploaded_file('{"id":"...","storage":"...","metadata":{...}}')
#=> #<Shrine::UploadedFile ...>

attacher.uploaded_file({ "id" => "...", "storage" => "...", "metadata" => {} })
#=> #<Shrine::UploadedFile ...>
# File lib/shrine/attacher.rb, line 332
def uploaded_file(value)
  shrine_class.uploaded_file(value)
end
url(**options) click to toggle source

If a file is attached, returns the uploaded file URL, otherwise returns nil. Any options are forwarded to the storage.

attacher.file = file
attacher.url #=> "https://..."

attacher.file = nil
attacher.url #=> nil
# File lib/shrine/attacher.rb, line 252
def url(**options)
  file&.url(**options)
end

Private Instance Methods

cached(value, **) click to toggle source

Converts a String or Hash value into an UploadedFile object and ensures it’s uploaded to temporary storage.

# from JSON data
attacher.cached('{"id":"...","storage":"cache","metadata":{...}}')
#=> #<Shrine::UploadedFile>

# from Hash data
attacher.cached({ "id" => "...", "storage" => "cache", "metadata" => { ... } })
#=> #<Shrine::UploadedFile>
# File lib/shrine/attacher.rb, line 361
def cached(value, **)
  uploaded_file = uploaded_file(value)

  # reject files not uploaded to temporary storage, because otherwise
  # attackers could hijack other users' attachments
  unless cached?(uploaded_file)
    fail Shrine::Error, "expected cached file, got #{uploaded_file.inspect}"
  end

  uploaded_file
end
change?(file) click to toggle source

Whether assigning the given file is considered a change.

# File lib/shrine/attacher.rb, line 384
def change?(file)
  @file != file
end
destroy?() click to toggle source

Whether attached file should be deleted.

# File lib/shrine/attacher.rb, line 379
def destroy?
  attached? && !cached?
end
initialize_copy(other) click to toggle source

The copy constructor that’s called on dup and clone We need to duplicate the context to prevent it from being shared

Calls superclass method
# File lib/shrine/attacher.rb, line 346
def initialize_copy(other)
  super
  @context = @context.dup
end
promote?() click to toggle source

Whether attached file should be uploaded to permanent storage.

# File lib/shrine/attacher.rb, line 374
def promote?
  changed? && cached?
end
uploaded?(file, storage_key) click to toggle source

Returns whether the file is uploaded to specified storage.

# File lib/shrine/attacher.rb, line 389
def uploaded?(file, storage_key)
  file&.storage_key == storage_key
end