I would like to assert that a value is a string is not blank (eg. " "). If I submit a number, I want only the message “must be a string” to appear, however, the “cannot be blank” message also appears. I have tried to debug this, and it seem that the not_blank? rule is failed automatically if the str? rule fails, as my debugger did not break when put in the not_blank? method. Is there a way to short circuit the checking, so that not_blank? is only checked if str? is true? I’m sure this has been answered before, however, I’ve had a bit of a search and cannot find an answer, and I’m not sure of the terms to search on. Thanks in advance.
require 'dry-validation' #v0.11.0
form = Dry::Validation.Form do
configure do
def not_blank?(value)
!blank?(value)
end
def blank?(value)
value.is_a?(String) && value.strip.empty?
end
def self.messages
super.merge(
en: { errors: { not_blank?: 'cannot be blank' } }
)
end
end
required(:name_one) { str? & not_blank? }
required(:name_two).filled(:str?, :not_blank?)
required(:name_three) { str? & (str? > not_blank?) }
end
puts form.call(name_one: 1, name_two: 2, name_three: 3).messages
# => {:name_one=>["must be a string", "cannot be blank"], :name_two=>["must be a string", "cannot be blank"], :name_three=>["must be a string", "cannot be blank"]}
In fact, the checks are short-circuit, and if one check fails the rest are skipped. To understand why you are getting extra messages you need to understand what kinds of error messages dry-validation has. It is described here, but I will give you a summary:
errors: messages which are the result of failing predicates;
hints: messages for predicates which weren’t evaluated because an earlier predicate had failed;
messages: errors and hints combined.
So what you need is to use Dry::Validation::Result#errors instead of Dry::Validation::Result#messages as you don’t need hints.
Here’s an example:
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'dry-validation', '~> 0.11.1'
gem 'rspec', require: 'rspec/autorun'
end
module BlankChecker
def self.call(value)
!value.to_s.strip.empty?
end
end
Form = Dry::Validation.Form do
configure do
def not_blank?(value)
# Delegate the check to a helper object because
# frozen form instances cannot be mocked.
BlankChecker.call(value)
end
def self.messages
super.merge(
en: { errors: { not_blank?: 'cannot be blank' } }
)
end
end
required(:name).filled(:str?, :not_blank?)
end
RSpec.describe 'Dry::Validation error messages' do
subject(:result) { Form.(name: name) }
context 'when input is a valid string' do
let(:name) { 'George' }
it "don't have error messages" do
# Blank check is performed:
expect(BlankChecker).to receive(:call).and_return(true)
expect(result.messages).to be_empty
expect(result.errors).to be_empty
expect(result.hints).to be_empty
end
end
context 'when input is a blank string' do
let(:name) { ' ' }
it 'have an error and no hints' do
# Blank check is performed:
expect(BlankChecker).to receive(:call).and_return(false)
expect(result.messages).to eq(name: ['cannot be blank'])
expect(result.errors).to eq(result.messages)
expect(result.hints[:name]).to be_empty
end
end
context 'when input is not a string' do
let(:name) { 0xDEADBABE }
it 'have an error and a hint' do
# Blank check is NOT performed!
expect(BlankChecker).not_to receive(:call)
expect(result.messages[:name]).to eq(result.errors[:name] + result.hints[:name])
expect(result.errors[:name]).to eq(['must be a string'])
expect(result.hints[:name]).to eq(['cannot be blank'])
end
end
end
That’s awesome, thank you. I had not seen the documentation for that feature.