はじめに
CarrierWaveでファイルをアップロードできるようにしたモデルから作ったフォームオブジェクトでファイルをアップロードすると、アップロード先が違ってしまい参照できなくなってしまいました。
少し工夫して参照できるようにしたので、同じようなことで困ってましたら、参考にしてください。
ソース
修正前: https://github.com/ken1flan/carrierwave_store_dir_sample
修正: https://github.com/ken1flan/carrierwave_store_dir_sample/pull/2
修正前のコード
Articleと、それを継承したForm::Articleというフォームオブジェクトがあったとします。
class Article < ApplicationRecord
mount_uploader :image, ImageUploader
end
class Form::Article < Article
end
ArticleがマウントしているのはCarrierWaveのジェネレータで作ったままのものです。
class ImageUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# この下は全部コメントだったので省略
end
フォームオブジェクトでファイルをアップロードすると参照できない
Form::Article
でレコードを作成すると、uploads/form/article
以下にアップロードされてしまい…。
原因
ImageUploader#store_dir
の実装を見てみると、クラス名をスネークケースにしたものをディレクトリ名に採用しているからでした。フォームオブジェクトだとその部分はform/article
になってしまいます。
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
修正
ApplicationRecord
にモデル名をスネークケースにして返すメソッドを準備しておいて…
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
def self.carrierwave_store_id
to_s.underscore
end
end
それを使うようにImageUploader#store_dir
を書き換えて…
def store_dir
"uploads/#{model.class.carrierwave_store_id}/#{mounted_as}/#{model.id}"
end
フォームオブジェクトでは'article'
を返すようにオーバーライドするようにしました。
class Form::Article < Article
def self.carrierwave_store_id
'article'
end
end
結果
想定通りのディレクトリに保存できて、無事に参照できるようになりました。
おわりに
Rails5でモデルの継承元がActiveRecord::BaseからApplicationRecordに変わったのですが、これが地味に助かることが多くて…。
長く広く使われているフレームワークの強みを感じました。