325

I am using the following code to check if a variable is not nil and not zero

if(discount != nil && discount != 0) 
  ...
end

Is there a better way to do this?

3
  • 1
    Probably because it's an exact copy of stackoverflow.com/questions/209495/…. Commented Oct 31, 2008 at 0:42
  • 1
    What should it do if discount is false? Commented Mar 16, 2011 at 3:34
  • 3
    I think discount.in? [0, nil] the cleaner way possible Commented Mar 20, 2019 at 13:15

19 Answers 19

501
unless discount.nil? || discount == 0
  # ...
end
Sign up to request clarification or add additional context in comments.

5 Comments

Using 'or' is dangerous. 'or' has lower operator presendence than '=', so the following has unexpected behaviour: a = false or true #a is false after this statement
Current "Ruby Style Guide" stands The and and or keywords are banned. It's just not worth it. Always use && and || instead.. And it's right, for David and Tom reasons.
I took 20 minutes to realize it is nil (not nul)! This is what it's like to be a newbie at programming (for someone who's been writing code for 17 years!).
Since this is the accepted answer, I'd like to point out that unless with multiple boolean conditions is discouraged by multiple sources (just google a bit...). It's more difficult to reason about what is happening because the logic is reversed. I'd really just keep this with the if clause.
RuboCop tells me that .zero? is to be preferred over == 0 (same for negated)
42
class Object
  def nil_zero?
    self.nil? || self == 0
  end
end

# which lets you do
nil.nil_zero? # returns true
0.nil_zero?   # returns true
1.nil_zero?   # returns false
"a".nil_zero? # returns false

unless discount.nil_zero?
  # do stuff...
end

Beware of the usual disclaimers... great power/responsibility, monkey patching leading to the dark side etc.

1 Comment

To my understanding, the opposite was asked, so you could also write def real_value; self != 0 ? self : nil; end, maybe even additionally. I couldn't come up with a better name then real_value. And RuboCop tells me that .zero? is to be preferred over == 0 (same for negated)
38

From Ruby 2.3.0 onward, you can combine the safe navigation operator (&.) with Numeric#nonzero?. &. returns nil if the instance was nil and nonzero? - if the number was 0:

if discount&.nonzero?
  # ...
end

Or postfix:

do_something if discount&.nonzero?

7 Comments

"foo"&.nonzero? # => NoMethodError: undefined method 'nonzero?' for "foo":String .... This is not safe to use on arbitrary objects.
@TomLord, as stated in the previous comment, this was not intended to work with arbitrary objects. Instead it is concerned with the case when you have something you know should be a number, but might also be nil.
@TomLord, it is stated in the answer "nonzero? - if the number was 0". Also the need to check if a completely arbitrary object is 0 arises extremely rarely compared to that to check a number that may or may not be nil. Hence it is almost implied. Even if someone somehow makes the contrary assumption, they will instantly understand what is happening when they try to execute it.
I do think that @TomLord has a point and use constructs like value.respond_to?(:nonzero) && value.nonzero? all over my code for that exact reason.
Minor nit, but Ruby's &. operator is "conditional send", not "safe navigation". There's a meaningful distinction: blog.jez.io/conditional-send
|
35

ok, after 5 years have passed....

if discount.try :nonzero?
  ...
end

It's important to note that try is defined in the ActiveSupport gem, so it is not available in plain ruby.

3 Comments

Note that this is a rails-specific answer. Vanilla ruby does not have a try method.
Correct. Although it is more like ActiveSupport-specific, which is a much lighter and widely used dependency than the full rails. Anyway now @ndn's response is the right one.
The answer now duplicates stackoverflow.com/a/34819818/1954610 ... I think there is value in leaving it as try to show the alternative option (this is why it was upvoted in the first place!), so long as it's clear to the reader that ActiveSupport is not vanilla ruby.
29
unless [nil, 0].include?(discount) 
  # ...
end

3 Comments

Beautiful? Yes. Readable? Not really.
I find this perfectly readable, and I would prefer it over a new class. Well done.
The most rubyist approach of handling two conditions.
25

You could do this:

if (!discount.nil? && !discount.zero?)

The order is important here, because if discount is nil, then it will not have a zero? method. Ruby's short-circuit evaluation should prevent it from trying to evaluate discount.zero?, however, if discount is nil.

1 Comment

I'm doing something like discount.respond_to?(:nonzero) ? discount : discount.nonzero? for robustness. This is also very rubyish as 5.nonzero? # => 5
18
if (discount||0) != 0
  #...
end

1 Comment

RuboCop tells me that .zero? is to be preferred over == 0 (same for negated)
11

You can convert your empty row to integer value and check zero?.

"".to_i.zero? => true
nil.to_i.zero? => true

2 Comments

careful: 0.1.to_i == 0
@oivoodoo I think .to_f is superior then .to_i in respect to the previous comment.
3
if discount and discount != 0
  ..
end

update, it will false for discount = false

Comments

3

Yes, we do have a clean way in ruby.

discount.to_f.zero?

This check handles good amount of cases i.e. discount may be nil, discount may be int 0, discount may be float 0.0, discount may be string "0.0", "0".

Comments

2

You can take advantage of the NilClass provided #to_i method, which will return zero for nil values:

unless discount.to_i.zero?
  # Code here
end

If discount can be fractional numbers, you can use #to_f instead, to prevent the number from being rounded to zero.

1 Comment

Does not work for arbitrary objects. "".to_i == "foo".to_i == "0".to_i == 0. Your method will make all sorts of unintended type coercions. It will also fail with a NoMethodError if discount does not respond to to_i.
2
def is_nil_and_zero(data)
     data.blank? || data == 0 
end  

If we pass "" it will return false whereas blank? returns true. Same is the case when data = false blank? returns true for nil, false, empty, or a whitespace string. So it's better to use blank? method to avoid empty string as well.

3 Comments

blank? is a rails-specific method, and not available in vanilla ruby.
You are correct!! I thought that this is related to "ror" tag so posted here. My mistake. This is not gonna work in vanilla ruby.
I would name this method blank_or_zero?
2

I prefer using a more cleaner approach :

val.to_i.zero?

val.to_i will return a 0 if val is a nil,

after that, all we need to do is check whether the final value is a zero.

Comments

1

When dealing with a database record, I like to initialize all empty values with 0, using the migration helper:

add_column :products, :price, :integer, default: 0

Comments

0

You could initialize discount to 0 as long as your code is guaranteed not to try and use it before it is initialized. That would remove one check I suppose, I can't think of anything else.

Comments

0
if discount.nil? || discount == 0
  [do something]
end

Comments

0

In modern rails I like using positive? in this context. positive? returns a boolean, but nonzero? returns either nil or a value.

if(discount&.positive?) 
  ...
end

Examples:

With nonzero?

[nil,0, 1].map{|v| v&.nonzero?}
=> [nil, nil, 1]

And with positive?

[nil,0, 1].map{|v| v&.positive?}
=> [nil, false, true]

Comments

-1

Alternative solution is to use Refinements, like so:

module Nothingness
  refine Numeric do
    alias_method :nothing?, :zero?
  end

  refine NilClass do
    alias_method :nothing?, :nil?
  end
end

using Nothingness

if discount.nothing?
  # do something
end

Comments

-7

I believe the following is good enough for ruby code. I don't think I could write a unit test that shows any difference between this and the original.

if discount != 0
end

1 Comment

It'd evaluate to true if discount were nil.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.