This commit is contained in:
Elliot Winkler 2017-11-29 14:46:19 -06:00
parent e5ce548600
commit e45a70dffa
21 changed files with 479 additions and 476 deletions

View File

@ -3,15 +3,16 @@ require 'shoulda/matchers/active_model/qualifiers'
require 'shoulda/matchers/active_model/validation_matcher'
require 'shoulda/matchers/active_model/validation_matcher/build_description'
require 'shoulda/matchers/active_model/validator'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_changed_value_error'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_does_not_exist_error'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setter'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setter_and_validator'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setters'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/attribute_setters_and_validators'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/successful_check'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher/successful_setting'
require 'shoulda/matchers/active_model/allow_or_disallow_value_matcher'
require 'shoulda/matchers/active_model/allow_value_matcher'
require 'shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error'
require 'shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error'
require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setter'
require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator'
require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setters'
require 'shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators'
require 'shoulda/matchers/active_model/allow_value_matcher/successful_check'
require 'shoulda/matchers/active_model/allow_value_matcher/successful_setting'
require 'shoulda/matchers/active_model/disallow_value_matcher'
require 'shoulda/matchers/active_model/validate_length_of_matcher'
require 'shoulda/matchers/active_model/validate_inclusion_of_matcher'

View File

@ -0,0 +1,358 @@
module Shoulda
module Matchers
module ActiveModel
# @private
class AllowOrDisallowValueMatcher
include Helpers
include Qualifiers::IgnoringInterferenceByWriter
attr_reader(
:after_setting_value_callback,
:attribute_to_check_message_against,
:attribute_to_set,
:context,
:subject,
)
attr_writer(
:attribute_changed_value_message,
:failure_message_preface,
:values_to_preset,
)
def initialize(*values)
super
@values_to_set = values
@options = {}
@after_setting_value_callback = -> {}
@expects_strict = false
@expects_custom_validation_message = false
@context = nil
@values_to_preset = {}
@failure_message_preface = nil
@attribute_changed_value_message = nil
@was_negated = nil
end
def for(attribute_name)
@attribute_to_set = attribute_name
@attribute_to_check_message_against = attribute_name
self
end
def on(context)
if context.present?
@context = context
end
self
end
def with_message(message, given_options = {})
if message.present?
@expects_custom_validation_message = true
options[:expected_message] = message
options[:expected_message_values] = given_options.fetch(:values, {})
if given_options.key?(:against)
@attribute_to_check_message_against = given_options[:against]
end
end
self
end
def expected_message
if options.key?(:expected_message)
if Symbol === options[:expected_message]
default_expected_message
else
options[:expected_message]
end
end
end
def expects_custom_validation_message?
@expects_custom_validation_message
end
def strict(expects_strict = true)
@expects_strict = expects_strict
self
end
def expects_strict?
@expects_strict
end
def _after_setting_value(&callback)
@after_setting_value_callback = callback
end
def was_negated?
@was_negated
end
def description
ValidationMatcher::BuildDescription.call(self, simple_description)
end
def model
subject.class
end
def last_attribute_setter_used
result.attribute_setter
end
def last_value_set
last_attribute_setter_used.value_written
end
def pretty_print(pp)
Shoulda::Matchers::Util.pretty_print(self, pp, {
was_negated: was_negated?,
attribute_to_set: attribute_to_set,
attribute_to_check_message_against: attribute_to_check_message_against,
values_to_set: values_to_set,
expected_message: expected_message,
expects_strict: expects_strict?,
subject: subject,
attribute_setters_and_validators_for_values_to_set: attribute_setters_and_validators_for_values_to_set,
})
end
protected
attr_reader(
:options,
:result,
:values_to_preset,
:values_to_set,
)
def matches?(subject)
@subject = subject
@was_negated = false
false
end
def does_not_match?(subject)
@subject = subject
@was_negated = true
false
end
def positive_failure_message
attribute_setter = result.attribute_setter
if result.attribute_setter.successfully_checked?
validator = result.validator
message = failure_message_preface.call
message << ' valid, but it was invalid instead,'
if validator.captured_validation_exception?
message << ' raising a validation exception with the message '
message << validator.validation_exception_message.inspect
message << '.'
else
message << " producing these validation errors:\n\n"
message << validator.all_formatted_validation_error_messages
end
else
message = attribute_setter.failure_message
end
if include_attribute_changed_value_message?
message << "\n\n" + attribute_changed_value_message.call
end
Shoulda::Matchers.word_wrap(message)
end
def negative_failure_message
attribute_setter = result.attribute_setter
if attribute_setter.successfully_checked?
validator = result.validator
message = failure_message_preface.call + ' invalid'
if validator.validation_message_type_matches?
if validator.has_matching_validation_messages?
message << ' and to'
if validator.captured_validation_exception?
message << ' raise a validation exception with message'
else
message << ' produce'
if expected_message.is_a?(Regexp)
message << ' a'
else
message << ' the'
end
message << ' validation error'
end
if expected_message.is_a?(Regexp)
message << ' matching '
message << Shoulda::Matchers::Util.inspect_value(
expected_message,
)
else
message << " #{expected_message.inspect}"
end
if !validator.captured_validation_exception?
message << " on :#{attribute_to_check_message_against}"
end
message << '. The record was indeed invalid, but'
if validator.captured_validation_exception?
message << ' the exception message was '
message << validator.validation_exception_message.inspect
message << ' instead.'
else
message << " it produced these validation errors instead:\n\n"
message << validator.all_formatted_validation_error_messages
end
else
message << ', but it was valid instead.'
end
elsif validator.captured_validation_exception?
message << ' and to produce validation errors, but the record'
message << ' raised a validation exception instead.'
else
message << ' and to raise a validation exception, but the record'
message << ' produced validation errors instead.'
end
else
message = attribute_setter.failure_message
end
if include_attribute_changed_value_message?
message << "\n\n" + attribute_changed_value_message.call
end
Shoulda::Matchers.word_wrap(message)
end
private
def run(strategy)
attribute_setters_for_values_to_preset.first_to_unexpectedly_not_pass ||
attribute_setters_and_validators_for_values_to_set.public_send(strategy)
end
def failure_message_preface
@failure_message_preface || method(:default_failure_message_preface)
end
def default_failure_message_preface
''.tap do |preface|
if descriptions_for_preset_values.any?
preface << 'After setting '
preface << descriptions_for_preset_values.to_sentence
preface << ', then '
else
preface << 'After '
end
preface << 'setting '
preface << description_for_resulting_attribute_setter
unless preface.end_with?('--')
preface << ','
end
preface << " the matcher expected the #{model.name} to be"
end
end
def include_attribute_changed_value_message?
!ignore_interference_by_writer.never? &&
result.attribute_setter.attribute_changed_value?
end
def attribute_changed_value_message
@attribute_changed_value_message ||
method(:default_attribute_changed_value_message)
end
def default_attribute_changed_value_message
<<-MESSAGE.strip
As indicated in the message above, :#{result.attribute_setter.attribute_name}
seems to be changing certain values as they are set, and this could have
something to do with why this test is failing. If you've overridden the writer
method for this attribute, then you may need to change it to make this test
pass, or do something else entirely.
MESSAGE
end
def descriptions_for_preset_values
attribute_setters_for_values_to_preset.
map(&:attribute_setter_description)
end
def description_for_resulting_attribute_setter
result.attribute_setter_description
end
def attribute_setters_for_values_to_preset
@_attribute_setters_for_values_to_preset ||=
AttributeSetters.new(self, values_to_preset)
end
def attribute_setters_and_validators_for_values_to_set
@_attribute_setters_and_validators_for_values_to_set ||=
AttributeSettersAndValidators.new(
self,
values_to_set.map { |value| [attribute_to_set, value] },
)
end
def inspected_values_to_set
Shoulda::Matchers::Util.inspect_values(values_to_set).to_sentence(
two_words_connector: ' or ',
last_word_connector: ', or ',
)
end
def default_expected_message
if expects_strict?
"#{human_attribute_name} #{default_attribute_message}"
else
default_attribute_message
end
end
def default_attribute_message
default_error_message(
options[:expected_message],
default_attribute_message_values,
)
end
def default_attribute_message_values
defaults = {
model_name: model_name,
model: subject,
attribute: attribute_to_check_message_against,
}
defaults.merge(options[:expected_message_values])
end
def model_name
subject.class.to_s.underscore
end
def human_attribute_name
subject.class.human_attribute_name(attribute_to_check_message_against)
end
end
end
end
end

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class AttributeChangedValueError < Shoulda::Matchers::Error
attr_accessor :matcher_name, :model, :attribute_name, :value_written,

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class AttributeDoesNotExistError < Shoulda::Matchers::Error
attr_accessor :model, :attribute_name, :value

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class AttributeSetter
def self.set(args)

