はじめに
一切かかわりのない方々が書いたコードを保守する機会がありました。
そこでファイルの保存にCarrierWaveを使っており、あまりみかけない使い方をしていて理解に時間がかかったので断片的にはなりますがまとめていきます。
つまづいたところ
CarrierWaveは利用されている割にはネットに情報がないイメージでした
以下が理解に時間がかかった箇所です
ファイルを複数あげたい
こちらの記事を参考にしました。
【Rails】Carrierwaveを用いて画像を複数アップロードする方法
ファイルを単体で上げられる前提で以下を修正しました。
複数ファイルを投稿できるように修正
= f.file_field :images, multiple: true
CarrierWaveで複数の写真を保存できるように修正
mount_uploaders :images, ImageUploader
ポイントは、uploaders
にすること
POSTしたパラメータを配列として受けとれるように修正
def book_params
params.require(:book).permit(:title, :body, { images: [] })
end
キャッシュを効かせたい
ファイルアップロードをしたときに、別の箇所(例えばタイトルを入力しないなど)でバリデーションが発生したときに、あげたファイルのデータが消えてしまうため、上げなおさないといけないという問題が起きます。そこでキャッシュを効かせる必要があり対応しました。
まず、attr_accesor
をモデルに追加します。
class Book
attr_accessor :images_cache
mount_uploaders :image, ImageUploader
end
attr_accesor
についてはRailsから入った人へ【attr_accessor】って?を見るとわかります。DBを使わないでカラムを定義することができるようになります。
つぎに、View側にhidden_field
を追加します。
<%= form_with @user, url: user_confirm_path, local: true do |f| %>
(省略)
<%= f.file_field %>
<%= f.hidden_field :images_cache %>
<%= f.submit %>
バリデーションで失敗したときにアップロードしたファイルをを@book.images_cache
に追加してから、newをレンダリングすることで、次のPOSTのときにparams[:images_cache]
から前にアップロードしたファイルのデータを取得することができます。
image_cache
への追加はCreateの最後で行います。
def create
@book = current_user.users.new(book_params)
if @book.save!
redirecto_to test_path
else
@book.images_cache = @book.images.map{ |image| images.cache_name }
render :new
end
end
このように保存に失敗したらキャッシュを保存するように設定します。
複数ファイルをアップロードしているので、imagesをmapで回して、cache_name
でキャッシュ用のIDを取得して配列にしてimage_cache
に保存しています。
_mounterについて
私が扱ったコードには_mounter
を利用したメソッドがありました。
def set_image
_mounter(:images).cache
end
def get_image
_mounter(:images).uploaders
end
いきなりでてきて戸惑いましたが、以下のコードを参考にすることで理解ができました。
_mounterを使うと以下のファイルのメソッドが呼び出されます。
_mounter(:images).cache
で実際にCarrierWaveでファイルを保存して、_mounter(:images).uploaders
で保存したファイルに関するUploadersオブジェクトを取得することができます。このオブジェクトを用いてCreateなどをするとCarrierWaveのレコードが作成されます。
キャッシュの復元
複数ファイルのキャッシュの復元に以下を行っていました。
def get_cache_images
_mounter(:images).cache_names=images_cache
end
images_cache
はhiddenで保持したキャッシュです。
ここでも_mounterを利用して、cache_nameメソッドを呼び出してUploaderオブジェクトを取得しています。
以下は_mounterの該当するコードです。
def cache_names=(cache_names)
cache_names = cache_names.reject(&:blank?)
return if cache_names.blank?
clear_unstaged
cache_names.each do |cache_name|
begin
uploader = blank_uploader
uploader.retrieve_from_cache!(cache_name)
@uploaders << uploader
rescue CarrierWave::InvalidParameter
# ignore
end
end
end
_mounter(:images).cache_names=images_cache
とすることでgetterを呼び出します。左辺に右辺を入れているように見えるので注意が必要です。メソッドを呼び出しているだけです。
これでキャッシュの復元ができるようになります。復元はuploader.retrieve_from_cache!(cache_name)
の箇所で実際には行っています。
名前が取れない
images.map{ |i| i[:name]}
で取れました。ハッシュでとるのと複数の考慮が必要でした
おわりに
コードに関しては適当に書いているものなので動くことは保証できませんのでご理解ください。
今回はCarrierWaveについての考え方や使い方をまとめました。バックエンドを気にせずに簡単利用することができるのがよいところですが、カスタマイズもうまくできることがわかりました。
参考文献