Rails 6.1 つまみ食い② : ActiveStorage の永続的なURL

おはようございます!

2021年はもっとJSと仲良くなりたい @shutooike です!

今回試すのは ActiveStorage の Permanent URLs です。

セットアップ

blog.ingage.jp

前回の記事↑で作った Rails アプリを今回も使います!

まず ActiveStorage をインストールします

$ dip rails active_storage:install
Creating test_rails_latest_backend_run ... done
Copied migration 20210119162125_create_active_storage_tables.active_storage.rb from active_storage
$ dip rails db:migrate
Creating test_rails_latest_backend_run ... done
== 20210119162125 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {})
   -> 0.0406s
-- create_table(:active_storage_attachments, {})
   -> 0.0151s
-- create_table(:active_storage_variant_records, {})
   -> 0.0351s
== 20210119162125 CreateActiveStorageTables: migrated (0.0931s) ===============

前回作った Post に画像を添付できるようにします。

app/models/post.rb

class Post < ApplicationRecord
  has_many :comments, dependent: :destroy_async
  has_one_attached :image # <- 追加
end

app/controllers/posts_controller.rb

class PostsController < ApplicationController
.
.
  private
.
.
    # Only allow a list of trusted parameters through.
    def post_params
      params.require(:post).permit(:title, :body, :image) # <- :image を追加
    end
end

app/views/posts/_form.html.erb

.
.
  <div class="field">
    <%= form.label :image %>
    <%= form.file_field :image %>
  </div>
.
.

app/views/posts/show.html.erb

.
.
<div>
  <p><b>Image:</b></p>
  <%= image_tag @post.image %>
</div>
.
.

f:id:shutooike:20210124190158p:plain
新規 Post 作成画面

Create Post を押下すると

f:id:shutooike:20210124190232p:plain
Post 詳細画面

これで Post に画像を添付できるようになりました!

永続的なURL

これまで

ActiveStorage 6.0 までは Blob#service_url は有効期限付きの一時的なURLを返していました。

