Help us understand the problem. What is going on with this article?

ActiveStorageでblobのファイルを加工したい場合はActiveStorage::Downloadingをつかう

More than 1 year has passed since last update.

追記

Rails-5.2より新しいリリースでは ActiveStorage::Blob#open という便利メソッドが追加されてActiveStorage::Downloading がDeprecatedになっている。


  • ActiveStorage 5.2

ActiveStorageでアップロードしたファイルをバッチなどで加工したり中身を分析したい場合の話。
ActiveStorage::VariantActiveStorage::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を使っている。

activestorage/app/models/active_storage/variant.rb
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を使っている。

activestorage/lib/active_storage/previewer/video_previewer.rb
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
troter
I'm a horse racing fan, a programmer, a mercurial evangelist in Japan. Live in #mercurialjp #TokyoMercurial .
http://troter.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした