指定した時だけバリデーションを実行したい

指定した時だけバリデーションを実行したい

開発をしているタイミングでバリデーションを実行したい時と、バリデーションを実行したくない時が発生したので、こちらにまとめます。

コードは、以下のようになっています。

[app/models/user.rb]

class User < ApplicationRecord
  authenticates_with_sorcery!

  validates :password, length: { minimum: 10 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password, presence: true
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: { case_sensitive: false }, presence: true
  validates :name, presence: true

  has_many :cooking_memories, dependent: :destroy
end
[app/controllers/users_controller.rb]

class UsersController < ApplicationController
  before_action :require_login, only: %i[show edit update]

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = t '.success_message'
      redirect_to login_path
    else
      flash.now[:danger] = t '.error_message'
      render :new
    end
  end

  def show
    @user = User.find(current_user.id)
  end

  def edit
    @user = User.find(current_user.id)
  end

  def update@user = User.find(current_user.id)
    if @user.update(profile_params)
      flash[:success] = t '.success_message'
      redirect_to profile_path
    else
      flash.now[:danger] = t '.error_message'
      render :edit
    end
  end
  
  private

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end

  def profile_params
    params.require(:user).permit(:name, :email)
  end
end

ユーザーの新規登録画面は、以下のようになっている。
Image from Gyazo

ユーザーの新規登録時は、[ニックネーム]、[Eメール]、[パスワード]、[パスワード再確認]の全部の項目の入力を必要にしたいが、 プロフィール画面では、[ニックネーム]、[Eメール]だけの入力で編集できるようにしたいが[パスワード]、[パスワード再確認]にpresence: trueのバリデーションを設定しているので更新時に以下のようにエラーになってしまう。

Image from Gyazo

①のupdateアクション(ユーザー情報の更新)をする時は、[ニックネーム]、[Eメール]のみバリデーションが実行されるようにしたい。
そんな時にバリデーションのカスタムコンテキストというのがあると分かったが、通常とは逆の設定にしたいとなりました。
つまり、カスタムコンテキストを記載した場合だけバリデーションが実行されないようにしたい。

結果以下のように記載することで、カスタムコンテキストを記載した場合だけバリデーションが実行されないようにできた!!

[app/models/user.rb]

class User < ApplicationRecord
  authenticates_with_sorcery!

  validates :password, length: { minimum: 10 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password, presence: true, unless: -> { validation_context == :not_password_validation }  ①
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: { case_sensitive: false }, presence: true
  validates :name, presence: true

  has_many :cooking_memories, dependent: :destroy
end
[app/controllers/users_controller.rb]

class UsersController < ApplicationController
  before_action :require_login, only: %i[show edit update]

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = t '.success_message'
      redirect_to login_path
    else
      flash.now[:danger] = t '.error_message'
      render :new
    end
  end

  def show
    @user = User.find(current_user.id)
  end

  def edit
    @user = User.find(current_user.id)
  end

  def update
    @user = User.find(current_user.id)
    @user.attributes = profile_params
    if @user.save(context: :not_password_validation)
      flash[:success] = t '.success_message'
      redirect_to profile_path
    else
      flash.now[:danger] = t '.error_message'
      render :edit
    end
  end
  
  private

  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end

  def profile_params
    params.require(:user).permit(:name, :email)
  end
end

参考記事

特定のvalidationをスキップする - プログラミングのメモ帳

Rails のバリデーションを特定のコンテキスト「以外」で実行させる | Webシステム開発/教育ソリューションのタイムインターメディア

バリデーションでコンテキストを活用する | ⬢ Appirits spirits

Railsで特定のコンテキスト「以外」のときだけバリデーションする - Qiita

Rails4でModelのvalidatesを切り替える - Qiita