Class: Ward::Matchers::Has

Inherits:
Matcher
  • Object
show all
Defined in:
lib/ward/matchers/has.rb

Overview

Tests whether the actual value is a collection with the expected number of members, or contains a collection with the expected number of members.

Examples:

Setting the exact size for a collection


class Author
  validate do |author|
    # All mean the same thing.
    author.has(5).posts
    author.has.exactly(5).posts
  end
end

Setting the minimum size for a collection


class Author
  validate do |author|
    # All mean the same thing.
    author.has.at_least(5).posts
    author.has.gte(5).posts

    author.has.greater_than(4).posts
    author.has.more_than(4).posts
    author.has.gt(4).posts
  end
end

Setting the maximum size for a collection


class Author
  validate do |author|
    # All mean the same thing.
    author.has.at_most(5).posts
    author.has.lte(5).posts

    author.has.less_than(6).posts
    author.has.fewer_than(6).posts
    author.has.lt(6).posts
  end
end

Setting a range of acceptable sizes for a collection


class Author
  validate do |author|
    # All mean the same thing.
    author.has.between(1..5).posts
    author.has.between(1, 5).posts
  end
end

Instance Attribute Summary collapse

Attributes inherited from Matcher

#expected, #extra_args

Instance Method Summary collapse

Methods inherited from Matcher

error_id

Constructor Details

#initialize(expected = nil, *extra_args) ⇒ Has

Creates a new matcher instance.

If no expected value is provided, the matcher will default to expecting that the collection has at_least(1).

Parameters:

  • expected (Object) (defaults to: nil)

    The expected value for the matcher.



79
80
81
82
83
84
85
86
# File 'lib/ward/matchers/has.rb', line 79

def initialize(expected = nil, *extra_args)
  case expected
    when Range   then super ; between(expected)
    when Numeric then super ; eql(expected)
    when :no     then super ; eql(0)
    else              super(1, *extra_args) ; @relativity = :gte
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &block) ⇒ Ward::Matchers::Has (private)

TODO:

Capture args and block to be used when fetching the collection.

Sets the collection name to be used by the matcher.

Returns:



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/ward/matchers/has.rb', line 245

def method_missing(method, *args, &block)
  @collection_desc = ActiveSupport::Inflector.humanize(method).downcase

  @collection_desc = ActiveSupport::Inflector.singularize(
    @collection_desc) if @expected == 1

  unless method == :characters and ''.respond_to?(:characters)
    @collection_name, @plural_collection_name =
      method, ActiveSupport::Inflector.pluralize(method.to_s).to_sym
  end

  self
end

Instance Attribute Details

#collection_nameSymbol? (readonly)

The name of the collection whose size is being checked.

Returns:

  • (Symbol, nil)

    nil indicates that no collection name has been set, and the matcher will attempt to call size or length on the collection owner.



63
64
65
# File 'lib/ward/matchers/has.rb', line 63

def collection_name
  @collection_name
end

#relativitySymbol (readonly)

Returns how to test the length of the collection.

Returns:

  • (Symbol)


69
70
71
# File 'lib/ward/matchers/has.rb', line 69

def relativity
  @relativity
end

Instance Method Details

#between(n, upper = nil) ⇒ Ward::Matchers::Has

Sets that the collection size should be between two values.

Parameters:

  • n (Range, Numeric)

    The lowest boundary for the collection size. Alternatively, a range specifying the acceptable size of the collection.

  • upper (Numeric) (defaults to: nil)

    The lowest boundary for the collection size. Omit if a range is supplied as the first argument.

Returns:



208
209
210
211
212
213
214
215
216
217
# File 'lib/ward/matchers/has.rb', line 208

def between(n, upper = nil)
  if n.kind_of?(Range)
    set_relativity(:between, n)
  elsif upper.nil?
    raise ArgumentError,
      'You must supply an upper boundary for the collection size'
  else
    set_relativity(:between, (n..upper))
  end
end

#customise_error_values(values) ⇒ String

Adds extra information to the error message.

Parameters:

  • error (String)

Returns:

  • (String)


224
225
226
227
228
229
230
231
232
233
# File 'lib/ward/matchers/has.rb', line 224

def customise_error_values(values)
  values[:collection] = @collection_desc || 'items'

  if @relativity == :between
    values[:lower] = @expected.first
    values[:upper] = @expected.last
  end

  values
end

#eql(n) ⇒ Ward::Matchers::Has Also known as: exactly

Sets that the collection should be the exact size of the expectation.

Parameters:

  • n (Numeric)

    The exact expected size of the collection.

Returns:



161
162
163
# File 'lib/ward/matchers/has.rb', line 161

def eql(n)
  set_relativity(:eql, n)
end

#gt(n) ⇒ Ward::Matchers::Has Also known as: greater_than, more_than

Sets that the collection should be greater than the expected value.

Parameters:

  • n (Numeric)

    The minimum size of the collection - 1.

Returns:



189
190
191
# File 'lib/ward/matchers/has.rb', line 189

def gt(n)
  set_relativity(:gte, n + 1)
end

#gte(n) ⇒ Ward::Matchers::Has Also known as: at_least

Sets that the collection should be no smaller than the expected value.

Parameters:

  • n (Numeric)

    The minimum size of the collection.

Returns:



175
176
177
# File 'lib/ward/matchers/has.rb', line 175

def gte(n)
  set_relativity(:gte, n)
end

#lt(n) ⇒ Ward::Matchers::Has Also known as: fewer_than, less_than

Sets that the collection should be smaller than the expected value.

Parameters:

  • n (Numeric)

    The maximum size of the collection + 1.

Returns:



132
133
134
# File 'lib/ward/matchers/has.rb', line 132

def lt(n)
  set_relativity(:lte, n - 1)
end

#lte(n) ⇒ Ward::Matchers::Has Also known as: at_most

Sets that the collection should be no larger than the expected value.

Parameters:

  • n (Numeric)

    The maximum size of the collection.

Returns:



147
148
149
# File 'lib/ward/matchers/has.rb', line 147

def lte(n)
  set_relativity(:lte, n)
end

#matches?(collection) ⇒ Boolean

Returns whether the given value is satisfied by the expected block.

Parameters:

  • collection (Object)

    The collection or the collection owner.

Returns:

  • (Boolean)


95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/ward/matchers/has.rb', line 95

def matches?(collection)
  unless @collection_name.nil?
    if collection.respond_to?(@collection_name)
      collection = collection.__send__(@collection_name)
    elsif collection.respond_to?(@plural_collection_name)
      collection = collection.__send__(@plural_collection_name)
    end
  end

  if collection.respond_to?(:length)
    actual = collection.length
  elsif collection.respond_to?(:size)
    actual = collection.size
  else
    raise RuntimeError,
      'The given value is not a collection (it does not respond to ' \
      '#length or #size)'
  end

  result = case @relativity
    when :eql, nil then actual == (@expected == :no ? 0 : @expected)
    when :lte      then actual <=  @expected
    when :gte      then actual >=  @expected
    when :between  then @expected.include?(actual)
  end

  [result, @relativity || :eql]
end