Is there a comparison Operator that returns true if both values are set and equal: false if both are nil

Viewed 242

Is there an ideomatic or shorter way to "compare two values but don't allow them to be nil".

One way would be:

(!left.nil? || !right.nil?) && (left == right)

One of the nil checks could be omitted, here, but I left it in the example, to show the intent better.

Is there a method in Ruby core, on Kernel or BasicObject, next to equal?, eql?, == and such that matches this?

I aim at something similar to what e.g. minitest assert_equal does (edited for brevity, nil-check at line 5):

def assert_equal exp, act, msg = nil
  msg = message(msg, E) { diff exp, act }
  result = assert exp == act, msg

  if nil == exp then
    refute_nil exp, "Use assert_nil if expecting nil."
  end

  result
end

In my case this is for an autorization system where I repeatedly have to guard against both sides being nil. E.g.

class ContactPolicy < ApplicationPolicy
  def add?
    !actor.id.nil? && subject.id == actor.id
  end
end

After repeating that pattern in various forms, I wondered if there is not a better Ruby-ism for this. I am now leaning towards a refactoring using null-objects that have a def ==(other) which always return false. The question of whether this "equal but not nil" remains interesting though.

1 Answers

Possible Approaches

The optimal answer will depend on why you think either value might be nil, and other aspects of your code that are not shown in your original question. Without that context, it appears that this is primarily an attempt to guard against nil values raising a NameError exception, while also preventing your expression from resolving to (nil == nil) == true. If that's the case, you can take one of the following approaches, among others:

  1. Rescue all possible exceptions.
  2. Rescue NameError explicitly.
  3. Avoid exception handling, and use a conditional expression chain to test whether your variables are both defined and not-nil.

Handle NameError Exceptions from Undefined Variables

You might rescue all exceptions, but this is generally considered a poor approach in the general case.

# Swallow anything descended from Exception. This is 
# common in the wild, so it's idiomatic by definition,
# but it can cast too wide of a net sometimes, and may
# lead to unexpected problems.
left == right rescue nil

It would be better to rescue NameError, but it's up to you to figure out how the program should handle the exception. Perhaps you will decide to set the variables, and then retry the rescued block, but there are certainly other approaches.

# Rescue just NameError; other exceptions would still
# get raised when necessary.
begin
  left == right
rescue NameError => e
  # handle the exception
end

Avoiding Undefined Variables

There are a number of situations where Ruby will autovivify variables within a given scope, but your example isn't one of them. In this case, you can rely on operator precedence to ensure your values are defined using the defined? keyword.

For example, assuming left and right are either undefined? or can respond_to? :nil?, then you can use the following expression to:

  1. Ensure both left and right are defined. Returns "expression" if they are both defined variables within an array literal.
  2. Ensure neither of the values in [left, right] are nil using Enumerable#none? (which is mixed into Array) and the Object#nil? method that Array inherits.
  3. Check equality of your two defined, non-nil variables.
defined? [left, right] and
  [left, right].none? &:nil? and
  left == right
#=> nil

Based on your question, the expectation is that when your variables resolve to nil == nil the whole expression should still return false. The same conditional expression also guards against both variables being set to nil rather than simply being undefined. For example:

# Test with nil values.
left, right = nil, nil

defined? [left, right] and
  [left, right].none? &:nil? and
  left == right
#=> false
Related