318

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?

Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
hectorsq
  • 74,396
  • 19
  • 43
  • 46

18 Answers18

491
unless discount.nil? || discount == 0
  # ...
end
Dejan Simic
  • 7,820
  • 2
  • 28
  • 15
  • 43
    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 – Tom G Apr 24 '14 at 09:24
  • 72
    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. – Andre Figueiredo May 21 '15 at 19:13
  • 1
    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!). – Sridhar Sarnobat Feb 16 '18 at 21:13
  • 1
    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. – dlouzan Jan 28 '20 at 10:16
  • RuboCop tells me that `.zero?` is to be preferred over `== 0` (same for negated) – Cadoiz Aug 09 '23 at 10:33
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.

madlep
  • 47,370
  • 7
  • 42
  • 53
  • 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) – Cadoiz Aug 09 '23 at 10:30
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.

rewritten
  • 16,280
  • 2
  • 47
  • 50
  • 10
    Note that this is a **rails-specific** answer. Vanilla ruby does not have a `try` method. – Tom Lord Jun 06 '17 at 14:36
  • 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. – rewritten Jun 07 '17 at 19:42
  • 2
    The answer now duplicates https://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. – Tom Lord Jan 25 '19 at 09:58
34

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?
ndnenkov
  • 35,425
  • 9
  • 72
  • 104
  • **`"foo"&.nonzero? # => NoMethodError: undefined method 'nonzero?' for "foo":String`** .... This is not safe to use on arbitrary objects. – Tom Lord Jun 06 '17 at 14:38
  • 3
    @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`. – ndnenkov Jun 06 '17 at 14:44
  • 1
    @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. – ndnenkov Jun 06 '17 at 14:50
  • 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. – Cadoiz Aug 09 '23 at 10:17
29
unless [nil, 0].include?(discount) 
  # ...
end
Dejan Simic
  • 7,820
  • 2
  • 28
  • 15
23

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.

Paige Ruten
  • 172,675
  • 36
  • 177
  • 197
  • I'm doing something like `discount.respond_to?(:nonzero) ? discount : discount.nonzero?` for robustness. This is also very rubyish as `5.nonzero? # => 5` – Cadoiz Aug 09 '23 at 10:19
18
if (discount||0) != 0
  #...
end
Raimonds Simanovskis
  • 2,948
  • 1
  • 21
  • 17
11

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

"".to_i.zero? => true
nil.to_i.zero? => true
oivoodoo
  • 774
  • 7
  • 23
3
if discount and discount != 0
  ..
end

update, it will false for discount = false

rubyprince
  • 17,559
  • 11
  • 64
  • 104
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".

NIshank
  • 97
  • 6
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.

Dave G-W
  • 57
  • 4
  • 1
    Is this not the same as @oivoodo's answer? – Cary Swoveland May 29 '15 at 06:59
  • 1
    **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`. – Tom Lord Jun 06 '17 at 14:48
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.

Saroj
  • 1,551
  • 2
  • 14
  • 30
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.

Ozesh
  • 6,536
  • 1
  • 25
  • 23
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
pastullo
  • 4,171
  • 3
  • 30
  • 36
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.

Ed S.
  • 122,712
  • 22
  • 185
  • 265
0
if discount.nil? || discount == 0
  [do something]
end
Abhinay Reddy Keesara
  • 9,763
  • 2
  • 18
  • 28
-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
RichOrElse
  • 114
  • 5
-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
Andrew Grimm
  • 78,473
  • 57
  • 200
  • 338
Jeff Waltzer
  • 532
  • 5
  • 12