View File

@ -3,7 +3,7 @@ require 'forwardable'
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class AttributeSetterAndValidator
extend Forwardable
@ -16,7 +16,7 @@ module Shoulda
:expected_message,
:expects_strict?,
:ignore_interference_by_writer,
:instance,
:subject,
)
def initialize(allow_value_matcher, attribute_name, value)
@ -30,7 +30,7 @@ module Shoulda
def attribute_setter
@_attribute_setter ||= AttributeSetter.new(
matcher_name: :allow_value,
object: instance,
object: subject,
attribute_name: attribute_name,
value: value,
ignore_interference_by_writer: ignore_interference_by_writer,
@ -44,7 +44,7 @@ module Shoulda
def validator
@_validator ||= Validator.new(
instance,
subject,
attribute_to_check_message_against,
context: context,
expects_strict: expects_strict?,

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class AttributeSetters
include Enumerable

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class AttributeSettersAndValidators
include Enumerable
@ -51,7 +51,7 @@ module Shoulda
# binding.pry
tuple.attribute_setter.set!
# -- BEFORE: -- !tuple.validator.does_not_pass?
# tuple.validator.passes?
# -- ALSO: -- tuple.validator.passes?
!tuple.validator.fails?
end
end

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class SuccessfulCheck
def successful?

View File

@ -1,7 +1,7 @@
module Shoulda
module Matchers
module ActiveModel
class AllowValueMatcher
class AllowOrDisallowValueMatcher
# @private
class SuccessfulSetting
def successful?

View File

@ -304,369 +304,32 @@ module Shoulda
alias_method :allow_values, :allow_value
# @private
class AllowValueMatcher
include Helpers
include Qualifiers::IgnoringInterferenceByWriter
attr_reader(
:after_setting_value_callback,
:attribute_to_check_message_against,
:attribute_to_set,
:context,
:instance
)
attr_writer(
:attribute_changed_value_message,
:failure_message_preface,
:values_to_preset,
)
def initialize(*values, will_be_negated: nil)
super
@values_to_set = values
@options = {}
@after_setting_value_callback = -> {}
@expects_strict = false
@expects_custom_validation_message = false
@context = nil
@values_to_preset = {}
@failure_message_preface = nil
@attribute_changed_value_message = nil
@will_be_negated = will_be_negated
@was_negated = nil
class AllowValueMatcher < AllowOrDisallowValueMatcher
def simple_description
"pass validation when :#{attribute_to_set} is set to " +
"#{inspected_values_to_set}"
end
def for(attribute_name)
@attribute_to_set = attribute_name
@attribute_to_check_message_against = attribute_name
self
end
def matches?(subject)
super(subject)
def on(context)
if context.present?
@context = context
end
self
end
def with_message(message, given_options = {})
if message.present?
@expects_custom_validation_message = true
options[:expected_message] = message
options[:expected_message_values] = given_options.fetch(:values, {})
if given_options.key?(:against)
@attribute_to_check_message_against = given_options[:against]
end
end
self
end
def expected_message
if options.key?(:expected_message)
if Symbol === options[:expected_message]
default_expected_message
else
options[:expected_message]
end
end
end
def expects_custom_validation_message?
@expects_custom_validation_message
end
def strict(expects_strict = true)
@expects_strict = expects_strict
self
end
def expects_strict?
@expects_strict
end
def _after_setting_value(&callback)
@after_setting_value_callback = callback
end
def matches?(instance)
@instance = instance
@was_negated = false
@result = run(:first_to_unexpectedly_not_pass)
@result.nil?
end
def does_not_match?(instance)
@instance = instance
@was_negated = true
@result = run(:first_to_unexpectedly_not_fail)
@result.nil?
end
# def does_not_match?(subject)
# super(subject)
# @result = run(:first_to_unexpectedly_not_fail)
# @result.nil?
# end
def failure_message
attribute_setter = result.attribute_setter
if result.attribute_setter.successfully_checked?
validator = result.validator
message = failure_message_preface.call
message << ' valid, but it was invalid instead,'
if validator.captured_validation_exception?
message << ' raising a validation exception with the message '
message << validator.validation_exception_message.inspect
message << '.'
else
message << " producing these validation errors:\n\n"
message << validator.all_formatted_validation_error_messages
end
else
message = attribute_setter.failure_message
end
if include_attribute_changed_value_message?
message << "\n\n" + attribute_changed_value_message.call
end
Shoulda::Matchers.word_wrap(message)
positive_failure_message
end
def failure_message_when_negated
attribute_setter = result.attribute_setter
if attribute_setter.successfully_checked?
validator = result.validator
message = failure_message_preface.call + ' invalid'
if validator.type_of_message_matched?
if validator.has_messages?
message << ' and to'
if validator.captured_validation_exception?
message << ' raise a validation exception with message'
else
message << ' produce'
if expected_message.is_a?(Regexp)
message << ' a'
else
message << ' the'
end
message << ' validation error'
end
if expected_message.is_a?(Regexp)
message << ' matching '
message << Shoulda::Matchers::Util.inspect_value(
expected_message
)
else
message << " #{expected_message.inspect}"
end
unless validator.captured_validation_exception?
message << " on :#{attribute_to_check_message_against}"
end
message << '. The record was indeed invalid, but'
if validator.captured_validation_exception?
message << ' the exception message was '
message << validator.validation_exception_message.inspect
message << ' instead.'
else
message << " it produced these validation errors instead:\n\n"
message << validator.all_formatted_validation_error_messages
end
else
message << ', but it was valid instead.'
end
elsif validator.captured_validation_exception?
message << ' and to produce validation errors, but the record'
message << ' raised a validation exception instead.'
else
message << ' and to raise a validation exception, but the record'
message << ' produced validation errors instead.'
end
else
message = attribute_setter.failure_message
end
if include_attribute_changed_value_message?
message << "\n\n" + attribute_changed_value_message.call
end
Shoulda::Matchers.word_wrap(message)
end
def will_be_negated?
@will_be_negated
end
def was_negated?
@was_negated
end
def description
ValidationMatcher::BuildDescription.call(self, simple_description)
end
def simple_description
"allow :#{attribute_to_set} to be #{inspected_values_to_set}"
end
def model
instance.class
end
def last_attribute_setter_used
result.attribute_setter
end
def last_value_set
last_attribute_setter_used.value_written
end
def pretty_print(pp)
Shoulda::Matchers::Util.pretty_print(self, pp, {
will_be_negated: will_be_negated?,
was_negated: was_negated?,
attribute_to_set: attribute_to_set,
attribute_to_check_message_against: attribute_to_check_message_against,
values_to_set: values_to_set,
expected_message: expected_message,
expects_strict: expects_strict?,
instance: instance,
attribute_setters_and_validators_for_values_to_set: attribute_setters_and_validators_for_values_to_set,
})
end
protected
attr_reader(
:options,
:result,
:values_to_preset,
:values_to_set,
)
private
def run(strategy)
attribute_setters_for_values_to_preset.first_to_unexpectedly_not_pass ||
attribute_setters_and_validators_for_values_to_set.public_send(strategy)
end
def failure_message_preface
@failure_message_preface || method(:default_failure_message_preface)
end
def default_failure_message_preface
''.tap do |preface|
if descriptions_for_preset_values.any?
preface << 'After setting '
preface << descriptions_for_preset_values.to_sentence
preface << ', then '
else
preface << 'After '
end
preface << 'setting '
preface << description_for_resulting_attribute_setter
unless preface.end_with?('--')
preface << ','
end
preface << " the matcher expected the #{model.name} to be"
end
end
def include_attribute_changed_value_message?
!ignore_interference_by_writer.never? &&
result.attribute_setter.attribute_changed_value?
end
def attribute_changed_value_message
@attribute_changed_value_message ||
method(:default_attribute_changed_value_message)
end
def default_attribute_changed_value_message
<<-MESSAGE.strip
As indicated in the message above, :#{result.attribute_setter.attribute_name}
seems to be changing certain values as they are set, and this could have
something to do with why this test is failing. If you've overridden the writer
method for this attribute, then you may need to change it to make this test
pass, or do something else entirely.
MESSAGE
end
def descriptions_for_preset_values
attribute_setters_for_values_to_preset.
map(&:attribute_setter_description)
end
def description_for_resulting_attribute_setter
result.attribute_setter_description
end
def attribute_setters_for_values_to_preset
@_attribute_setters_for_values_to_preset ||=
AttributeSetters.new(self, values_to_preset)
end
def attribute_setters_and_validators_for_values_to_set
@_attribute_setters_and_validators_for_values_to_set ||=
AttributeSettersAndValidators.new(
self,
values_to_set.map { |value| [attribute_to_set, value] }
)
end
def inspected_values_to_set
Shoulda::Matchers::Util.inspect_values(values_to_set).to_sentence(
two_words_connector: " or ",
last_word_connector: ", or "
)
end
def default_expected_message
if expects_strict?
"#{human_attribute_name} #{default_attribute_message}"
else
default_attribute_message
end
end
def default_attribute_message
default_error_message(
options[:expected_message],
default_attribute_message_values
)
end
def default_attribute_message_values
defaults = {
model_name: model_name,
instance: instance,
attribute: attribute_to_check_message_against,
}
defaults.merge(options[:expected_message_values])
end
def model_name
instance.class.to_s.underscore
end
def human_attribute_name
instance.class.human_attribute_name(
attribute_to_check_message_against
)
negative_failure_message
end
end
end

View File

@ -1,87 +1,34 @@
require 'forwardable'
module Shoulda
module Matchers
module ActiveModel
# @private
class DisallowValueMatcher
extend Forwardable
def_delegators(
:allow_matcher,
:_after_setting_value,
:attribute_changed_value_message=,
:attribute_to_set,
# :description,
:expects_strict?,
:failure_message_preface,
:failure_message_preface=,
:ignore_interference_by_writer,
:last_attribute_setter_used,
:last_value_set,
:model,
# :simple_description,
:values_to_preset=,
)
def initialize(value)
@allow_matcher = AllowValueMatcher.new(value, will_be_negated: true)
end
def description
ValidationMatcher::BuildDescription.call(self, simple_description)
end
class DisallowValueMatcher < AllowOrDisallowValueMatcher
def simple_description
"not #{allow_matcher.simple_description}"
"fail validation when :#{attribute_to_set} is set to " +
"#{inspected_values_to_set}"
end
def matches?(subject)
allow_matcher.does_not_match?(subject)
# !allow_matcher.matches?(subject)
super(subject)
@result = run(:first_to_unexpectedly_not_fail)
@result.nil?
end
def does_not_match?(subject)
allow_matcher.matches?(subject)
# !allow_matcher.does_not_match?(subject)
end
# def does_not_match?(subject)
# super(subject)
def for(attribute)
allow_matcher.for(attribute)
self
end
def on(context)
allow_matcher.on(context)
self
end
def with_message(message, options={})
allow_matcher.with_message(message, options)
self
end
def strict(strict = true)
allow_matcher.strict(strict)
self
end
def ignoring_interference_by_writer(value = :always)
allow_matcher.ignoring_interference_by_writer(value)
self
end
# @result = run(:first_to_unexpectedly_not_pass)
# !@result.nil?
# end
def failure_message
allow_matcher.failure_message_when_negated
negative_failure_message
end
def failure_message_when_negated
allow_matcher.failure_message
positive_failure_message
end
protected
attr_reader :allow_matcher
end
end
end

View File

@ -18,7 +18,7 @@ module Shoulda
def default_error_message(type, options = {})
model_name = options.delete(:model_name)
attribute = options.delete(:attribute)
attribute = options.fetch(:attribute)
instance = options.delete(:instance)
RailsShim.generate_validation_message(

View File

@ -79,21 +79,24 @@ module Shoulda
# @private
class ValidateAbsenceOfMatcher < ValidationMatcher
def initialize(attribute)
super
super(attribute)
@expected_message = :present
end
def matches?(subject)
super(subject)
disallows_value_of(value, @expected_message)
def simple_description
"fail validation when :#{attribute} is empty/falsy"
end
def simple_description
"validate that :#{@attribute} is empty/falsy"
protected
def add_submatchers
add_matcher_disallowing([value], expected_message)
end
private
attr_reader :expected_message
def value
if reflection
obj = reflection.klass.new
@ -115,9 +118,9 @@ module Shoulda
end
def column_type
@subject.class.respond_to?(:columns_hash) &&
@subject.class.columns_hash[@attribute.to_s].respond_to?(:type) &&
@subject.class.columns_hash[@attribute.to_s].type
subject.class.respond_to?(:columns_hash) &&
subject.class.columns_hash[attribute.to_s].respond_to?(:type) &&
subject.class.columns_hash[attribute.to_s].type
end
def collection?
@ -129,8 +132,8 @@ module Shoulda
end
def reflection
@subject.class.respond_to?(:reflect_on_association) &&
@subject.class.reflect_on_association(@attribute)
subject.class.respond_to?(:reflect_on_association) &&
subject.class.reflect_on_association(attribute)
end
end
end

View File

@ -375,15 +375,13 @@ EOT
end
def matches?(subject)
super(subject)
if @array
add_submatchers_to_test_array
elsif @range
add_submatchers_to_test_range
end
all_submatchers_match?
super(subject)
end
def pretty_print(pp)

View File

@ -13,6 +13,7 @@ module Shoulda
@submatchers = []
@expected_message = nil
@expects_custom_validation_message = false
@_submatchers_added = false
@was_negated = nil
end
@ -49,14 +50,23 @@ module Shoulda
def matches?(subject)
@subject = subject
@was_negated = false
false
if !@_submatchers_added
add_submatchers
@_submatchers_added = true
end
all_submatchers_match?.tap do
@was_negated = false
end
end
def does_not_match?(subject)
@subject = subject
!matches?(subject)
@was_negated = true
!matches?(subject).tap do
@was_negated = true
end
end
def failure_message
@ -116,6 +126,9 @@ module Shoulda
subject.class
end
def add_submatchers
end
def add_submatcher(submatcher)
submatchers << submatcher
end
@ -132,7 +145,7 @@ module Shoulda
end
alias_method :add_matcher_allowing, :allows_value_of
def disallows_value_of(value_or_values, message: nil, &block)
def disallows_value_of(value_or_values, message = nil, &block)
matcher =
if value_or_values.is_a?(Array)
disallow_value_matcher(*value_or_values, message: message, &block)

View File

@ -44,11 +44,9 @@ module Shoulda
if matcher.try(:expects_custom_validation_message?)
description_clauses.last << ' with a custom message'
end
description_clauses.last << ' on failure'
elsif matcher.try(:expects_custom_validation_message?)
description_clauses <<
'producing a custom validation error on failure'
'producing a custom validation error'
end
description_clauses

View File

@ -41,6 +41,10 @@ module Shoulda
expects_strict? == captured_validation_exception?
end
def has_matching_validation_messages?
matched_validation_messages.compact.any?
end
def all_formatted_validation_error_messages
format_validation_errors(all_validation_errors)
end
@ -100,10 +104,7 @@ module Shoulda
end
def validation_messages_match?
validation_message_type_matches? && (
validation_messages.none? ||
matched_validation_messages.compact.any?
)
validation_message_type_matches? && has_matching_validation_messages?
end
def validation_messages

View File

@ -144,7 +144,7 @@ module Shoulda
]
translate_options =
{ default: default_translation_keys }.merge(options)
I18n.translate(primary_translation_key, translate_options)
I18n.translate(primary_translation_key.join('.'), translate_options)
end
end
end

View File

@ -79,7 +79,7 @@ module UnitTests
overrides[:changing_values_with]
)
if respond_to?(:write_attribute)
if respond_to?(:write_attribute) && attribute_names.include?(attribute_name)
write_attribute(attribute_name, new_value)
else
super(new_value)

View File

@ -57,9 +57,16 @@ describe Shoulda::Matchers::ActiveModel::ValidateAbsenceOfMatcher, type: :model
record = define_model(:example, attr: :string).new
message = <<-MESSAGE
Example did not properly validate that :attr is empty/falsy.
After setting :attr to "an arbitrary value", the matcher expected
the Example to be invalid, but it was valid instead.
Expected Example to fail validation when :attr is empty/falsy, but there
were some issues.
All of these submatchers should have passed:
should fail validation when :attr is set to "an arbitrary value",
producing a custom validation error
After setting :attr to "an arbitrary value", the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE
assertion = lambda do
@ -96,9 +103,16 @@ the Example to be invalid, but it was valid instead.
context 'an ActiveModel class without an absence validation' do
it 'rejects with the correct failure message' do
message = <<-MESSAGE
Example did not properly validate that :attr is empty/falsy.
After setting :attr to "an arbitrary value", the matcher expected
the Example to be invalid, but it was valid instead.
Expected Example to fail validation when :attr is empty/falsy, but there
were some issues.
All of these submatchers should have passed:
should fail validation when :attr is set to "an arbitrary value",
producing a custom validation error
After setting :attr to "an arbitrary value", the matcher expected
the Example to be invalid, but it was valid instead.
MESSAGE
assertion = lambda do
@ -160,9 +174,16 @@ the Example to be invalid, but it was valid instead.
model = having_and_belonging_to_many(:children, absence: false)
message = <<-MESSAGE
Parent did not properly validate that :children is empty/falsy.
After setting :children to [#<Child id: nil>], the matcher expected
the Parent to be invalid, but it was valid instead.
Expected Parent to fail validation when :children is empty/falsy, but
there were some issues.
All of these submatchers should have passed:
should fail validation when :children is set to [#<Child id: nil>],
producing a custom validation error
After setting :children to [#<Child id: nil>], the matcher expected
the Parent to be invalid, but it was valid instead.
MESSAGE
assertion = lambda do