画像の複数枚保存はできなかったが1枚は保存/表示させることができた

転職活動用の個人アプリを作成しています。
食べたものやカロリー値などを投稿してカロリーコントロールしやすくするためのダイエットアプリを作成しています。

カロリー値やPFC値をpostモデルに保存するのと同時に、画像はimageモデルに保存したいのですが、それができずに困っています。

ビューの書き方も間違っていて、表示ができないのはあとで自分で考えて修正しますが、それ以前にMySQLに保存する段階でつまずいているので、まずそこの解決するためのヒントを頂ければ幸いです。

よろしくお願いします。
## 前提・実現したいこと
画像を保存できるようにしたい。
images_attributesを使って、カロリー値やPFC値を投稿するときに、画像データはpostモデルに紐づく形でimageモデルに保存できるようにしたい。

## 発生している問題・エラーメッセージ
投稿データがpostモデルに保存されるのと同時に、画像データはimageモデルに保存されるようにしたいのにできない。
paramsには画像データが入ってるのに、post_paramsだと入っていない。
imagesテーブルのimageカラムがnilになってしまう。

```ruby:ターミナル
pry(#<PostsController>)> @post.images
  Image Load (0.4ms)  SELECT `images`.* FROM `images` WHERE `images`.`post_id` = 167
  ↳ app/controllers/posts_controller.rb:33
=> [#<Image:0x00007fdeaf3c5650 id: 23, image: nil, post_id: 167, created_at: Sun, 16 Aug 2020 09:33:38 UTC +00:00, updated_at: Sun, 16 Aug 2020 09:33:38 UTC +00:00>]
```
![スクリーンショット 2020-08-16 午後6.42.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547640/b49fdacb-a8eb-9016-2bdc-5b963b1b49cb.png)
id=21~23の部分だけ見てください。

## 該当のソースコード

### コントローラー(posts_controller.rb)
```ruby:posts_controller.rb
  def index
    @posts = Post.all.includes(:user).order("created_at DESC").page(params[:page]).per(5)
    @post = Post.new
    @post.images.build
  end
  
  def create
    @post = Post.new(post_params) 
    if @post.save
      redirect_back(fallback_location: root_path)
    else
      @posts = Post.includes(:user)
      flash.now[:alert] = '必須項目をしてください。' 
      redirect_back(fallback_location: root_path)
    end
  end

  private
  def post_params
    params.require(:post).permit(:food, :calorie, :protein, :fat, :carbo, :text, images_attributes: [:image]).merge(user_id: current_user.id)
  end
```
indexアクションでは、`@post.images.build`で`@post`に紐づく形でimageを保存するための空き箱を作ります。

createアクションでは、form_withで入力された情報をプライベートメソッドの`post_params`で受け取って、`@post`に代入しています。

この時、`post_params`の`images_attributes: [:image]`で画像データも一緒に保存されるはずなのですが、実際は保存されてくれません。

### postモデル(post.rb)
```ruby:post.rb
class Post < ApplicationRecord
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
end
```

### ビュー(_main.html.haml)
```ruby:_main.html.haml
.main
  .section
    .new-post
      = form_with model: @post, id: 'new_post' do |f|
        .input-box
          （省略）
          .icon
            .image-upload
              = f.fields_for :images do |image| 
                %i.fa.fa-camera.fa-2x
                = image.file_field :image, multiple: true, class: 'image_upload'
            .btn-square
              = f.submit '投稿する', class: 'post-btn'
```
親モデルに`accepts_nested_attributes_for: 〇〇(子モデルのテーブル名)`と書くと、親モデルの情報と一緒にその子モデルの情報も保存されます。

今回で言うと親モデル(postモデル)の情報(投稿データ。食べたもの、カロリー値など)と同時に画像データは子モデル(imageモデル)に別で保存されます。

参考にしたページ：https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

### imageモデル(image.rb)
```ruby:image.rb
class Image < ApplicationRecord
  belongs_to :post
  mount_uploader :image, ImageUploader
end
```
CarrierWaveを使って画像をアップロードしたい場合、画像を保存するテーブル（今回はimagesテーブル）に追加したカラムの名前を`mount_uploader`に指定する。

## 試したこと
paramsの中身とpost_paramsの中身を確認した。
その結果、paramsでは画像データ（image5.png）が入っていたが、post_paramsには画像データが入ってなかった。

