やりたいこと
・refileを使用して画像アップロードの実装
・複数モデルに一度で保存したいため、fields_forを使用
この2つの組み合わせの書き方が調べてもほとんど出てこなかったので書いておく。
環境
Ruby 2.5.1
Rails 5.2.3
導入、基本的な使い方参考
refileの基本と複数画像のアップロード
導入方法や基本的な使い方は上記サイトを参考にしてください。
ただ一点、Gemfileのインストールは上記サイトの通りだとエラーが出たので、
下記コードで実行できました。
gem "refile", require: "refile/rails", github: 'manfe/refile'
gem "refile-mini_magick", github: 'refile/refile-mini_magick'
参考サイト様だとgithub: 'refile/refile'
となってる部分、
bundle install時にエラーが出たので、エラーメッセージに従ってgithub: 'manfe/refile'
としたら成功しました。
fields_forを使用する場合の書き方
前提:
・親モデルと子モデルは1対1のアソシエーション
・1画像のみアップロードするものとする
入力フォーム
= form_with model: @user do |f| # 親モデルをuserと仮定
# 省略
= f.fields_for :image do |i| # 子モデルをimageと仮定
= i.attachment_field :image # 「:image」 はimage_idカラムの_idを抜いたもの
attachment_field
を使用する。
これでfile_field
と同じファイル選択フォームが作成される。
モデルのアソシエーション
# userモデル
has_one :image, dependent: :destroy # 「dependent: :destroy」でuserを削除すると関連するimageも削除される
accepts_nested_attributes_for :image # fields_forに必要な記述
accepts_attachments_for :image, attachment: :image # refileに必要な記述
# imageモデル
attachment :image # 「:image」 はimage_idカラムのidを抜いたもの
belongs_to :user
attachment :image
の:image
はモデル名ではなく、カラム名なので間違えないように。
カラムは「image_id」で登録していても、ここでは「_id」は抜かして書く。
コントローラーの記述
def new
@user = User.new
@user.build_image # fields_forのために親子モデルを関連付け
end
def create
User.create(user_params)
end
def index
@users = User.all.includes(:image) # この呼び出し方が必須というわけではないです
end
def user_params
params.require(:user).permit(
:name,
:email,
image_attributes: [
:id,
:image, # 「image_id」カラムでも「_id」は入れなくて良い
:image_cache_id # これを記述しておくとキャッシュファイルも保存される(らしい)
]
)
end
最低限の記述のみ書いてます。
ちなみに、.includes(:image)
で子モデルが2つ以上ある場合は、
.includes([:image, :address])
のように、引数内で[]
で囲む必要があるので注意。
ビューファイルでの保存した画像の表示
= attachment_image_tag @user.image, :image, :fill, 200, 120, format: "jpg"
# 「@user.image」 は子モデル名。今回はfields_forでuserが親モデルとして紐づいているためこのように指定
# 「:image」 はimage_idカラムのidを抜いたもの
# 「200, 120,」 は表示する画像のサイズ。 「横, 縦,」
attachment_image_tag
を使用して保存したデータを呼び出す。
今回、1画像のみのアップロード前提で書いたが、複数画像アップロードだとかなり変わってくる。
複数画像アップロードの場合
入力フォーム
= form_with model: @user do |f|
# 省略
= f.fields_for :images do |i| # :imagesと複数形にする
= i.attachment_field :image, multiple: true, name: "images_attributes[0][image][]"
# 「multiple: true」 で複数ファイル選択を可能にする
# 「name: "images_attributes[0][image][]"」 でname属性をparamsに合わせる
= f.fields_for :images do |i|
のモデル指定を:images
と複数形にする。
その下の= i.attachment_field :image
はimage_idカラムのidを抜いたものなので単数形のままでOK。
multiple: true
を指定することにより、fields_forもあるためparamsの形がかなり変わり、そのままではparamsに値が入らない。
値がちゃんとparamsに入るよう、name: "images_attributes[0][image][]"
によりname属性を変わった後のparamsと合わせた形に指定し、値がparamsに入るようにする。
モデルのアソシエーション
# userモデル
has_many :images, dependent: :destroy # has_manyに変更、複数形に変更
accepts_nested_attributes_for :images # 複数形に変更
accepts_attachments_for :images, attachment: :images # 複数形に変更
# imageモデル
attachment :image # カラムなので単数形のままでOK
belongs_to :user
attachment :image
も複数形に変えないよう注意
コントローラーの記述
def new
@user = User.new
@user.images.build # 複数形になり記述が少し変更
end
def create
# 先にuserインスタンスにparamsを入れる
User.new(user_params)
# 複数画像は配列で持っているため、1つ1つインスタンスに入れる
params[:images_attributes][:"0"][:image].each do |image|
@user.images.build(image)
end
User.save
end
def index
@users = User.all.includes(:images) # 複数形に変更。この呼び出し方が必須というわけではないです
end
# image_attributes:をストロングパラメーターから削除
def user_params
params.require(:user).permit(
:name,
:email
)
end
コントローラーのcreateアクションとストロングパラメーターについては、もっと良い書き方があるはず。
正直、params[:images_attributes][:"0"][:image]
のストロングパラメーターでの取得の方法がわからなかったため、苦肉の策でストロングパラメーターからimageを消し、createアクションで直接paramsを渡している。
いろいろな書き方を試したが、ストロングパラメーターでは取得できなかった。
できる方法があれば教えていただけると助かります。
ビューファイルでの保存した画像の表示
# 複数画像は配列で保存されているため、1つ1つ取り出して表示
@user.images.each do |image| # 「@user.images」と複数形に変更
= attachment_image_tag image, :image, :fill, 200, 120, format: "jpg"
# 「attachment_image_tag image」 の「image」はeach文で指定した変数
複数画像の場合については以上。
これで複数画像のDB保存と表示ができるはず。
複数保存の問題点
上記のコードで一応複数画像についても、DB保存、表示が可能になった。
しかし問題がある。
元々fields_for
と.attachment_field
は1つだけ書いてファイル選択フォームは1つしか無かったものが、例えば3画像保存し、編集しようとするとあら不思議、フォームが3つに増えています!
| fields_forは紐づくcaptured_images全てに対して展開されるため、 |
| editアクションのviewにてfields_forが呼ばれるたびに、 |
| 選択された画像それぞれに対していちいち展開されてしまう点でした。 |
上記のリンク先でも記述されているが、複数保存したものをeditアクションなどで編集しようとすると、保存した画像の数だけファイル選択フォームが増えるという現象が起きる。
上記リンク先によるとどうもfields_for
の仕様で、紐づくレコードが複数あるとその分だけフォームが呼び出されるらしい。
editアクションで表示するビューをnewアクションのビューと同じにしていると確実にフォームが増えてしまうので、編集の仕方をどうにか工夫しなければならない。
何か良い方法があったら教えていただけると助かります。