なにがしたいのか
S3にそのまま上げても使えはするんですが、大人の事情で1つのバケットしか使えない等の理由でディレクトリに入れたい時あると思います。
S3にディレクトリの概念は無いんですがそこは便宜上ディレクトリで行かせてください。
確かにhas_one_attached
でもrecord.attach(key: 'images/example.png')
の形でキーは使えます。
ただ、楽をするためにRailsを使ってるんだ!ってスタンスの自分としては気に食わないわけです。
form_withでフォーム作ってfile_field使ってパラメーター渡すだけで簡単にアップロードしたくないですか??私はしたいです。
ActiveStorageでフォルダはサポートされていない
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::CreateOne
のsave
メソッドが呼ばれ、そこから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
参考になれば幸いです。