```ruby:ターミナル
pry(#<PostsController>)> params
=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"h63zuMZOESYUInxzZD8jFqMbInn7kkMcWj1auQF2Irl1agWdILfmE8SEX9gYsvobDQbcAe336Nsu4SRCHezT7w==", "post"=><ActionController::Parameters {"food"=>"うどん", "calorie"=>"10", "protein"=>"10", "fat"=>"10", "carbo"=>"10", "text"=>"うどんを食べました。", "images_attributes"=>{"0"=>{"image"=>[#<ActionDispatch::Http::UploadedFile:0x00007fdea83150b8 @tempfile=#<Tempfile:/var/folders/sy/26p55v5j47s2zjd8h7vr8f6h0000gn/T/RackMultipart20200816-2275-g3cxd2.png>, @original_filename="image5.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"post[images_attributes][0][image][]\"; filename=\"image5.png\"\r\nContent-Type: image/png\r\n">]}}} permitted: false>, "commit"=>"投稿する", "controller"=>"posts", "action"=>"create"} permitted: false>
[2] pry(#<PostsController>)> post_params
Unpermitted parameter: :image
=> <ActionController::Parameters {"food"=>"うどん", "calorie"=>"10", "protein"=>"10", "fat"=>"10", "carbo"=>"10", "text"=>"うどんを食べました。", "images_attributes"=><ActionController::Parameters {"0"=><ActionController::Parameters {} permitted: true>} permitted: true>, "user_id"=>17} permitted: true>
```

チーム開発の時のコードと比較してみた。
スクールのチーム開発の時は問題なく同じようにimageモデルに画像を保存できて、その時のコードと見比べてみたが、`images_attributes: [:image]`の部分はチーム開発でも同じだったので、ここが間違っているわけではないのか？

```ruby:チーム開発
[2] pry(#<ItemsController>)> item_params
=> <ActionController::Parameters {"name"=>"Tシャツ", "description"=>"Tシャツ",  
 "price"=>"3000", "condition"=>"未使用に近い", "parent_category_id"=>"1",  
 "child_category_id"=>"21", "category_id"=>"22", "images_attributes"=> 
 <ActionController::Parameters {"0"=><ActionController::Parameters {"image"=># 
 <ActionDispatch::Http::UploadedFile:0x00007f9881f982d0 @tempfile=# 
 <Tempfile:/var/folders/sy/26p55v5j47s2zjd8h7vr8f6h0000gn/T/RackMultipart20200802- 
 70048-1vwny66.png>, @original_filename="bg-image1.png",  
 @content_type="image/png", @headers="Content-Disposition: form-data;  
 name=\"item[images_attributes][0][image]\"; filename=\"bg- 
 image1.png\"\r\nContent-Type: image/png\r\n">} permitted: true>} permitted:  
 true>, "shipping_attributes"=><ActionController::Parameters {"shipping_day"=>"2〜3 
 日で発送", "shipping_fee"=>"送料込み（出品者負担）", "shippingway_id"=>"1",  
 "shippingarea_id"=>"2"} permitted: true>} permitted: true>  
```

```ruby:今回の個人アプリ
[2] pry(#<PostsController>)> post_params
Unpermitted parameter: :image
=> <ActionController::Parameters {"food"=>"うどん", "calorie"=>"10",  
 "protein"=>"10", "fat"=>"10", "carbo"=>"10", "text"=>"うどんを食べました。",  
 "images_attributes"=><ActionController::Parameters {"0"=> 
 <ActionController::Parameters {ここに入ってるべき画像データが入っていない} permitted: true>} permitted: true>,  
 "user_id"=>17} permitted: true>  
```

## わからないこと・疑問点
なぜparamsには画像データが入っているのに、post_paramsでは画像データが入っていないのか？

→paramsには問題なく入るのは、form_withのフォームで投稿してるから。paramsには入ってるのにpost_paramsには入っていないということは、`posts_controller`のプライベートメソッド`post_params`の`images_attributes: [:image]`この部分の書き方が間違っているのか？

→スクールのチーム開発の時は問題なく同じようにimageモデルに画像を保存できて、その時のコードと見比べてみたが、`images_attributes: [:image]`の部分はチーム開発でも同じだったので、ここが間違っているわけではないのか？

→postモデルの書き方問題なし、imageモデルの書き方問題なし、ストロングパラメーターのimages_attributesの書き方問題なしだとすると、あとどこが原因でうまくいかないのかがわからない。

