LoginSignup
13
20

More than 3 years have passed since last update.

[Rails]refile + fields_for の書き方

Last updated at Posted at 2019-07-08

やりたいこと

・refileを使用して画像アップロードの実装
・複数モデルに一度で保存したいため、fields_forを使用

この2つの組み合わせの書き方が調べてもほとんど出てこなかったので書いておく。

環境

Ruby 2.5.1
Rails 5.2.3

導入、基本的な使い方参考

refileの基本と複数画像のアップロード
導入方法や基本的な使い方は上記サイトを参考にしてください。

ただ一点、Gemfileのインストールは上記サイトの通りだとエラーが出たので、
下記コードで実行できました。

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画像のみアップロードするものとする

入力フォーム

ビューファイル.html.haml
= 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と同じファイル選択フォームが作成される。

モデルのアソシエーション

モデル.rb
  # 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」は抜かして書く。

コントローラーの記述

users_controller.rb
  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])
のように、引数内で[]で囲む必要があるので注意。

ビューファイルでの保存した画像の表示

ビューファイル.html.haml
= 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画像のみのアップロード前提で書いたが、複数画像アップロードだとかなり変わってくる。

複数画像アップロードの場合

入力フォーム

ビューファイル.html.haml
= 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に入るようにする。

モデルのアソシエーション

モデル.rb
  # 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も複数形に変えないよう注意

コントローラーの記述

users_controller.rb
  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を渡している。

いろいろな書き方を試したが、ストロングパラメーターでは取得できなかった。
できる方法があれば教えていただけると助かります。

ビューファイルでの保存した画像の表示

ビューファイル.html.haml
# 複数画像は配列で保存されているため、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アクションのビューと同じにしていると確実にフォームが増えてしまうので、編集の仕方をどうにか工夫しなければならない。
何か良い方法があったら教えていただけると助かります。

参考

refile公式
Refile のバックエンドに S3 を利用する

13
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
20