lib/rspec/matchers/built_in/have_attributes.rb
module RSpec
module Matchers
module BuiltIn
# @api private
# Provides the implementation for `have_attributes`.
# Not intended to be instantiated directly.
class HaveAttributes < BaseMatcher
# @private
attr_reader :respond_to_failed
def initialize(expected)
@expected = expected
@values = {}
@respond_to_failed = false
@negated = false
end
# @private
def actual
@values
end
# @api private
# @return [Boolean]
def matches?(actual)
@actual = actual
@negated = false
return false unless respond_to_attributes?
perform_match(:all?)
end
# @api private
# @return [Boolean]
def does_not_match?(actual)
@actual = actual
@negated = true
return false unless respond_to_attributes?
perform_match(:none?)
end
# @api private
# @return [String]
def description
described_items = surface_descriptions_in(expected)
improve_hash_formatting "have attributes #{RSpec::Support::ObjectFormatter.format(described_items)}"
end
# @api private
# @return [Boolean]
def diffable?
!@respond_to_failed && !@negated
end
# @api private
# @return [String]
def failure_message
respond_to_failure_message_or do
"expected #{actual_formatted} to #{description} but had attributes #{ formatted_values }"
end
end
# @api private
# @return [String]
def failure_message_when_negated
respond_to_failure_message_or { "expected #{actual_formatted} not to #{description}" }
end
private
def cache_all_values
@values = {}
expected.each do |attribute_key, _attribute_value|
actual_value = @actual.__send__(attribute_key)
@values[attribute_key] = actual_value
end
end
def perform_match(predicate)
cache_all_values
expected.__send__(predicate) do |attribute_key, attribute_value|
actual_has_attribute?(attribute_key, attribute_value)
end
end
def actual_has_attribute?(attribute_key, attribute_value)
values_match?(attribute_value, @values.fetch(attribute_key))
end
def respond_to_attributes?
matches = respond_to_matcher.matches?(@actual)
@respond_to_failed = !matches
matches
end
def respond_to_matcher
@respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments.tap { |m| m.ignoring_method_signature_failure! }
end
def respond_to_failure_message_or
if respond_to_failed
respond_to_matcher.failure_message
else
improve_hash_formatting(yield)
end
end
def formatted_values
values = RSpec::Support::ObjectFormatter.format(@values)
improve_hash_formatting(values)
end
end
end
end
end