はじめに
Railsを使って某フリマアプリのクローンを作成する際、一つの商品に対して複数の写真投稿が可能な場合の処理に手こずったため、何かの参考になればと思い投稿します。
今回行ったこと
単純なアプリケーションを作って流れなどを理解しやすくし、fields_forを用いた形でのパラメーターの動きやcarriewaveで複数画像をアップロードする際の必要な記述に気を配りながら検証を行った。
環境
Rails 5.0.7.2
Ruby 2.5.1
アプリケーションの設計
テーブル
今回は以下のような単純な設計のアプリケーションを作成しました。
一つのツイートが複数の写真を持つことができるようになっています。
tweet |
---|
content |
image |
---|
url |
tweet_id |
Gemのインストール
今回インストールするGemはこれだけです。hamlは現在使い慣れているものなので導入しています。
gem 'carrierwave'
gem 'mini_magick'
gem 'haml-rails'
モデルファイル
親モデルに当たるtweetモデルにはaccepts_nested_attributes_for
の記述をします。これはtweetモデルと同時にimageモデルへも同時に作成するのに必要な記述です。
子モデルのimageモデルにはmount_uploader
で指定したカラムに対してのアップロード先を指定してあげます。image_uploader
の作成も忘れずに。
class Tweet < ApplicationRecord
has_many :images
accepts_nested_attributes_for :images
end
class Image < ApplicationRecord
belongs_to :tweet, optional: true
mount_uploader :url, ImageUploader
end
投稿フォーム
.contents
= form_for @tweet do |f|
= f.label :content, "テキスト"
= f.text_area :content, placeholder: "ツイート"
.file
= f.fields_for :images do |c|
= c.label :url, '画像'
= c.file_field :url
.submit
= f.submit "投稿", {class: "btn"}
fields_for
を使用することで、tweetの属性と同時にimageの属性もフォームを送ることができます。
コントローラー
class TweetsController < ApplicationController
#投稿結果がわかるようにindexに全て表示させています。
def index
@tweets = Tweet.includes(:images).all
end
#ここから
def new
@tweet = Tweet.new
2.times{@tweet.images.build}
end
def create
@tweet = Tweet.new(tweet_params)
@tweet.save
if @tweet.save
redirect_to action: "index"
else
render "new"
end
end
private
def tweet_params
params.require(:tweet).permit(
:content,
images_attributes: [:url]
)
end
end
newアクション内の@tweet.images.build
はtweetにネストしたimageを定義しておくものです。2.timesとすることでビュー側で画像アップロードのフォームが二つ生成されます。
また、ストロングパラメーターをimages_attributes: [:url]
とすることで、imageの属性値を配列で受け取るようにします。
フォーム画面
このような見た目になるので、テキストと画像を選択すれば投稿ができます。画像が一つだけでも大丈夫でした。
multiple: trueを記述する方法はcarrierwaveと相性が悪い?
ここでタイトルにもある内容に入るのですが、私はこの部分にかなり苦戦させられてしまいました。
というのも、画像を複数投稿する方法に関する記事をまわって情報を集めていたのですが、フォームのfile_field
にmultiple: true
という記述を書くことで複数投稿ができるということだったのですが、私はこの方法ではどうにもうまくいかず質問させていただいたところ、carrierwaveでmultiple: trueの記述を用いるのはあまり好ましくないとのことでした。
ただ、それで複数投稿が不可能ではないのでゴリ押しでできてしまうこともあるらしいです。
# 修正前
class Image < ApplicationRecord
belongs_to :tweet, optional: true
mount_uploaders :url, ImageUploader
end
↓
# 修正後
class Image < ApplicationRecord
belongs_to :tweet, optional: true
mount_uploader :url, ImageUploader
end
# 修正前
= f.fields_for :images do |c|
= c.label :url, '画像'
= c.file_field :url, multiple: true
↓
# 修正後
= f.fields_for :images do |c|
= c.label :url, '画像'
= c.file_field :url
修正前の記述の状態でずっと検証を行っていたのですが、配列としてうまくパラメータが送られてこず、undefined method ‘map'
と言われていました。
おわりに
もともと私のパラメーター周りの知識が少なかったこともあり、かなり苦戦した経験の一つとなりました。また、今回のような記事を見つけられなかったため、もし似た状況で困っている方の手助けになれば幸いです。
当方、まだまだ実務経験のない初学者のため、誤った認識の箇所などご指摘いただけたらと思います。
#今回参考にさせていただいた記事
https://qiita.com/katsu105/items/83977a7b2b4437caa2e2
https://qiita.com/sinagaki58/items/a0d59cc41c6824bb5f67