こんにちは、おーしまです。
今回は、自動車レビューアプリの画像編集機能が一応成功したので、報告します。
問題点については、こちら
tomo-bb-aki0117115.hatenablog.com
簡単に、問題点について説明すると、
編集画面で画像を変更しない場合、そのまま前回保存した画像が維持されるが、
画像を変更した場合、前回で保存されていた画像が消えてしまう
ということでした。
前の記事では、解決策として、画像を選択するたび、jsonでデータを送信し、データベースに保存すると書いていましたが、別の方法を思いついたので、そちらで実装しました。
解決策は、
1、edit画面に遷移した時に、元々保存されていた、画像のプレビュー表示のidを取得して、配列を作る。
2、古い画像が、削除されたら、配列からそのidを削除していく。
3、送られた配列から、古い画像のどの画像を残すのか分かる。
4、新しい画像と、残す画像を足し合わせて保存する。
という流れです。
1、edit画面に遷移した時に、元々保存されていた、画像のプレビュー表示のidを取得して、配列を作る。
画像のプレビューの上に新しく、formを作ることで、パラメーターとして送ることができるようにしました。
本来は、display: none;で隠れています。
このレビューは画像が、3枚保存されていたので、[0, 1, 2]という感じで、javascriptで配列をformに入れています。
2、古い画像が、削除されたら、配列からそのidを削除していく。
真ん中の、プレビュー画像を削除すると、同時にformの中身の数字も削除されるようにします。
もし、これで、送られれば、古い画像の[0][2]を残せば良いことが分かりますね。
3、送られた配列から、古い画像のどの画像を残すのか分かる。
cars_contoroller.rb
def update @form = SaveCarsTag.new(car_params, car: @car) #~~~~~~~~~~ここから~~~~~~~~~~~~ old_images = [] old_images_id = params[:car][:old_ids].split(",") old_images_id.each do |id| old_images << @car.images[id.to_i] end #~~~~~~~~~~ここまで~~~~~~~~~~~~ tag_list = params[:car][:name].split(",") if @form.valid? @form.save(tag_list, old_images) return redirect_to car_path(@car) else render :edit end end
params[:car][:old_ids].split(",")で文字列で送られてきたものを、配列化します。
すでに保存されている画像と同じ順番で並んでいるので、each文で現在保存されている画像をold_imagesという配列に入れます。
@car.images[id.to_i]で、元々のimagesの配列の何番目かを選んでいます。
@form.save(tag_list, old_images)でsaveメソッドにold_imagesを運びます。
4、新しい画像と、残す画像を足し合わせて保存する。
save_cars_tag.rb
def save(tag_list, old_images) ActiveRecord::Base.transaction do #〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜ここから〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 #// 新しく追加する画像がない場合は、imagesカラムを一旦、空にする if images.blank? @car.update(images: []) else @car.update(images: images) end #// 残す画像と新しく追加する画像を足し合わせて、もう一度保存 @car.images.each do |image| old_images << image end @car.update(title: title, images: old_images, text: text, maker_id: maker_id, car_name: car_name, body_type_id: body_type_id, user_id: user_id) #〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜ここまで〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 #// @carに紐付くタグがあれば、car_tagsテーブルの紐付くレコードを全て消去する @car.car_tags.each do |tag| tag.delete end #// tag_listのタグの数だけ、tagsテーブルと、car_tagsテーブルに保存する tag_list.each do |tag_name| tag = Tag.where(name: tag_name).first_or_initialize tag.save car_tag = CarTag.where(car_id: @car.id, tag_id: tag.id).first_or_initialize car_tag.update(car_id: @car.id, tag_id: tag.id) end end end
ここで、問題が発生して、すでにDBに保存されていた古い画像とまだ保存してない新しい画像は、形式が違うので、単に同じ配列に入れてupdateしても、古い画像の方しか適応されず、新しい画像は保存されませんでした。調べていろいろ試しましたが、成功しなかったため、新しい画像だけで一度保存して、それを取り出して、古い画像と新しい画像を足し合わせて配列にして、もう一度保存するという、荒技しか手がありませんでした。(二回保存するという実装しかできない私の未熟さを神様どうか許してください、、、)二回保存するので、少し重くなった気がします。
#〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜ここから〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 #// 新しく追加する画像がない場合は、imagesカラムを一旦、空にする if images.blank? @car.update(images: []) else @car.update(images: images) end #// 残す画像と新しく追加する画像を足し合わせて、もう一度保存 @car.images.each do |image| old_images << image end @car.update(title: title, images: old_images, text: text, maker_id: maker_id, car_name: car_name, body_type_id: body_type_id, user_id: user_id) #〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜ここまで〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
if images.blank?で、新しく保存する画像があるかみて、
無ければ、imagesカラムを、[]に
有れば、imagesカラムに、新しい画像だけを保存します。
これをしないと、新しい画像がない場合、imagesに古い画像が入っているので、古い画像が2回保存され、画像の量が2倍になってしまいます。
@car.imagesにさっき保存した新しい画像があるので、それをeachで、前に古い画像の残すやつを入れたold_images(コントローラーで作ったやつ)に入れていきます。それを再度、imagesカラムに保存して完了です。
まとめ
2回保存するというのは、プログラム的に悪いことですが、具体的な悪影響としては、パソコンが重くなることくらいでしょうか。本来、複数枚画像が投稿できる機能であれば、画像のモデルを作って、そちらと結びつけるのが良いのではないかと思います。そもそもカラムに配列で保存するのは正規化の面からも良くないです。今回は、設計段階でそういうことを、全く考えられていなかったので、私のアプリケーションの規模であれば、多少重くなるだけで済むと考え、このような実装をしましたが、今後は、この件で要件定義や設計の大切さがよく分かったので、開発前の準備を入念にしていきたいと思いました。
今回はここまでです。
それでは、また