$ dip rails c
Creating test_rails_latest_backend_run ... done
Running via Spring preloader in process 14
Loading development environment (Rails 6.1.0)
irb(main):001:0> ActiveStorage::Current.host = 'http://localhost:3000'
=> "http://localhost:3000"
irb(main):002:0> Post.last.image.service_url
   (1.4ms)  SELECT sqlite_version(*)
  Post Load (3.6ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (2.7ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 4], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (3.1ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
DEPRECATION WARNING: service_url is deprecated and will be removed from Rails 6.2 (use url instead) (called from irb_binding at (irb):2)
  Disk Storage (10.6ms) Generated URL for file at key: hhq2ryr88ot5hkkepsy2xxg7jgxo (http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhhR2h4TW5KNWNqZzRiM1ExYUd0clpYQnplVEo0ZUdjM2FtZDRid1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2RW14dlkyRnNYM0J5YVhaaGRHVT0iLCJleHAiOiIyMDIxLTAxLTI0VDE0OjMyOjE5LjczNFoiLCJwdXIiOiJibG9iX2tleSJ9fQ==--dfa4689e26923e9d0783baaad60aab40953870f4/ingage.png)
=> "http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhhR2h4TW5KNWNqZzRiM1ExYUd0clpYQnplVEo0ZUdjM2FtZDRid1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2RW14dlkyRnNYM0J5YVhaaGRHVT0iLCJleHAiOiIyMDIxLTAxLTI0VDE0OjMyOjE5LjczNFoiLCJwdXIiOiJibG9iX2tleSJ9fQ==--dfa4689e26923e9d0783baaad60aab40953870f4/ingage.png"

なので Blob#service_url が返した URL を開くと最初はこのように表示できますが、

f:id:shutooike:20210124232957p:plain
Blob#service_url が返したURL

5分すると・・・

f:id:shutooike:20210124233244p:plain
5分後

有効期限が切れて表示できなくなります。

irb(main):005:0> url1 = Post.last.image.url
  Post Load (3.4ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (3.5ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (7.8ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  Disk Storage (1.3ms) Generated URL for file at key: 8lrdasfec0ti6trsc2z2y1bsjibj (http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2Q214dlkyRnMiLCJleHAiOiIyMDIxLTAxLTI0VDE1OjI1OjIxLjc4MloiLCJwdXIiOiJibG9iX2tleSJ9fQ==--2258a7523a81600867d2146764da7a9dce6ebfbd/ingage.png)
=> "http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJ..."
irb(main):006:0> sleep 5.minutes
=> 300
irb(main):007:0> url2 = Post.last.image.url
  Post Load (11.4ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (23.4ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (7.6ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  Disk Storage (1.3ms) Generated URL for file at key: 8lrdasfec0ti6trsc2z2y1bsjibj (http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2Q214dlkyRnMiLCJleHAiOiIyMDIxLTAxLTI0VDE1OjMxOjA2LjAzN1oiLCJwdXIiOiJibG9iX2tleSJ9fQ==--ae964cba7705144602bb150cd790f35c6be9d0f4/ingage.png)
=> "http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJ..."
irb(main):008:0> url1 == url2
=> false

Rails Console での検証↑

6.1 から

github.com

6.1 からは config/storage.yml で public: true | false が設定でき、true の場合は永続的なURLが返されるようになりました!

まず public: true を設定に追加します

config/storage.yml

.
.

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>
  public: true
.
.

Rails Console で検証してみます。

$ dip rails c
Creating test_rails_latest_backend_run ... done
Running via Spring preloader in process 14
Loading development environment (Rails 6.1.0)
irb(main):001:0> ActiveStorage::Current.host = 'http://localhost:3000'
=> "http://localhost:3000"
irb(main):002:0> url1 = Post.last.image.url
   (2.4ms)  SELECT sqlite_version(*)
  Post Load (5.7ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (3.7ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (5.7ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  Disk Storage (7.7ms) Generated URL for file at key: 8lrdasfec0ti6trsc2z2y1bsjibj (http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2Q214dlkyRnMiLCJleHAiOm51bGwsInB1ciI6ImJsb2Jfa2V5In19--e502a110ec7258265e886e90ae501d254087ca1b/ingage.png)
=> "http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJ..."
irb(main):003:0> sleep 5.minutes
=> 300
irb(main):004:0> url2 = Post.last.image.url
  Post Load (72.4ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (7.0ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 6], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (3.5ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
  Disk Storage (2.8ms) Generated URL for file at key: 8lrdasfec0ti6trsc2z2y1bsjibj (http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2Q214dlkyRnMiLCJleHAiOm51bGwsInB1ciI6ImJsb2Jfa2V5In19--e502a110ec7258265e886e90ae501d254087ca1b/ingage.png)
=> "http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhPR3h5WkdGelptVmpNSFJwTm5SeWMyTXllako1TVdKemFtbGlhZ1k2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJ..."
irb(main):005:0> url1 == url2
=> true

5分経ってもURLは変わっていないですね!🎉

ちなみに Rails 6.1 からは Blob#service_url が非推奨になり、代わりに Blob#url を使います。*1

次回は ActiveStorage が multiple storage services に対応したことを書く予定です!

ではまた!

おまけ

Blob#service_url を Rails Console で試してみると URI::InvalidURIError (bad URI(is not URI?): nil) というエラーが出ました。

$ dip rails c
Creating test_rails_latest_backend_run ... done
Running via Spring preloader in process 14
Loading development environment (Rails 6.1.0)
irb(main):001:0> Post.last.image.blob.service_url
   (1.2ms)  SELECT sqlite_version(*)
  Post Load (4.6ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (5.6ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 5], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (5.3ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
DEPRECATION WARNING: service_url is deprecated and will be removed from Rails 6.2 (use url instead) (called from irb_binding at (irb):1)
  Disk Storage (9.8ms) Generated URL for file at key: d7j7u5gnrvzl73uhlse3ch62n7x1 ()
Traceback (most recent call last):
        1: from (irb):1
URI::InvalidURIError (bad URI(is not URI?): nil)

どうやら ActiveStorage::Current.hostnil ぽいので値を入れてあげると無事 URL を返してくれるようになりました!

irb(main):002:0> ActiveStorage::Current.host = 'http://localhost:3000'
=> "http://localhost:3000"
irb(main):003:0> Post.last.image.blob.service_url
  Post Load (8.5ms)  SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT ?  [["LIMIT", 1]]
  ActiveStorage::Attachment Load (4.4ms)  SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = ? AND "active_storage_attachments"."record_type" = ? AND "active_storage_attachments"."name" = ? LIMIT ?  [["record_id", 5], ["record_type", "Post"], ["name", "image"], ["LIMIT", 1]]
  ActiveStorage::Blob Load (4.0ms)  SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
DEPRECATION WARNING: service_url is deprecated and will be removed from Rails 6.2 (use url instead) (called from irb_binding at (irb):3)
  Disk Storage (4.6ms) Generated URL for file at key: d7j7u5gnrvzl73uhlse3ch62n7x1 (http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhaRGRxTjNVMVoyNXlkbnBzTnpOMWFHeHpaVE5qYURZeWJqZDRNUVk2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2Q214dlkyRnMiLCJleHAiOiIyMDIxLTAxLTI0VDE1OjA1OjUxLjU4NloiLCJwdXIiOiJibG9iX2tleSJ9fQ==--bd4bd9aa30ad12d6c2512cf633f94ad49ae22a80/ingage.png)
=> "http://localhost:3000/rails/active_storage/disk/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdDVG9JYTJWNVNTSWhaRGRxTjNVMVoyNXlkbnBzTnpOMWFHeHpaVE5qYURZeWJqZDRNUVk2QmtWVU9oQmthWE53YjNOcGRHbHZia2tpUDJsdWJHbHVaVHNnWm1sc1pXNWhiV1U5SW1sdVoyRm5aUzV3Ym1jaU95Qm1hV3hsYm1GdFpTbzlWVlJHTFRnbkoybHVaMkZuWlM1d2JtY0dPd1pVT2hGamIyNTBaVzUwWDNSNWNHVkpJZzVwYldGblpTOXdibWNHT3daVU9oRnpaWEoyYVdObFgyNWhiV1U2Q214dlkyRnMiLCJleHAiOiIyMDIxLTAxLTI0VDE1OjA1OjUxLjU4NloiLCJwdXIiOiJibG9iX2tleSJ9fQ==--bd4bd9aa30ad12d6c2512cf633f94ad49ae22a80/ingage.png"

検証していないですが、Service に Disk を使っているときに開発環境で起こるみたいです。誰かの参考になれば 🙏 🙏

*1:記事内ではごっちゃになってます。すいません!