[Rails] 複数モデルへの同時保存の際にバリデーションを設定する。

# はじめに
Railsのバリーデーションについて、複数のモデルに対して同時保存をする際のバリデーションを設定する際、基礎の理解が甘いまま疎かになってしまっていたので備忘録として残そうと思います。

# バリデーションとは？
はじめに、バリデーションというのは入力された値がちゃんと正しいものなのかチェックすることです。
例えば、カナ入力欄にひらがなや漢字が入力されていたり、電話番号入力欄に20桁くらいの数列が入力されているなど、こういったものが送信、登録されてしまうと不都合ですよね。それを登録するために弾く仕組みがバリデーションです。

# 今回行うこと
複数モデルを同時に登録することのできる簡単なアプリを用意し、バリデーションを設定。エラーメッセージを表示させる。

# 環境
Rails 5.0.7.2
Ruby 2.5.1

# アプリケーションの設計
今回作成したアプリケーションは、一つのツイートが複数の写真を持つことができるようになっています。
[別の記事](https://qiita.com/a2cfry/items/4fa93a426eaf9967461a)でもほぼ同じものを使用しましたが、今回はそれに追記していく形になります。

### テーブル
|tweet|
|:-----------------:|
|content|

|image|
|:-----------------:|
|url|
|tweet_id|

### モデルファイル
```ruby:models/tweet.rb
class Tweet < ApplicationRecord
  has_many :images
  accepts_nested_attributes_for :images

  #バリデーション記述
  validates_associated :images

  validates :content, presence: true

  validates :images, presence: true
end
```

```ruby:models/image.rb
class Image < ApplicationRecord
  belongs_to :tweet, optional: true
  mount_uploader :url, ImageUploader
end
```

`accepts_nested_attributes_for`の記述によって複数モデルへ同時投稿を可能にしています。
今回追記した部分は、`tweet.rb`の「バリデーション記述」以下の部分です。
**validates_associated :images**の記述によって、アソシエーションを組んであるモデルに対してのバリデーションを組むことができます。tweetとimageの関係は1対多のため、複数形で記述します。
そして、その下にバリデーションの条件を記述しています。こちらは本題の前提となる部分ですので、割愛させていただきます。

### コントローラー
```ruby:tweet.controller.rb
class TweetsController < ApplicationController
  def index
    @tweets = Tweet.includes(:images).all.order("id DESC")
  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

  def edit
    @tweet = Tweet.find(params[:id])

    num = @tweet.images.length
    num = 2 - num

    num.times{@tweet.images.build}
  end

  def update
    @tweet = Tweet.find(params[:id])
    @tweet = @tweet.update(tweet_params)
    if @tweet.save
      redirect_to action: "index"
    else
      render "edit"
    end
  end

  private

  def tweet_params
    params.require(:tweet).permit(
      :content,
      images_attributes: [:id, :url]
    )
  end
end
```
ここは`createアクション`を見てもらいたいのですが、バリデーションが働き、tweetが保存できなかった場合にnewアクションにrenderされるようになっています。

### フォーム
```ruby:_form.html.haml
.contents
  - if @tweet.errors.any?
    .chat-group-form__errors
      %h2="#{@tweet.errors.full_messages.count}件のエラーが発生しました。"
      %ul
        - @tweet.errors.full_messages.each do |message|
          %li= message
  = form_for @tweet, class: "form" do |f|
    .text-box
      = f.label :content, "テキスト"
      = f.text_area :content, placeholder: "ツイート"
    - if @tweet.images
      - @tweet.images.each do |img|
        .file
          = f.fields_for :images, img do |c|
            .image-field{id: "#{c.index}"}
              - if img.url.present?
                = image_tag img.url
              - else
                .image-blank

            = c.file_field :url
            
    .submit
      = f.submit "投稿", {class: "btn"}
```
`if @tweet.errors.any?`でエラー文がある場合に全てのエラー文を表示させるようになっています。モデルファイルに`validates_associated :images`の記述をすれば、ここは特に変わった記述をする必要はありません。

### 結果
何も入力せず投稿ボタンを押すと、

```
2件のエラーが発生しました。
Content can't be blank
Images can't be blank
```
と表示されます。

# まとめ
ツイッターのような、一つのツイートに対して複数の写真を登録したいときなどのバリデーションを組むときは、親モデルファイルに`validates_associated`の記述をし、他は特に変わった記述をせず実装することができる。

まだまだ理解が甘い部分も多いため、間違っている箇所がありましたらご指摘いただけるとありがたいです。

# 関連リンク
今回で使ったアプリを作成した時の記事→　[carrierwaveでmultiple: trueは相性が悪い？](https://qiita.com/a2cfry/items/4fa93a426eaf9967461a)