```ruby:posts_controller.rb
 private
  def post_params
    params.require(:post).permit(:food, :calorie, :protein, :fat, :carbo, :text,   images_attributes: [:image]).merge(user_id: current_user.id)  
  end
```

## 補足情報（FW/ツールのバージョンなど）
Ruby 2.5.1
Rails 5.2.4.3

## 今やっていること（追記）
結局、いつまでもこの機能だけに時間をかけていられないので、いったんpostモデルと別のimageモデルに分けて保存するのは諦めて、

まずは画像もpostモデルで一括して保存できるようにするのを目指すことにしました。
<br>

そもそもなぜpostモデルとは別のimageモデルに分けて保存しようとしたかというと、画像を複数枚保存したかったからです。

画像を1枚だけ保存するなら同じpostモデルのpostsテーブルにimageカラムを作って保存すればいいのですが、複数枚保存するとなると、別のimageモデルのimagesテーブルに保存させる必要があると思います（自信はないですが...）。

ただ、このアプリの想定ユーザーの目的は、画像が少なくとも1枚保存して表示できれば叶えられるので、複数枚にこだわる必要はないと判断しました（技術的にはできるようになりたいけど）。

<br>
なので、今は同じpostモデルのpostsテーブルに新たにimageカラムを作って、そこに画像を1枚保存できるように実装を進めています。

その過程で直面しているのが下記のエラーです。

```ruby:ターミナル
[1] pry(#<PostsController>)> post_params
Unpermitted parameter: :images
=> <ActionController::Parameters {"food"=>"うどん", "calorie"=>"10", "protein"=>"10", "fat"=>"10", "carbo"=>"10", "text"=>"うどん", "user_id"=>17} permitted: true>
```
MySQLで確認すると、postsテーブルのカラム名はちゃんと`image`になってるのに、なぜ
`Unpermitted parameter: :images`
というエラーが出てしまうのだろうか？
![スクリーンショット 2020-08-17 午後9.32.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547640/182ef8bd-54fd-084a-4145-becc756581d3.png)

#### 疑問
```
Unpermitted parameter: :images

この「:images」はどこからきているのか？
「imageを許可して」と頼んでるのに、「imagesは許しません」と言われてしまうのはなぜなのか？
「imagesをpermitして」なんて頼んでないんですけど...
```
#### 確認してわかったこと①
`ストロングパラメーター名[:カラム名]`（ex.`post_params[:calorie]`）
これで調べたいストロングパラメーターのカラムに入ってる値がわかる

```ruby
[1] pry(#<PostsController>)> post_params[:calorie]
Unpermitted parameter: :images
=> "10"
[2] pry(#<PostsController>)> post_params[:protein]
Unpermitted parameter: :images
=> "10"
[3] pry(#<PostsController>)> post_params[:fat]
Unpermitted parameter: :images
=> "10"
[4] pry(#<PostsController>)> post_params[:food]
Unpermitted parameter: :images
=> "うどん"
[5] pry(#<PostsController>)> post_params[:carbo]
Unpermitted parameter: :images
=> "10"
[6] pry(#<PostsController>)> post_params[:text]
Unpermitted parameter: :images
=> "うどん"
[7] pry(#<PostsController>)> post_params[:image]
Unpermitted parameter: :images
=> nil
[8] pry(#<PostsController>)> 
```
#### 確認してわかったこと②
MySQLで確認したら、postsテーブルのimageカラムに保存したいのに、imagesテーブルのimageカラムに保存されようとされてることがわかった（まあそれもうまくいかずnilになっちゃってるけど）。

![スクリーンショット 2020-08-17 午後10.01.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547640/0541eeed-934b-d86c-f4a2-e451b5f1babc.png)

→ imageモデル、imagesテーブルを消そう。

```ruby
rails destroy model image
```
これでimageモデルを削除した。
これをしてもまだimagesテーブルは残っているので、マイグレーションファイルを削除するコマンドでimagesテーブルのマイグレーションファイルを消す。

ここで衝撃の事実が発覚した！

```
rails db:migrate:status
```
でマイグレーションファイルの状態を確認したところ、`NO FILE`が4つもあった。

```
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200510204552  Devise create users
   up     20200514033900  Create comments
   up     20200514040853  Create likes
   up     20200514042245  ********** NO FILE **********
   up     20200514080622  Create posts
   up     20200517211741  Add names to users
   up     20200517213855  Add user id to posts
   up     20200618024836  Create relationships
   up     20200701095714  Create standards
   up     20200702024059  Add column standards
   up     20200720212843  ********** NO FILE **********
   up     20200803113146  ********** NO FILE **********
   up     20200803195950  ********** NO FILE **********
   up     20200803212001  Change column to allownull
   up     20200816120835  Add image to posts
```
まずこれら4つのNO FILEを消さねば。。。

