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

new(type, array_matchers = [], keyword_matchers = {}) click to toggle source

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

===(target)
Alias for: call
[](target)
Alias for: call
call(target) click to toggle source

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
Also aliased as: ===, [], match?
match?(target)
Alias for: call
to_proc() click to toggle source

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

array_call(target) click to toggle source

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
case_match?(target, matcher) click to toggle source

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
combined_check(*checks) click to toggle source

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
hash_case_match?(target, match_key, matcher) click to toggle source

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
hash_method_case_match?(target, match_property, matcher) click to toggle source

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
hash_method_predicate_match?(target, match_key, match_predicate) click to toggle source

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
hash_recurse(target, matcher) click to toggle source

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
keyword_call(target) click to toggle source

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
match_hash_value?(target, match_key, matcher) click to toggle source

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
match_object_value?(target, match_property, matcher) click to toggle source

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
match_value?(target, matcher) click to toggle source

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
match_with(collection, &fn) click to toggle source

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
method_matches?(target, matcher) click to toggle source

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
method_send(target, matcher) click to toggle source

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