class Qo::Matchers::Matcher
Matcher
used to determine whether a value matches a certain set of conditions
@author baweaver @since 1.0.0
Public Class Methods
Creates a new matcher
@param type [String]
Type of the matcher: any, all, or none. Used to determine how a match is determined
@param array_matchers = [] [Array]
Conditions given as an array
@param keyword_matchers = {} [Hash[Any, Any]]
Conditions given as keywords
@return [Qo::Matchers::Matcher]
# File lib/qo/matchers/matcher.rb, line 23 def initialize(type, array_matchers = [], keyword_matchers = {}) @type = type @array_matchers = array_matchers @keyword_matchers = keyword_matchers end
Public Instance Methods
Calls the matcher on a given target value
@param target [Any]
Target to match against
@return [Boolean]
Whether or not the target matched
# File lib/qo/matchers/matcher.rb, line 43 def call(target) combined_check(array_call(target), keyword_call(target)) end
Proc-ified version of `call`
@return [Proc => Boolean]
# File lib/qo/matchers/matcher.rb, line 32 def to_proc -> target { self.call(target) } end
Private Instance Methods
Runs the matcher directly.
If the target is an Array, it will be matched via index
If the target is an Object, it will be matched via public send
@param target [Any]
Target to match against
@return [Boolean]
Result of the match
# File lib/qo/matchers/matcher.rb, line 80 def array_call(target) return true if @array_matchers == target if target.is_a?(::Array) return false unless target.size == @array_matchers.size match_with(@array_matchers.each_with_index) { |matcher, i| match_value?(target[i], matcher) } else match_with(@array_matchers) { |matcher| match_value?(target, matcher) } end end
Wraps a case equality statement to make it a bit easier to read. The typical left bias of `===` can be confusing reading down a page, so more of a clarity thing than anything. Also makes for nicer stack traces.
@param target [Any]
Target to match against
@param matcher [#===]
Anything that responds to ===, preferably in a unique and entertaining way.
@return [Boolean]
# File lib/qo/matchers/matcher.rb, line 107 def case_match?(target, matcher) matcher === target end
When combining array and keyword type matchers, depending on how we're matching we may need to combine them slightly differently.
@param *checks [Array]
The checks we're combining
@return [Boolean]
Whether or not there's a match
# File lib/qo/matchers/matcher.rb, line 289 def combined_check(*checks) case @type when 'and', 'not' then checks.all? when 'or' then checks.any? else false end end
Double wraps case match in order to ensure that we try against both Symbol and String variants of the keys, as this is a very common mixup in Ruby.
@param target [Hash]
Target of the match
@param match_key [Symbol]
Key to match against
@param matcher [#===]
Matcher
@return [Boolean]
# File lib/qo/matchers/matcher.rb, line 210 def hash_case_match?(target, match_key, matcher) return true if case_match?(target[match_key], matcher) return false unless target.keys.first.is_a?(String) match_key.respond_to?(:to_s) && target.key?(match_key.to_s) && case_match?(target[match_key.to_s], matcher) end
Attempts to run a case match against a method call derived from a hash key, and checks the result.
@param target [Hash]
Target of the match
@param match_property [Symbol]
Method to call
@param matcher [#===]
Matcher
@return [Boolean]
# File lib/qo/matchers/matcher.rb, line 248 def hash_method_case_match?(target, match_property, matcher) case_match?(method_send(target, match_property), matcher) end
Attempts to run a matcher as a predicate method against the target
@param target [Hash]
Target of the match
@param match_key [Symbol]
Method to call
@param match_predicate [Symbol]
Matcher
@return [Boolean]
# File lib/qo/matchers/matcher.rb, line 231 def hash_method_predicate_match?(target, match_key, match_predicate) method_matches?(target[match_key], match_predicate) end
Recurses on nested hashes.
@param target [Hash]
Target to recurse into
@param matcher [Hash]
Matcher to use to recurse with
@return [Boolean]
# File lib/qo/matchers/matcher.rb, line 261 def hash_recurse(target, matcher) Qo::Matchers::Matcher.new(@type, [], matcher).call(target) end
Used to match against a matcher made from Keyword Arguments (a Hash)
@param matchers [Hash[Any, ===
]]
Any key mapping to any value that responds to `===`. Notedly more satisfying when `===` does something fun.
@return [Boolean]
Result of the match
# File lib/qo/matchers/matcher.rb, line 59 def keyword_call(target) return true if @keyword_matchers == target match_fn = target.is_a?(::Hash) ? Proc.new { |match_key, matcher| match_hash_value?(target, match_key, matcher) } : Proc.new { |match_key, matcher| match_object_value?(target, match_key, matcher) } match_with(@keyword_matchers, &match_fn) end
Checks if a hash value matches a given matcher
@param target [Any]
Target of the match
@param match_key [Symbol]
Key of the hash to reference
@param matcher [#===]
Any matcher responding to ===
@return [Boolean]
Match status
# File lib/qo/matchers/matcher.rb, line 170 def match_hash_value?(target, match_key, matcher) return false unless target.key?(match_key) return hash_recurse(target[match_key], matcher) if target.is_a?(Hash) && matcher.is_a?(Hash) hash_case_match?(target, match_key, matcher) || hash_method_predicate_match?(target, match_key, matcher) end
Checks if an object property matches a given matcher
@param target [Any]
Target of the match
@param match_property [Symbol]
Property of the object to reference
@param matcher [#===]
Any matcher responding to ===
@return [Boolean] Match status
# File lib/qo/matchers/matcher.rb, line 191 def match_object_value?(target, match_property, matcher) return false unless target.respond_to?(match_property) hash_method_case_match?(target, match_property, matcher) end
Defines what it means for a value to match a matcher
@param target [Any]
Target to match against
@param matcher [Any]
Any matcher to run against, most frequently responds to ===
@return [Boolean]
Match status
# File lib/qo/matchers/matcher.rb, line 152 def match_value?(target, matcher) case_match?(target, matcher) || method_matches?(target, matcher) end
Runs the relevant match method against the given collection with the given matcher function.
@param collection [Enumerable] Any collection that can be enumerated over @param fn [Proc] Function to match with
@return [Boolean] Result of the match
# File lib/qo/matchers/matcher.rb, line 272 def match_with(collection, &fn) case @type when 'and' then collection.all?(&fn) when 'or' then collection.any?(&fn) when 'not' then collection.none?(&fn) else false end end
Predicate variant of `method_send` with the same guard concerns
@param target [Any]
Object to send to
@param matcher [#to_sym]
Anything that can be coerced into a method name
@return [Boolean]
Success status of predicate
# File lib/qo/matchers/matcher.rb, line 138 def method_matches?(target, matcher) !!method_send(target, matcher) end
Guarded version of `public_send` meant to stamp out more obscure errors when running against non-matching types.
@param target [Any]
Object to send to
@param matcher [#to_sym]
Anything that can be coerced into a method name
@return [Any]
Response of sending to the method, or false if failed
# File lib/qo/matchers/matcher.rb, line 122 def method_send(target, matcher) matcher.respond_to?(:to_sym) && target.respond_to?(matcher.to_sym) && target.public_send(matcher) end