追記
Rails-5.2より新しいリリースでは ActiveStorage::Blob#open
という便利メソッドが追加されてActiveStorage::Downloading
がDeprecatedになっている。
- ActiveStorage 5.2
ActiveStorageでアップロードしたファイルをバッチなどで加工したり中身を分析したい場合の話。
ActiveStorage::Variant
やActiveStorage::Previewer
を読む限り、ActiveStorage::Downloading
を使うのがいいらしい。
使い方
インスタンスメソッドblob
を持つクラスにincludeすればOK。
class User < ApplicationRecord
has_one_attached :avatar
end
class Foo
include ActiveStorage::Downloading
def initialize(user)
@user = user
end
# ActiveStorage::Downloadingを使うためにはblobが必要。
def blob
user.avatar.attachment.blob
end
def run
download_blob_to_tempfile do |file|
# リモート(service: :localでも)から file にblobの内容をダウンロードしてくれる
# このブロックの中で加工なり操作を行う。
end
end
end
ActiveStorageのソースコード内での使い方
ActiveStorage::Variant
での使い方。MiniMagick::Image.create
は内部でtempfileを用意しているのでActiveStorage::Downloading#download_blob_to
を使っている。
class ActiveStorage::Variant
include ActiveStorage::Downloading
attr_reader :blob, :variation
delegate :service, to: :blob
def initialize(blob, variation_or_variation_key)
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation_or_variation_key)
end
private
def process
open_image do |image|
transform image
format image
upload image
end
end
def open_image(&block)
image = download_image
begin
yield image
ensure
image.destroy!
end
end
def download_image
require "mini_magick"
MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) }
end
end
ActiveStorage::Previewer::VideoPreviewer
での使い方。こっちはdownload_blob_to_tempfile
を使っている。
module ActiveStorage
class Previewer::VideoPreviewer < Previewer
def self.accept?(blob)
blob.video?
end
def preview
download_blob_to_tempfile do |input|
draw_relevant_frame_from input do |output|
yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
end
end
end
private
def draw_relevant_frame_from(file, &block)
draw ffmpeg_path, "-i", file.path, "-y", "-vcodec", "png",
"-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
end
def ffmpeg_path
ActiveStorage.paths[:ffmpeg] || "ffmpeg"
end
end
end
初めはpreviewの中のyieldでキーワード引数?って勘違いしていましたが、これはHashとして処理されてるだけっぽいです(ちゃんとソースコードを追ってはいない。最終的にはメソッドのキーワード引数として渡されているっぽいけど)
# 関連しそうな場所を抜粋
class ActiveStorage::Preview
private
def process
previewer.preview { |attachable| image.attach(attachable) }
end
end
module ActiveStorage
class Attached::One < Attached
def attach(attachable)
blob_was = blob if attached?
blob = create_blob_from(attachable)
end
end
end
module ActiveStorage
class Attached
private
def create_blob_from(attachable)
case attachable
when Hash
ActiveStorage::Blob.create_after_upload!(attachable)
end
end
end
end
class ActiveStorage::Blob < ActiveRecord::Base
class << self
def create_after_upload!(io:, filename:, content_type: nil, metadata: nil)
build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!)
end
end
end