【Rails】ActiveStorageで、既存のオブジェクトに紐づいたファイルを他のオブジェクトに流用して紐づける方法
まとめ
UserモデルとAuthorモデルがある- 既存のユーザー(
user)にアタッチしてあるファイルを、そのまま流用して別の「著者」(author)にアタッチしたい
上記のことが、以下のコードで可能という記事です。
author.authors_image.attach(user.users_image.blob)
やってみる
rails newして、User/Authorモデルを作る- ActiveStorageを導入して、
has_one_attachedでモデルとファイルを紐づける設定をする - ビューで画像を表示できるようにしておく
- 下のサンプルコードは
Userのビューのものだけだが、Authorのビューでも同じことをしておく
- 下のサンプルコードは
class User < ApplicationRecord has_one_attached :users_image end class Author < ApplicationRecord has_one_attached :authors_image end
<div id="<%= dom_id user %>">
<p>
<strong>Name:</strong>
<%= user.name %>
</p>
<p>
<strong>image:</strong>
<% if user.users_image.present? %>
<%= image_tag user.users_image %>
<% end %>
</p>
</div>
適当にユーザーを作って、画像をアタッチして保存。

次に、適当に著者を作成(この時点では画像は持たせない)。

Railsコンソールを開いて、ユーザーと著者を取得し、冒頭のコマンドを実行。
$ user = User.first $ author = Author.first $ author.authors_image.attach(user.users_image.blob)
attach-test-app(dev)> author.authors_image.attach(user.users_image.blob)
ActiveStorage::Attachment Load (0.1ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 2 AND "active_storage_attachments"."record_type" = 'User' AND "active_storage_attachments"."name" = 'users_image' LIMIT 1 /*application='AttachTestApp'*/
ActiveStorage::Blob Load (0.1ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = 2 LIMIT 1 /*application='AttachTestApp'*/
TRANSACTION (0.5ms) BEGIN immediate TRANSACTION /*application='AttachTestApp'*/
ActiveStorage::Blob Load (4.4ms) SELECT "active_storage_blobs".* FROM "active_storage_blobs" INNER JOIN "active_storage_attachments" ON "active_storage_blobs"."id" = "active_storage_attachments"."blob_id" WHERE "active_storage_attachments"."record_id" = 3 AND "active_storage_attachments"."record_type" = 'Author' AND "active_storage_attachments"."name" = 'authors_image' LIMIT 1 /*application='AttachTestApp'*/
ActiveStorage::Attachment Load (0.2ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = 3 AND "active_storage_attachments"."record_type" = 'Author' AND "active_storage_attachments"."name" = 'authors_image' LIMIT 1 /*application='AttachTestApp'*/
ActiveStorage::Attachment Create (0.2ms) INSERT INTO "active_storage_attachments" ("name", "record_type", "record_id", "blob_id", "created_at") VALUES ('authors_image', 'Author', 3, 2, '2025-02-23 02:56:27.784094') RETURNING "id" /*application='AttachTestApp'*/
Author Update (0.1ms) UPDATE "authors" SET "updated_at" = '2025-02-23 02:56:27.788402' WHERE "authors"."id" = 3 /*application='AttachTestApp'*/
TRANSACTION (0.1ms) COMMIT TRANSACTION /*application='AttachTestApp'*/
=>
#<ActiveStorage::Attached::One:0x00007f509baccea0
@name="authors_image",
@record=
#<Author:0x00007f509b672620
id: 3,
name: "著者です",
created_at: "2025-02-23 02:52:29.138843000 +0000",
updated_at: "2025-02-23 02:56:27.788402000 +0000">>
著者のページを更新すると、ビュー上のコードimage_tag(author.authors_image)によってユーザーと同じ画像が表示されます。

ユーザー / 著者に紐づく画像が同じものであることは、Railsコンソールからも確かめられます。
attach-test-app(dev)> user.users_image.blob == author.authors_image.blob => true
なぜできるのか
ActiveStorageでオブジェクトとファイルを紐づける際、オブジェクト(今回であればユーザーや著者)とファイル(1)は下のように中間テーブル(2)を通して結ばれます。
- ファイルに関するデータのためのテーブル:
active_storage_blobs - 中間テーブル:
active_storage_attachments- 1のblobsとオブジェクトを接続する役割
冒頭のコードは、「著者オブジェクトの画像として、ユーザーオブジェクトに紐づく画像のファイルデータ(blob)をアタッチしてね」という意味だったわけですね(attach(user.users_image)だとうまくいきません)。
author.authors_image.attach(user.users_image.blob)
※更に調べてみると、user.users_imageで取得できるオブジェクトのクラスはActiveStorage::AttachmentではなくActiveStorage::Attached::Oneです。このオブジェクトはアタッチされたファイルのラッパーのようなクラスのようで、ファイルがアタッチされているかを確認するattached?メソッドもここに定義されていました。
実行結果のSQLでも分かる通り、このときに作られているのは中間テーブルのレコードだけです(すでにストレージにある画像を著書に新たに紐づけるためのレコード)。
実際、実行後の中間テーブルのレコードの数は2個ですが、ファイルデータのためのテーブルのそれは1個のみ(画像自体は1枚しかアップロードしていない)であることが確認できます。
attach-test-app(dev)> ActiveStorage::Attachment.count ActiveStorage::Attachment Count (0.7ms) SELECT COUNT(*) FROM "active_storage_attachments" /*application='AttachTestApp'*/ => 2 attach-test-app(dev)> ActiveStorage::Blob.count ActiveStorage::Blob Count (0.9ms) SELECT COUNT(*) FROM "active_storage_blobs" /*application='AttachTestApp'*/ => 1
同じ画像を使うために再度ファイルをアップロードする必要がないのはよさそうですね。ただ、同じ画像を使い回すこと自体には「どちらか一方で削除・更新が起こったら?」という問題が付いてきそうなので、注意が必要かなとも思いました。
なんでこんな記事を書いたのか
実際にこういうことができないかを考える機会があり、さっと調べたところ日本語の記事がパッと出てこなかったので書いておこうと思ったためです。
Railsのリポジトリでは英語でこれについてのやりとりがなされていましたので、大変参考にさせていただきました。またActiveStorageの構造の話は『パーフェクトRuby on Rails』にもしっかり書かれており、大変助かりました。
以上です。