1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Ruby on RailsAdvent Calendar 2022

Day 13

ActiveStorage + has_one_attached + S3でもサブディレクトリに入れたい!

Last updated at Posted at 2022-12-12

なにがしたいのか

S3にそのまま上げても使えはするんですが、大人の事情で1つのバケットしか使えない等の理由でディレクトリに入れたい時あると思います。
S3にディレクトリの概念は無いんですがそこは便宜上ディレクトリで行かせてください。

確かにhas_one_attachedでもrecord.attach(key: 'images/example.png')の形でキーは使えます。
ただ、楽をするためにRailsを使ってるんだ!ってスタンスの自分としては気に食わないわけです。

form_withでフォーム作ってfile_field使ってパラメーター渡すだけで簡単にアップロードしたくないですか??私はしたいです。

ActiveStorageでフォルダはサポートされていない

image.png
https://github.com/rails/rails/issues/32790

バッサリですね。

じゃあどうするか

こうです
メソッドオーバーライドします。

S3以外のサービスでちゃんと動くかは試してないので分からないですが、とりあえずこれでいけます。

class MyModel < ApplicationRecord
  has_one_attached :image

  def image_blob=(blob)
    blob.key = "images/#{blob.key}"
    super(blob)
  end
end

解説

さっさと結論出してしまいましたが一応解説しておきます。
アトリビュート名はimageを例にして読みやすく書き換えてます。

has_one_attachedは何をしているのか?

has_one_attachedはマクロです。
メソッド定義を読めば一目瞭然ですが、アソシエーションやメソッドを定義しています。

これを追って行きます。

アタッチ時の処理

先程のマクロで定義しているここでメソッド呼んでるのでここから追って行きます。

def image=(attachable)
  attachment_changes["image"] =
    if attachable.nil?
      ActiveStorage::Attached::Changes::DeleteOne.new("image", self)
    else
      ActiveStorage::Attached::Changes::CreateOne.new("image", self, attachable)
    end
end

今回は作成時の処理を見たいので更にActiveStorage::Attached::Changes::CreateOneを掘り下げてみます。

おやおや...目ぼしい処理がありますね...

def save
  record.public_send("#{name}_attachment=", attachment)
  record.public_send("#{name}_blob=", blob)
end

これはhas_one_attachedメソッドで定義されるafter_saveコールバックで呼び出されるメソッドです。
つまり、after_saveコールバックが発火するとActiveStorage::Attached::Changes::CreateOnesaveメソッドが呼ばれ、そこからimage_blob=メソッドが呼ばれます。

ActiveStorageはblob.keyの値が保存されるファイル名になる仕様のため、image_blob=メソッドをオーバライドしてkeyを書き換えてしまえばその名前でファイルが作成されるわけです。

また、S3の場合folder/image.pngのようにスラッシュで区切るとディレクトリ的な扱いになるのでそれを利用します。
なのでそれらを利用するとこうなるかと

class MyModel < ApplicationRecord
  has_one_attached :image

  def image_blob=(blob)
    blob.key = "images/#{blob.key}"
    super(blob)
  end
end

参考になれば幸いです。

1
0
3

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?