NO FILEのマイグレーションファイルの消し方はこの記事読めばできます↓
https://qiita.com/YujiNaito/items/b6f6ef89f7b60309d5b1

やり方としては、
① touchコマンドでNO FILEというマイグレーションネームに適当な名前をつける

```
touch db/migrate/20200514042245_hoge.rb
```
② そのマイグレーションファイルの中身を簡単に書く

```ruby
class Hoge < ActiveRecord::Migration[5.2]
  def change
  end
end
```
③ `rails db:migrate:status`で新たにマイグレーションネームがつけられてることを確認する

```
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200514042245  Hoge
```
④ `rails db:migrate:down VERSION=マイグレーションID`でステータスをdownさせる

```
rails db:migrate:down VERSION=20200514042245
```
⑤ rmコマンドで削除する（この時パスを間違えると「そんなファイルはありません」と怒られる）

```
rm db/migrate/20200514042245_hoge.rb 
```

その結果、

```
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200510204552  Devise create users
   up     20200514033900  Create comments
   up     20200514040853  Create likes
   up     20200514080622  Create posts
   up     20200517211741  Add names to users
   up     20200517213855  Add user id to posts
   up     20200618024836  Create relationships
   up     20200701095714  Create standards
   up     20200702024059  Add column standards
   up     20200803212001  Change column to allownull
   up     20200816120835  Add image to posts
```
NO FILEは全てなくなりすっきりしたが、

```
Unpermitted parameter: :images
```
というエラーは依然として変わらず...。
また別の方法を考えねばならない。。。

#### 試したこと
viewに問題があるのではないかと思い始めた。

```ruby
.image-upload
  = f.fields_for :images do |image| 
    %i.fa.fa-camera.fa-2x
    = image.file_field :image, multiple: true, class: 'image_upload'
```
`multiple: true`オプションを削除した → だが変わらず。
`multiple: true`は画像を複数枚保存するときのための記述だ。

次に思ったのは、

```ruby
= f.fields_for :images do |image|
```
おや？
ここに`:images`とあるじゃないか！
実に怪しい。これが原因だったのか？！


そもそも`fields_for`ってどういう時に使うんだっけ？
→ こちらの記事 https://qiita.com/kouuuki/items/5daf2b5f34273d8457f7 によると、`fields_for`は異なるモデル（子モデル）を編集できるようにしたい時に使うらしい。

今回で言うと、当初やろうとしていたpostモデル(親モデル)に紐づくimageモデル(子モデル)を編集したい時に必要だった。

だが、今回、いったんそれは諦めて、imageモデルは使わず、postモデルにimageカラムを追加してまとめて管理することにしたので、`fields_for`は不要になったのだ。

ということで、`fields_for`の1行を削除した。
↓ ↓ ↓
BINGO！！
やっとpost_paramsに画像が入ってくれました😄！

```ruby
[1] pry(#<PostsController>)> post_params
=> <ActionController::Parameters {"food"=>"うどん", "calorie"=>"10", "protein"=>"10", "fat"=>"10", "carbo"=>"10", "text"=>"うどんを食べました。", "image"=>#<ActionDispatch::Http::UploadedFile:0x00007fa47b98f7c0 @tempfile=#<Tempfile:/var/folders/sy/26p55v5j47s2zjd8h7vr8f6h0000gn/T/RackMultipart20200818-19826-1g6molg.png>, @original_filename="image4.png", @content_type="image/png", @headers="Content-Disposition: form-data; name=\"post[image]\"; filename=\"image4.png\"\r\nContent-Type: image/png\r\n">, "user_id"=>17} permitted: true>
```
![スクリーンショット 2020-08-18 午後8.07.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547640/4ccbced1-5f80-91fb-eab3-15888982f9cb.png)

正直、個人アプリ開発始めて以来、いちばん嬉しい！！
ずっと詰まってたエラーを自分の力で解決できたから。

解決してみるとそんなことか、と呆気ないけれど、そこに気づくに至るまでにいろいろ試行錯誤を繰り返して解決できたから、達成感が半端ないです。

#### 最後の問題が残ってる（画像の表示）
これでは画像が表示されない。
これ結構簡単に解決すると思ったら思いの外難しい。。。

