LoginSignup
17
13

More than 3 years have passed since last update.

Active Storageの基本で困ったこと

Posted at

今回始めてActive Storageを触る機会があったのでその備忘録です。

テスト環境

  • Ruby 2.6.3
  • Rails 6.0.0.rc1

Rails 6.0.0.rc1である理由は特にないです。
今回たまたま機会があったからです。

URLが欲しい

オプションや設定が足らないせいなのでしょうか?ビューでurl_forを使うと/railsから始まるパスが返って来て困りました。コンソールだとURLが返って来てるのできっと解決策あると思うのですが、とりあえずpolymorphic_urlで無事取得できました。

config/environments/development.rb
Rails.application.configure do
  ...
  default_url_options[:host] = "localhost:3000"
end
irb(main):001:0> app.url_for Test.first.file
=> "http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--f21f99c34dd58d1224cad0c1cf7c68d31c4e4e85/test.txt"
irb(main):002:0> app.polymorphic_url Test.first.file
=> "http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCZz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--f21f99c34dd58d1224cad0c1cf7c68d31c4e4e85/test.txt"

アップロードしたファイルをダウンロードしたい

Active Storageでアップロードしたファイルをダウンロードしたい場合、これまではActiveStorage::Downloadingをインクルードしていたようですが、どうもRails 6からはDeprecatedらしく、さらに6.1では消されちゃうようです。

ですが、代わりにActiveStorage::Blob#openが使えるみたいです。
こちらは特別何かをインクルードする必要もなさそうです。

app/model/test.rb
class Test < ApplicationRecord
  has_one_attached :file
end
irb(main):001:0> t = Test.new test: "test"
=> #<Test id: nil, test: "test", created_at: nil, updated_at: nil>
irb(main):002:0> f = open "tmp/test.txt"
=> #<File:tmp/test.txt>
irb(main):003:0> t.file.attach io: f, filename: File.basename(f)
=> #<ActiveStorage::Attached::Changes::CreateOne:0x00007fabf9f8b6d0 @name="file", @record=#<Test id: nil, test: "test", created_at: nil, updated_at: nil>, @attachable={:io=>#<File:tmp/test.txt>, :filename=>"test.txt"}>
irb(main):004:0> t.save

...

irb(main):009:0> t.file.blob.open { |tmp| tmp.path }
=> "/var/folders/q1/4wl3c44s73jg_s34cbnbttk4w4lkcd/T/ActiveStorage-1-20190526-24240-ebxwry.txt"
irb(main):010:0> t.file.blob.open { |tmp| tmp.read }
=> "test\n"
irb(main):011:0> File.exist? t.file.blob.open { |tmp| tmp.path }
=> false

ブロックを抜けたらちゃんとファイルが削除されているので、Tempfile.createを使っているんですかね。
わざわざ自分で URI.open を使って書いた後に知ったのでとても悔しい気持ちになりましたw

複数ファイルをダウンロードして使いたい

(この話はActive Storageとは直接関係ないです)

仕様や設計の都合によって1レコード・1ファイルとしてアップロードする必要があったのですが、同時にwhereで取得したレコードに紐づくファイルをまとめてダウンロードして一度にローカルで処理する必要がありました。しかしActiveStorage::Blob#openはブロックを抜けた後ちゃんとファイルが存在しなくなるので、全てのファイルが存在する状況をどうやって作るかが問題になりました。

今回は以下のように再帰を用いてwhereで取得したレコードのファイルを全て配列に加えていき、全てのファイルが配列に加えられたら与えられたブロックを評価するという手法にしました。

app/model/test.rb
class Test < ApplicationRecord

  has_one_attached :file

  def on_local
    file.blob.open { |tmp| yield tmp }
  end

  class << self

    def on_local_recursive instances, files, &block
      return block.call files unless files.count < instances.count
      instances[files.count].on_local do |file|
        on_local_recursive instances, files + [file], &block
      end
    end

    def on_local &block
      on_local_recursive all, [], &block
    end
  end
end
irb(main):001:0> Test.count
=> 14
irb(main):002:0> Test.where(id: 1..2).ids
=> [1, 2]
irb(main):003:0> Test.where(id: 1..2).on_local { |files| files.map(&:path) }
=> ["/var/folders/q1/4wl3c44s73jg_s34cbnbttk4w4lkcd/T/ActiveStorage-1-20190616-15932-1lovhy.txt", "/var/folders/q1/4wl3c44s73jg_s34cbnbttk4w4lkcd/T/ActiveStorage-2-20190616-15932-1ldwync.txt"]
irb(main):004:0> Test.where(id: 1..2).on_local { |files| files.all? { |file| File.exist? file } }
=> true
irb(main):005:0> Test.where(id: 1..2).on_local { |files| files.map(&:path) }.map { |file| File.exist? file }
=> [false, false]

この手法の利点はActive Storageが提供するActiveStorage::Blob#openをそのまま使っているので実装が楽なことと、使い方もブロックを渡す形が同じであること、かつブロックを抜けたらファイルが削除される挙動も同じであることだと思います。ただもっと良い方法があればぜひとも教えてもらいたいですね〜。

感想

Active Storageは思っていたよりも全然使いやすくて良かったです。まだまだ基本的なところしか使っていないだけかもしれませんがw

17
13
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
13