module Shrine::Plugins::Derivatives::AttacherMethods

Attributes

derivatives[R]

Public Class Methods

new(derivatives: {}, **options) click to toggle source

Adds the ability to accept derivatives.

Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 108
def initialize(derivatives: {}, **options)
  super(**options)

  @derivatives       = derivatives
  @derivatives_mutex = Mutex.new if shrine_class.derivatives_options[:mutex]
end

Public Instance Methods

add_derivative(name, file, **options) click to toggle source

Uploads a given file and adds it to the derivatives hash.

attacher.derivatives #=>
# {
#   thumb: #<Shrine::UploadedFile>,
# }
attacher.add_derivative(:cropped, cropped)
attacher.derivatives #=>
# {
#   thumb: #<Shrine::UploadedFile>,
#   cropped: #<Shrine::UploadedFile>,
# }
# File lib/shrine/plugins/derivatives.rb, line 235
def add_derivative(name, file, **options)
  add_derivatives({ name => file }, **options)
  derivatives[name]
end
add_derivatives(files, **options) click to toggle source

Uploads given hash of files and adds uploaded files to the derivatives hash.

attacher.derivatives #=>
# {
#   thumb: #<Shrine::UploadedFile>,
# }
attacher.add_derivatives({ cropped: cropped })
attacher.derivatives #=>
# {
#   thumb: #<Shrine::UploadedFile>,
#   cropped: #<Shrine::UploadedFile>,
# }
# File lib/shrine/plugins/derivatives.rb, line 217
def add_derivatives(files, **options)
  new_derivatives = upload_derivatives(files, **options)
  merge_derivatives(new_derivatives)
  new_derivatives
end
change(*) click to toggle source

Clears derivatives when attachment changes.

attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
attacher.change(file)
attacher.derivatives #=> {}
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 462
def change(*)
  result = super
  set_derivatives({})
  result
end
create_derivatives(*args, storage: nil, **options) click to toggle source

Calls processor and adds returned derivatives.

Attacher.derivatives_processor :my_processor do |original|
  # ...
end

attacher.create_derivatives(:my_processor)
# File lib/shrine/plugins/derivatives.rb, line 199
def create_derivatives(*args, storage: nil, **options)
  files = process_derivatives(*args, **options)
  add_derivatives(files, storage: storage)
end
data() click to toggle source

Adds derivative data into the hash.

attacher.attach(io)
attacher.add_derivatives({ thumb: thumb })
attacher.data
#=>
# {
#   "id" => "...",
#   "storage" => "store",
#   "metadata" => { ... },
#   "derivatives" => {
#     "thumb" => {
#       "id" => "...",
#       "storage" => "store",
#       "metadata" => { ... },
#     }
#   }
# }
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 416
def data
  result = super

  if derivatives.any?
    result ||= {}
    result["derivatives"] = map_derivative(derivatives, transform_keys: :to_s) do |_, derivative|
      derivative.data
    end
  end

  result
end
delete_derivatives(derivatives = self.derivatives) click to toggle source

Deletes given hash of uploaded files.

attacher.delete_derivatives({ thumb: uploaded_file })
uploaded_file.exists? #=> false
# File lib/shrine/plugins/derivatives.rb, line 384
def delete_derivatives(derivatives = self.derivatives)
  map_derivative(derivatives) { |_, derivative| derivative.delete }
end
derivatives=(derivatives) click to toggle source

Sets a hash of derivatives.

attacher.derivatives = { thumb: Shrine.uploaded_file(...) }
attacher.derivatives #=> { thumb: #<Shrine::UploadedFile ...> }
# File lib/shrine/plugins/derivatives.rb, line 472
def derivatives=(derivatives)
  unless derivatives.is_a?(Hash)
    fail ArgumentError, "expected derivatives to be a Hash, got #{derivatives.inspect}"
  end

  @derivatives = derivatives
end
destroy() click to toggle source

In addition to deleting the main file it also deletes any derivatives.

attacher.add_derivatives({ thumb: thumb })
attacher.derivatives[:thumb].exists? #=> true
attacher.destroy
attacher.derivatives[:thumb].exists? #=> false
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 187
def destroy
  super
  delete_derivatives
end
get(*path) click to toggle source

Convenience method for accessing derivatives.

photo.image_derivatives[:thumb] #=> #<Shrine::UploadedFile>
# can be shortened to
photo.image(:thumb) #=> #<Shrine::UploadedFile>
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 120
def get(*path)
  return super if path.empty?

  get_derivatives(*path)
end
get_derivatives(*path) click to toggle source

Convenience method for accessing derivatives.

photo.image_derivatives.dig(:thumbnails, :large)
# can be shortened to
photo.image_derivatives(:thumbnails, :large)
# File lib/shrine/plugins/derivatives.rb, line 131
def get_derivatives(*path)
  return derivatives if path.empty?

  path = derivative_path(path)

  derivatives.dig(*path)
end
load_data(data) click to toggle source

Loads derivatives from data generated by ‘Attacher#data`.

attacher.load_data({
  "id" => "...",
  "storage" => "store",
  "metadata" => { ... },
  "derivatives" => {
    "thumb" => {
      "id" => "...",
      "storage" => "store",
      "metadata" => { ... },
    }
  }
})
attacher.file        #=> #<Shrine::UploadedFile>
attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 445
def load_data(data)
  data ||= {}
  data   = data.dup

  derivatives_data = data.delete("derivatives") || data.delete(:derivatives) || {}
  @derivatives     = shrine_class.derivatives(derivatives_data)

  data = nil if data.empty?

  super(data)
end
map_derivative(derivatives, **options, &block) click to toggle source

Iterates through nested derivatives and maps results.

attacher.map_derivative(derivatives) { |path, file| ... }
# File lib/shrine/plugins/derivatives.rb, line 483
def map_derivative(derivatives, **options, &block)
  shrine_class.map_derivative(derivatives, **options, &block)
end
merge_derivatives(new_derivatives) click to toggle source

Deep merges given uploaded derivatives with current derivatives.

attacher.derivatives #=> { one: #<Shrine::UploadedFile> }
attacher.merge_derivatives({ two: uploaded_file })
attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
# File lib/shrine/plugins/derivatives.rb, line 303
def merge_derivatives(new_derivatives)
  derivatives_synchronize do
    merged_derivatives = deep_merge_derivatives(derivatives, new_derivatives)
    set_derivatives(merged_derivatives)
  end
end
process_derivatives(processor_name = :default, source = nil, **options) click to toggle source

Downloads the attached file and calls the specified processor.

Attacher.derivatives_processor :thumbnails do |original|
  processor = ImageProcessing::MiniMagick.source(original)

  {
    small:  processor.resize_to_limit!(300, 300),
    medium: processor.resize_to_limit!(500, 500),
    large:  processor.resize_to_limit!(800, 800),
  }
end

attacher.process_derivatives(:thumbnails)
#=> { small: #<File:...>, medium: #<File:...>, large: #<File:...> }
# File lib/shrine/plugins/derivatives.rb, line 278
def process_derivatives(processor_name = :default, source = nil, **options)
  # handle receiving only source file without a processor
  unless processor_name.respond_to?(:to_sym)
    source         = processor_name
    processor_name = :default
  end

  source ||= file!

  processor_settings = self.class.derivatives_processor_settings(processor_name) || {}

  if processor_settings[:download]
    shrine_class.with_file(source) do |file|
      _process_derivatives(processor_name, file, **options)
    end
  else
    _process_derivatives(processor_name, source, **options)
  end
end
promote(**options) click to toggle source

In addition to promoting the main file, also promotes any cached derivatives. This is useful when these derivatives are being created as part of a direct upload.

attacher.assign(io)
attacher.add_derivative(:thumb, file, storage: :cache)
attacher.promote
attacher.stored?(attacher.derivatives[:thumb]) #=> true
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 162
def promote(**options)
  super
  promote_derivatives
  create_derivatives if create_derivatives_on_promote?
end
promote_derivatives(**options) click to toggle source

Uploads any cached derivatives to permanent storage.

# File lib/shrine/plugins/derivatives.rb, line 169
def promote_derivatives(**options)
  stored_derivatives = map_derivative(derivatives) do |path, derivative|
    if cached?(derivative)
      upload_derivative(path, derivative, **options)
    else
      derivative
    end
  end

  set_derivatives(stored_derivatives) unless derivatives == stored_derivatives
end
remove_derivative(path, **options) click to toggle source

Removes derivative with specified name from the derivatives hash.

attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
attacher.remove_derivative(:one) #=> #<Shrine::UploadedFile> (removed derivative)
attacher.derivatives #=> { two: #<Shrine::UploadedFile> }

Nested derivatives are also supported:

attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> } }
attacher.remove_derivative([:nested, :one]) #=> #<Shrine::UploadedFile> (removed derivative)
attacher.derivatives #=> { nested: { one: #<Shrine::UploadedFile> } }

The :delete option can be passed for deleting removed derivative:

attacher.derivatives #=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile> }
derivative = attacher.remove_derivatives(:two, delete: true)
derivative.exists? #=> false
# File lib/shrine/plugins/derivatives.rb, line 376
def remove_derivative(path, **options)
  remove_derivatives(path, **options).first
end
remove_derivatives(*paths, delete: false) click to toggle source

Removes derivatives with specified name from the derivatives hash.

attacher.derivatives
#=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }

attacher.remove_derivatives(:two, :three)
#=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)

attacher.derivatives
#=> { one: #<Shrine::UploadedFile> }

Nested derivatives are also supported:

attacher.derivatives
#=> { nested: { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> } }

attacher.remove_derivatives([:nested, :two], [:nested, :three])
#=> [#<Shrine::UploadedFile>, #<Shrine::UploadedFile>] (removed derivatives)

attacher.derivatives
#=> { nested: { one: #<Shrine::UploadedFile> } }

The :delete option can be passed for deleting removed derivatives:

attacher.derivatives
#=> { one: #<Shrine::UploadedFile>, two: #<Shrine::UploadedFile>, three: #<Shrine::UploadedFile> }

