テスト実行時など、「どこで、何ゆえ、Validationエラーなのか」わからないことがある。
特に、子データなどをnested_attributeで処理していたり、
フォーム項目が大量にあったり、
そもそもどのValidatorがいつ発動したか、すらわからなかったり。
最近でくわした凶悪なものでは、
- test環境で
- 「has_many through」先のモデルで、
- さらにその先の「belongs_to」で存在しないデータに関連がついていた
- Validationメッセージは「has_many through」の部分で「○○は不正です」
fixtureを足して解決できたわけだが…、頭ひねったところでムリゲ。
そんなところで使える強力なTips(独学)3つ。備忘録
どのモデルの、どの種類のValidation Errorでも、NGになったタイミングで捕まえる
問答無用で、デバグします。
ActiveModel::Errors
gems/activemodel-5.2.0/lib/active_model/errors.rb # L295あたり
def add(attribute, message = :invalid, options = {})
message = message.call if message.respond_to?(:call)
detail = normalize_detail(message, options)
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
raise exception, full_message(attribute, message)
end
details[attribute.to_sym] << detail
messages[attribute.to_sym] << message
end
↑ ここらへんは、「Validation実行後、NGくらってなんらかのメッセージが追加された」時に必ず通る。
なので、ここで byebug
挿入。
> @base
で該当のModelの詳細を。
300: if exception = options[:strict]
301: exception = ActiveModel::StrictValidationFailed if exception == true
(byebug) @base
#<City id: 980190962, code: "1", pref_code: "980190962", name: "新宿区", name_kana: "シンジュク", lon: 1.0, lat: 1.0, specialward: nil, creator_id: nil, updater_id: nil, deleter_id: nil, created_at: "2018-06-08 07:04:41", updated_at: "2018-06-08 07:04:41", deleted_at: nil>
(byebug)
> where
で、タイミングを把握できる
冒頭のValidationは、↑のbreakpointを見張っていたら、
「ん? 変なModelで変なタイミングで引っかかってるな…」というので検出できた。
全部ログ出力してみる
以下のGemを使用する。
https://rubygems.org/gems/whiny_validation
出力イメージ
Validation failed #<Menu id: 77, name: "ヴェルサ", ..., tax_included: false, creator_id: nil, updater_id: nil, deleter_id: nil, created_at: nil, updated_at: nil, deleted_at: nil>
=> 率を入力してください
Validation failed #<AllianceProject id: nil, alliance_id: 999, project_id: 2, except_baby: false, kind: "limited", creator_id: 15, updater_id: 15, deleter_id: nil, created_at: nil, updated_at: nil, deleted_at: nil>
=> 具体メニューは不正な値です
Validation failed #<Alliance id: 999, name: "基本割引(料金調整用項目)", name_kana: "んんん", ..., creator_id: 1, updater_id: 15, deleter_id: nil, created_at: "2018-11-19 05:10:29", updated_at: "2018-11-19 05:10:29", deleted_at: nil>
=> 具体メニューは不正な値です
=> 単位を入力してください
=> 回収方法を入力してください
=> Alliance projectsは不正な値です
(1.1ms) ROLLBACK
2, 3モデル先でも、きっちり受け止めてくれるみたいですね。
Validationメッセージ自体は、i18n後の文字列(errors.full_messages)。
列名と箇所を判明したいので、full_messagesよりかは、
列キーとハッシュがいいな。
ドキュメントがほぼほぼないが、カスタマイズ方法模索中。
Testで、Validationの詳細をチェックする
minitest で controller_testなら、以下のテストを追加しておく
assert_equal({}, assigns[:customer].errors.messages.select { |_, v| v.present? })
F
Failure:
CustomersControllerTest#test_should_create_customer [/home/ec2-user/environment/base2/test/controllers/customers_controller_test.rb:22]:
Expected: {}
Actual: {:status=>["を入力してください"]}
どのフィールドが、何のValidationで落ちたか、を、テスト結果で確認できる
テスト結果で即わかるので、対応が楽。
毎回実装方法を忘れるので、
controller_test(今はfunctional_testというのか)のテンプレに格納。
rails g系で自動生成させます
以下、ご参考までに。
{root}/lib/templates/test_unit/scaffold/functional_test.rb
require 'test_helper'
<% module_namespacing do -%>
class <%= controller_class_name %>ControllerTest < ActionDispatch::IntegrationTest
<%- if mountable_engine? -%>
include Engine.routes.url_helpers
<%- end -%>
include Devise::Test::IntegrationHelpers
setup do
@<%= singular_table_name %> = <%= fixture_name %>(:one)
sign_in users(:one)
end
test 'should get index' do
get <%= index_helper %>_url
assert_response :success
end
test 'should get new' do
get <%= new_helper %>
assert_response :success
end
test 'should create <%= singular_table_name %>' do
assert_difference('<%= class_name %>.count') do
post <%= index_helper %>_url, params: { <%= "#{singular_table_name}: {" %>
<% attributes_hash.each do |field, row| -%>
<% next if field.in? %w[id creator_id created_at updater_id updated_at deleter_id deleted_at] -%>
<%= "#{field}: #{row}," %>
<% end %>
<%= '}' %> }
assert_equal({}, assigns[:<%= singular_table_name %>].errors.messages.select { |_, v| v.present? })
end
assert_redirected_to <%= singular_table_name %>_url(<%= class_name %>.last)
end
test 'should show <%= singular_table_name %>' do
get <%= show_helper %>
assert_response :success
end
test 'should get edit' do
get <%= edit_helper %>
assert_response :success
end
test 'should update <%= singular_table_name %>' do
patch <%= show_helper %>, params: { <%= "#{singular_table_name}: {" %>
<% attributes_hash.each do |field, row| -%>
<% next if field.in? %w[id creator_id created_at updater_id updated_at deleter_id deleted_at] -%>
<%= "#{field}: #{row}," %>
<% end %>
<%= '}' %> }
assert_equal({}, assigns[:<%= singular_table_name %>].errors.messages.select { |_, v| v.present? })
assert_redirected_to <%= singular_table_name %>_url(<%= "@#{singular_table_name}" %>)
end
test 'should destroy <%= singular_table_name %>' do
assert_difference('<%= class_name %>.count', -1) do
delete <%= show_helper %>
end
assert_redirected_to <%= singular_table_name %>_url(<%= "@#{singular_table_name}" %>)
end
end
<% end -%>