Class: Matchi::Predicate

Inherits:
Object
  • Object
show all
Defined in:
lib/matchi/predicate.rb

Overview

Predicate matcher that checks if an object responds to a predicate method with a truthy value.

This matcher converts a predicate name (starting with ‘be_’ or ‘have_’) into a method call ending with ‘?’ and verifies that calling this method returns a boolean value. It’s useful for testing state-checking methods and collection properties. The matcher supports two types of predicate formats: ‘be_*’ which converts to ‘*?’ and ‘have_*’ which converts to ‘has_*?’.

Examples:

Basic empty check

matcher = Matchi::Predicate.new(:be_empty)
matcher.match? { [] }        # => true
matcher.match? { [1, 2] }    # => false

Object property check with arguments

matcher = Matchi::Predicate.new(:have_key, :name)
matcher.match? { { name: "Alice" } }  # => true
matcher.match? { { age: 30 } }        # => false

Using keyword arguments

class Record
  def complete?(status: nil)
    status.nil? || status == :validated
  end
end

matcher = Matchi::Predicate.new(:be_complete, status: :validated)
matcher.match? { Record.new }  # => true

With block arguments

class List
  def all?(&block)
    block ? super : empty?
  end
end

matcher = Matchi::Predicate.new(:be_all) { |x| x.positive? }
matcher.match? { [1, 2, 3] }    # => true
matcher.match? { [-1, 2, 3] }   # => false

See Also:

Constant Summary collapse

PREFIXES =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

Mapping of predicate prefixes to their method name transformations. Each entry defines how a prefix should be converted to its method form.

{
  "be_"   => ->(name) { "#{name.gsub(/\A(?:be_)/, "")}?" },
  "have_" => ->(name) { "#{name.gsub(/\A(?:have_)/, "has_")}?" }
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(name, *args, **kwargs, &block) ⇒ Predicate

Initialize the matcher with a predicate name and optional arguments.

Examples:

With simple predicate

Predicate.new(:be_empty)                # Empty check

With arguments

Predicate.new(:have_key, :id)           # Key presence check

With keyword arguments

Predicate.new(:be_valid, status: true)  # Conditional validation

Parameters:

  • name (#to_s)

    A matcher name starting with ‘be_’ or ‘have_’

  • args (Array)

    Optional positional arguments to pass to the predicate method

  • kwargs (Hash)

    Optional keyword arguments to pass to the predicate method

  • block (Proc)

    Optional block to pass to the predicate method

Raises:

  • (ArgumentError)

    if the predicate name format is invalid



74
75
76
77
78
79
80
81
# File 'lib/matchi/predicate.rb', line 74

def initialize(name, *args, **kwargs, &block)
  @name = String(name)
  raise ::ArgumentError, "invalid predicate name format" unless valid_name?

  @args = args
  @kwargs = kwargs
  @block = block
end

Instance Method Details

#match? { ... } ⇒ Boolean

Checks if the yielded object responds to and returns true for the predicate.

This method converts the predicate name into a method name according to the prefix mapping and calls it on the yielded object with any provided arguments. The method must return a boolean value, or a TypeError will be raised.

Examples:

Basic usage

matcher = Predicate.new(:be_empty)
matcher.match? { [] }                # => true
matcher.match? { [1] }               # => false

With arguments

matcher = Predicate.new(:have_key, :id)
matcher.match? { { id: 1 } }         # => true

Yields:

  • Block that returns the object to check

Yield Returns:

  • (Object)

    The object to call the predicate method on

Returns:

  • (Boolean)

    true if the predicate method returns true

Raises:

  • (ArgumentError)

    if no block is provided

  • (TypeError)

    if predicate method returns non-boolean value



107
108
109
110
111
112
113
114
# File 'lib/matchi/predicate.rb', line 107

def match?
  raise ::ArgumentError, "a block must be provided" unless block_given?

  value = yield.send(method_name, *@args, **@kwargs, &@block)
  return value if [false, true].include?(value)

  raise ::TypeError, "Boolean expected, but #{value.class} instance returned."
end

#to_sString

Returns a human-readable description of the matcher.

Examples:

Simple predicate

Predicate.new(:be_empty).to_s                # => "be empty"

With arguments

Predicate.new(:have_key, :id).to_s           # => "have key :id"

With keyword arguments

Predicate.new(:be_valid, active: true).to_s  # => "be valid active: true"

Returns:

  • (String)

    A string describing what this matcher verifies



130
131
132
133
134
135
136
137
138
# File 'lib/matchi/predicate.rb', line 130

def to_s
  (
    "#{@name.tr("_", " ")} " + [
      @args.map(&:inspect).join(", "),
      @kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", "),
      (@block.nil? ? "" : "&block")
    ].reject { |i| i.eql?("") }.join(", ")
  ).strip
end