two, three = attacher.remove_derivatives(:two, :three, delete: true)

two.exists?   #=> false
three.exists? #=> false
# File lib/shrine/plugins/derivatives.rb, line 341
def remove_derivatives(*paths, delete: false)
  removed_derivatives = paths.map do |path|
    path = Array(path)

    if path.one?
      derivatives.delete(path.first)
    else
      derivatives.dig(*path[0..-2]).delete(path[-1])
    end
  end

  set_derivatives derivatives

  delete_derivatives(removed_derivatives) if delete

  removed_derivatives
end
set_derivatives(derivatives) click to toggle source

Sets the given hash of uploaded files as derivatives.

attacher.set_derivatives({ thumb: uploaded_file })
attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
# File lib/shrine/plugins/derivatives.rb, line 392
def set_derivatives(derivatives)
  self.derivatives = derivatives
  set file # trigger model write
  derivatives
end
upload_derivative(path, file, storage: nil, **options) click to toggle source

Uploads the given file and deletes it afterwards.

hash = attacher.upload_derivative(:thumb, thumb)
hash[:thumb] #=> #<Shrine::UploadedFile>
# File lib/shrine/plugins/derivatives.rb, line 254
def upload_derivative(path, file, storage: nil, **options)
  path      = derivative_path(path)
  storage ||= derivative_storage(path)

  file.open    if file.is_a?(Tempfile)       # refresh file descriptor
  file.binmode if file.respond_to?(:binmode) # ensure binary mode

  upload(file, storage, derivative: path, delete: true, action: :derivatives, **options)
end
upload_derivatives(files, **options) click to toggle source

Uploads given hash of files.

hash = attacher.upload_derivatives({ thumb: thumb })
hash[:thumb] #=> #<Shrine::UploadedFile>
# File lib/shrine/plugins/derivatives.rb, line 244
def upload_derivatives(files, **options)
  map_derivative(files) do |path, file|
    upload_derivative(path, file, **options)
  end
end
url(*path, **options) click to toggle source

Allows generating a URL to the derivative by passing the derivative name.

attacher.add_derivatives({ thumb: thumb })
attacher.url(:thumb) #=> "https://example.org/thumb.jpg"
Calls superclass method
# File lib/shrine/plugins/derivatives.rb, line 144
def url(*path, **options)
  return super if path.empty?

  path = derivative_path(path)

  url   = derivatives.dig(*path)&.url(**options)
  url ||= default_url(**options, derivative: path)
  url
end

Private Instance Methods

_process_derivatives(processor_name, source, **options) click to toggle source

Calls the derivatives processor with the source file and options.

# File lib/shrine/plugins/derivatives.rb, line 490
def _process_derivatives(processor_name, source, **options)
  processor = self.class.derivatives_processor(processor_name)

  return {} unless processor

  result = instrument_derivatives(processor_name, source, options) do
    instance_exec(source, **options, &processor)
  end

  unless result.is_a?(Hash)
    fail Error, "expected derivatives processor #{processor_name.inspect} to return a Hash, got #{result.inspect}"
  end

  result
end
create_derivatives_on_promote?() click to toggle source

Whether to automatically create derivatives on promotion

# File lib/shrine/plugins/derivatives.rb, line 544
def create_derivatives_on_promote?
  shrine_class.derivatives_options[:create_on_promote]
end
deep_merge_derivatives(o1, o2) click to toggle source

Deep merge nested hashes/arrays.

# File lib/shrine/plugins/derivatives.rb, line 533
def deep_merge_derivatives(o1, o2)
  if o1.is_a?(Hash) && o2.is_a?(Hash)
    o1.merge(o2) { |_, v1, v2| deep_merge_derivatives(v1, v2) }
  elsif o1.is_a?(Array) && o2.is_a?(Array)
    o1 + o2
  else
    o2
  end
end
derivative_path(path) click to toggle source

Returns symbolized array or single key.

# File lib/shrine/plugins/derivatives.rb, line 519
def derivative_path(path)
  path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
  path = path.first if path.one?
  path
end
derivative_storage(path) click to toggle source

Storage to which derivatives will be uploaded to by default.

# File lib/shrine/plugins/derivatives.rb, line 526
def derivative_storage(path)
  storage = self.class.derivatives_storage
  storage = instance_exec(path, &storage) if storage.respond_to?(:call)
  storage
end
derivatives_synchronize() { || ... } click to toggle source
# File lib/shrine/plugins/derivatives.rb, line 548
def derivatives_synchronize
  if @derivatives_mutex
    @derivatives_mutex.synchronize { yield }
  else
    yield
  end
end
instrument_derivatives(processor_name, source, processor_options) { || ... } click to toggle source

Sends a ‘derivatives.shrine` event for instrumentation plugin.

# File lib/shrine/plugins/derivatives.rb, line 507
def instrument_derivatives(processor_name, source, processor_options, &block)
  return yield unless shrine_class.respond_to?(:instrument)

  shrine_class.instrument(:derivatives, {
    processor:         processor_name,
    processor_options: processor_options,
    io:                source,
    attacher:          self,
  }, &block)
end