```ruby:_post.html.haml
- if @post.image.present?
  = image_tag @post.image.url
```
これでエラーは出ないのだが、ifの条件を削除すると、
`Nil location provided. Can't build URI`
というエラーが生じる。

[Nil location provided. Can't build URIエラーが出た時にやったこと](https://simpledancer.hatenablog.com/entry/2019/05/19/Nil_location_provided__Can%27t_build_URI%E3%82%A8%E3%83%A9%E3%83%BC%E3%81%8C%E5%87%BA%E3%81%9F%E6%99%82%E3%81%AB%E3%82%84%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8)
↑このページを読んで、

自信はないが、postsテーブルのimageカラムがnilになっているのが存在することが問題なのか？と予想を立てた(本当は画像なしでも投稿できるようにしたいのだが...)。
![スクリーンショット 2020-08-18 午後9.15.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547640/33e78af6-9a50-e713-0ffd-5ace609c762b.png)
ターミナルのコマンドでレコードを削除できることを知った。
ターミナルで`rails c`を打ってから、例えば次のようなコマンドを打つと、消去できる。

```
[6] pry(main)> Post.where(id: 1..7).destroy_all 
Post→モデル名
where(id: 1..7)→idが1~7を選択
destroy_all→それらを全部消す
```
このwhereの指定を変えれば削除したいレコードを選択して削除できる。

#### ここでまた疑問が...
`@post`の中身を確認してみると、imageカラムの中は`image6.png`となっている。
これはOK。

```ruby
[1] pry(#<PostsController>)> @post
=> #<Post:0x00007faf155a0868
 id: 197,
 food: "うどん",
 calorie: 100,
 protein: 100,
 fat: 100,
 carbo: 100,
 text: "うどん",
 created_at: Tue, 18 Aug 2020 12:34:38 UTC +00:00,
 updated_at: Tue, 18 Aug 2020 12:34:38 UTC +00:00,
 user_id: 17,
 start_time: nil,
 image: "image6.png">
```
問題はこのあと。
`@post.image`の中身を確認すると、こんな感じになってしまう。
これは問題あるのか？ないのか？がわからない。

予想では`image6.png`で出てくれると思ったのに。
`image6.png`とならないとおかしいんじゃないか？

```ruby
[2] pry(#<PostsController>)> @post.image
=> #<ImageUploader:0x00007faf1cc33430
 @cache_id=nil,
 @cache_storage=#<CarrierWave::Storage::File:0x00007faf1cc2a3f8 @cache_called=nil, @uploader=#<ImageUploader:0x00007faf1cc33430 ...>>,
 @file=
  #<CarrierWave::SanitizedFile:0x00007faf15d94d58
   @content=nil,
   @content_type="image/png",
   @file="/Users/naota7118/projects/pfc-master/public/uploads/post/image/197/image6.png",
   @original_filename=nil>,
 @filename="image6.png",
 @identifier=nil,
 @model=
  #<Post:0x00007faf155a0868
   id: 197,
   food: "うどん",
   calorie: 100,
   protein: 100,
   fat: 100,
   carbo: 100,
   text: "うどん",
   created_at: Tue, 18 Aug 2020 12:34:38 UTC +00:00,
   updated_at: Tue, 18 Aug 2020 12:34:38 UTC +00:00,
   user_id: 17,
   start_time: nil,
   image: "image6.png">,
 @mounted_as=:image,
 @original_filename="image6.png",
 @staged=false,
 @storage=#<CarrierWave::Storage::File:0x00007faf1cc120f0 @cache_called=nil, @uploader=#<ImageUploader:0x00007faf1cc33430 ...>>,
 @versions={}>
```
CarrierWaveあるいはImageUploaderの設定を何か間違えたか？

#### 解決はしたけどすっきりしない😅
どうやって解決したかと言うと、@をとった。

```ruby
(before)
- if @post.image.present?
  = image_tag @post.image.url

(after)
- if post.image.present?
 = image_tag post.image_url
```
画像は表示されるようになったのだが、なぜ解決できたのかはちゃんと理解できておらず、いまいちすっきりしない。

予想としては、今回部分テンプレートを使って

```ruby:
.posts
  = render @posts
```
こんな感じで各投稿を一覧表示しているのだが、これが原因で、インスタンス変数を使った
`image_tag @post.image.url`
これが使えなくなってるのかな？と思った。

が、正直かなり自信がない（わかる方いらっしゃったらぜひ教えてください🙇‍♂️）。