2

I have a rubocop problem while doing my own ruby enumerables. I used a === and rubocop is asking me to change it. But every time I try to put something different my method stops working as desired.

module Enumerable
  def my_all?(arg = nil)
    return true if !block_given? && include?(nil) == false && include?(false) == false && arg.nil?
    return false unless block_given? || arg.nil? == false || empty?
    
    if block_given?
      my_each do |x|
        return false unless yield(x)
      end
    elsif arg.class == Regexp
      my_each do |x|
        return false unless x.match(arg)
      end
    elsif arg.class == Numeric
      my_each do |x|
        return false unless x.is_a? arg
      end
    else
      my_each do |x|
        return false unless arg === x
      end
    end
    true
  end
end

That last === is offering resistance to be changed. Thanks to the ones that are able to understand this and help!

Holger Just
  • 52,918
  • 14
  • 115
  • 123
Cat-Mouse
  • 35
  • 5
  • What exactly does Rubocop say? – anothermh Jul 29 '20 at 14:58
  • What is arg and what is x in that scenario? Is Rubocop suggesting you use a method like `arg.kind_of?(x)`? – user115014 Jul 29 '20 at 15:06
  • Rubocop just says: Avoid the use of case equality operator. The code works perfectly but it just has this lint error. Arg is the parameters that the method can take and x is the object or the thing that the method iterates with. I'm sorry I'm a beginner and I still don't know how is everything properly called. – Cat-Mouse Jul 29 '20 at 15:12
  • Pay attention here that `1 === Integer` => false; but `Integer === 1` => true` – Yurii Verbytskyi Jul 29 '20 at 15:21
  • Some weird things here: `elsif arg.class == Numeric` means, that arg is an instance of Numeric, but than you try to do `unless x.is_a? arg` that should raise an error, like in case of `1.is_a?(1)` => TypeError (class or module required) – Yurii Verbytskyi Jul 29 '20 at 15:26
  • Its inside module Enumerable – Cat-Mouse Jul 29 '20 at 15:48

2 Answers2

4

The docs for Enumerable#all? specifically say that when a pattern is given:

[...] the method returns whether pattern === element for every collection member.

So in order to replicate the method you actually have to call ===. Trying to substitute it just to please Rubocop would likely result in a different behavior.

In your case, I'd disable the cop using an inline comment:

my_each do |x|
  return false unless arg === x # rubocop:disable Style/CaseEquality
end
Stefan
  • 109,145
  • 14
  • 143
  • 218
3

The == method means is equivalent to, but === is a special purpose one that sometimes means that and more.

For Class the === method means is inherited from, not equivalent to, or in other words the two are the same:

  x === y
  x.is_a?(y)

Where you'll see how this plays out:

  String == String
  # => true
  String === String
  # => false

As String is a class, it is not a subclass of itself.

That being said, here's a Ruby idiomatic refactoring of your code:

module Enumerable
  def my_all?(arg = nil)
    return true if !block_given? && !include?(nil) && !include?(false) && arg.nil?
    return false unless block_given? || !arg.nil? || empty?
    
    if block_given?
      my_each do |x|
        return false unless yield(x)
      end
    else
      case arg
      when Regexp
        my_each do |x|
          return false unless x.match(arg)
        end
      when Numeric
        my_each do |x|
          return false unless x.is_a? arg
        end
      else
        my_each do |x|
          return false unless arg === x
        end
      end
    end

    true
  end
end

Where case internally uses the === method for comparisons so you can easily break out behaviours based on types.

tadman
  • 208,517
  • 23
  • 234
  • 262