複数モデル間でのバリデーションについて

初投稿です。至らない点もあるかとは思いますが、よろしくおねがいします。
今回はバリデーションのかけ方を書いていきたいと思います。
検索してもなかなかいい記事がなかったので、簡単にまとめていけたらと思います。

前提

複数モデル間のバリデーションを書いていきます。

モデルはItemモデルとProduct_imageモデルとします。
イメージとしてはフリマアプリのメルカリで商品を出品するところをイメージしていただけたらいいと思います。

f:id:shun_0211:20200407224635p:plain
商品出品ページ

商品出品ページのフォームに商品情報を入れ出品ボタンを押して、Itemテーブルとproduct_imageテーブルに保存していきます。

モデル間のアソシエーションはItem対product_image = 1 : 多とします。

モデルの記載

さっそくモデルにアソシエーションとバリデーションを記載していきます。

※ 分かりやすくシンプルにするため、他の機能のアソシエーションやバリデーションは省略しています。


item.rb

class Item < ApplicationRecord
  # アソシエーション
  has_many :product_images, dependent: :destroy
  accepts_nested_attributes_for :product_images, allow_destroy: true

  # バリデーション
  validates :product_name, :product_information, :product_status, :price, :product_condition, :shipping_charge, :days_of_ship, :prefecture_id, presence: true
  validates :product_name, length: { maximum: 40 }
  validates :product_information, length: { maximum: 1000 }
  validates :price, numericality: { only_integer: true, greater_than_or_equal_to: 300, less_than_or_equal_to: 9999999 }
  validates_associated :product_images
  validate :product_images_number

  private
  def product_images_number
    if product_images.size < 1 
      errors.add(:product_image, "product_images are blank")
    end
  end

end


それでは内容を説明していきます。

アソシエーション

ItemモデルとProduct_imagesモデルは1:多の関係なので、
has_many :product_imagesとしています。

  • dependent: :destroyオプション    :親レコードを削除した際、子レコードも同時に削除する役割があります。
  • accepts_nested_attributes_forメソッド :親モデルを通じてネストしたモデルの関連レコードの登録・更新を可能にします。これがないと画像の保存ができません。
  • allow_destroy: true → 親レコードに関連した子レコードの削除を許可します。dependent: :destroyとセットです。
バリデーション

各カラムに対してバリデーションを記載していきます。

例)

validates :product_name, :product_information, :product_status, :price, :product_condition, :shipping_charge, :days_of_ship, :prefecture_id, presence: true

上の例では、商品名(product_name)、商品情報(product_information)、、以下略
のカラムに対して、NOT NULL制約、つまり未入力を禁止するバリデーションをかけています。

 validates_associated :product_images

上の記載ではItemモデルだけでなく関連付けされたproduct_imageモデルのバリデーションも同時に実行するよという記載になります。

product_images.rb

class ProductImage  < ApplicationRecord

  belongs_to :item, optional: true
  mount_uploader :Product_image, ImageUploader

  # バリデーション
  validates :image, presence: true

end
商品画像が投稿されていないときに出品できないようにするには


また、ここからが自分がハマった箇所で最も重要な箇所になるのですが、validates_associated: product_imagesを書いて満足してはいけません。
今のままの記載では、product_imagesがあった場合にしかバリデーションチェックを行ってくれません。
それじゃあ、下の記載は意味ないじゃないかと言われそうです。そのとおりです。

 validates :image, presence: true


では、実際に商品画像が投稿されていない場合に投稿できないようにする記載をする必要があります。
それが下のコードで、validate :(メソッド名)でバリデーションのメソッドを作っています。
private以下にメソッドの処理を書いています。


メソッドの中身はparamsで送られてきた値の中で、product_imagesが空の場合は、エラー文に
「product_images are blank」のエラーメッセージを追加しています。

validate :product_images_number

  private
  def product_images_number
    if product_images.size < 1 
      errors.add(:product_image, "product_images are blank")
    end
  end
まとめ

いかがでしたでしょうか?
accepts_nested_attributes_forメソッドを使うと複数モデルへのデータ登録が簡単にできますが、初心者にはかなりハードな内容だと思います。
僕もめちゃくちゃ苦労しましたし、100%理解できているかと言われるとあやしいですが、初心者ながら学んだことをアウトプットしてみました。


なにか解釈の違いなどあると思いますので、コメントでの指摘待ってます!!