【Rails】コールバックでターミナルにバリデーションエラーを出力する


◯◯.saveや◯◯.createに失敗する時の原因を教えてくれなくて困る

例えばmessageモデルのインスタンスのsaveに失敗した時

Started POST "/groups/2/messages" for ::1 at 2020-08-01 13:56:59 +0900
Processing by MessagesController#create as */*
  Parameters: {"authenticity_token"=>"PKsrO+YPeqs8AmQ+sB+9YacFtKCb+gpdTkHPTgxG4/vs6wE0swZrWNYXKyBe4ipm9aQDII8PpHSUzL3JPeYPpw==", "message"=>{"content"=>""}, "group_id"=>"2"}
  User Load (25.1ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 ORDER BY `users`.`id` ASC LIMIT 1
  Group Load (0.6ms)  SELECT `groups`.* FROM `groups` WHERE `groups`.`id` = 2 LIMIT 1
  ↳ app/controllers/messages_controller.rb:30:in `set_group'
   (0.2ms)  BEGIN
  ↳ app/controllers/messages_controller.rb:11:in `create'
  User Load (0.4ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` = 6 LIMIT 1
  ↳ app/controllers/messages_controller.rb:11:in `create'
   (0.2ms)  ROLLBACK

ROLLBACKとだけ出てパッと見では原因がわからない。

【対策1】コントローラでsave!を使う方法

savecreatesave!create!にするとどのバリデーションが原因なのかエラーが表示される。

【対策2】モデルでコールバックを使う方法

本題。after_validationを使ってself.errors.messagesなどをputsメソッドで表示する。

<メリット>
・deviseを使ったuserモデルのように、コントローラを触るのが面倒な場合でも使える
・application_record.rbに一度書けばそれぞれのモデルで使える

手順1. pry-railsのgemを導入する

バリデーションエラーが発生した場合にbinding.pryを走らせたいので、いつもどおりpry-railsのgemを導入する。

Gemfile
group :development, :test do
  〜省略〜
  gem 'pry-rails'
end
ターミナル
bundle

手順2. models/application_record.rbにコールバックを追加する

models/application_record.rbに以下を追加する。

application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  ## 以下を追加する

  after_validation :display_validation_result

  private
  def display_validation_result
   ## development以外のときはオフ
    return false unless Rails.env.development?

    puts "============【display_validation_result】============"
    puts "◆◆◆◆◆◆#{self.class.name}モデル◆◆◆◆◆◆"
    puts "+++++属性値一覧++++++"
    self.attributes.each do |key, value|
      puts "#{key}: #{value}"
    end

    ## deviseを使ったuserモデルの場合passwordもputsする
    if self.class.name == "User"
      puts "password: #{self.password}"
    end

    puts "++++++++++++++++++++++++++++"
    ## メインはここから
    puts "★★★★★★バリデーションエラー★★★★★★"
    if self.errors.messages.length == 0
      puts "発生しませんでした"
    else
      puts "バリデーションエラー発生"
      puts self.errors.messages
      binding.pry
    end
    puts "★★★★★★★★★★★★★★★★★★★★★★★★★★★★★"
    puts "==========================="
  end

end

やっていることはバリデーションエラーが発生した時(self.errors.messages.lengthが0出ない時)にself.errors.messagesをターミナルに表示してbinding.pryを走らせているだけ。その他はおまけの情報。

結果

バリデーションエラーが発生してインスタンスのsaveに失敗する時、以下のようにターミナルに出力される。

============【display_validation_result】============
◆◆◆◆◆◆Userモデル◆◆◆◆◆◆
+++++属性一覧++++++
"id: "
"email: dwada"
"encrypted_password: $2a$11$zvwhx8qOe7u62tLbGQCaMOjItYjtjzg5yuaInysQ9wd15SMpMsBti"
"nickname: dadwa"
"first_name: dwadad"
"first_name_reading: dadawdwa"
"last_name: dwada"
"last_name_reading: dadwadwa"
"birthday: 2015-04-10"
"reset_password_token: "
"reset_password_sent_at: "
"remember_created_at: "
"password: dwadad"
++++++++++++++++++++++++++++
★★★★★★バリデーションエラー★★★★★★
バリデーションエラー発生
{:email=>["は不正な値です"], :password_confirmation=>["とパスワードの入力が一致しません"], :first_name_reading=>["はカタカナで入力して下さい。"], :last_name_reading=>["はカタカナで入力して下さい。"], :password=>["は不正な値です"]}

From: /Users/hogehoge/hogehoge/app/models/application_record.rb:27 ApplicationRecord#display_validation_result:

     6: def display_validation_result
     7:   puts "============【display_validation_result】============"
     8:   puts "◆◆◆◆◆◆#{self.class.name}モデル◆◆◆◆◆◆"
     9:   puts "+++++属性一覧++++++"
    10:   self.attributes.each do |key, value|
    11:     p "#{key}: #{value}"
    12:   end
    13: 
    14:   if self.class.name == "User"
    15:     p "password: #{self.password}"
    16:   end
    17: 
    18:   puts "++++++++++++++++++++++++++++"
    19:   puts "★★★★★★バリデーションエラー★★★★★★"
    20:   if self.errors.messages.length == 0
    21:     puts "発生しませんでした"
    22:   else
    23:     puts "バリデーションエラー発生"
    24:     puts self.errors.messages
    25:     binding.pry
    26:   end
 => 27:   puts "★★★★★★★★★★★★★★★★★★★★★★★★★★★★★"
    28:   puts "==========================="
    29: end

[1] pry(#<User>)>