Rails 3.1: Ruby idiom to prevent .each from throwing exception if nil?
Asked Answered
T

7

29

Is there a way to use .each so it does not throw an error if the object is nil or empty (without adding an additional nil/blank test?

It seems that if I say phonelist.each do |phone| that if phonelist is empty, then the block should not be executed.

But in my view (haml) I have - @myvar.phonelist.each do |phone| and if phonelist is empty, it throws a NoMethodError.

I run into this a lot, and always workaround by adding an explicit check/branch for .blank? but it seems there should be an easier way to tell .each that empty means do nothing.

Tilley answered 6/3, 2012 at 23:44 Comment(6)
Calling each on an empty enumerable should just do nothing.Predispose
@AndrewMarshall but that's not what's happening, he's calling each on nil.Ethiop
@AndrewMarshall: He meant a nil object, not an empty collection.Zeller
YEP. Thanks. Stupid bug. A helper method was setting to nil not []. Thanks for your comments.Tilley
The question states "so it does not throw an error if the object is nil or empty", I was commenting on that fact.Predispose
@jpwynn Editing your question with "SOLVED" and an explanation isn't really a good idea. Either accept one of the answers if they solved it, or post your own answer with the solution.Predispose
M
47

You can use the try method to call .each on a nil so it does not throw an error if the object is nil or empty.

phonelist = nil
phonelist.try(:each){|i| puts i}
Montcalm answered 7/3, 2012 at 4:2 Comment(4)
I guess I don't understand why people resort to something like this when it would be optimal to simply ensure that something that should not ever be nil is not ever nil. If that cannot be done (for whatever reason), why would you do this instead of a simple if statement? I don't get it.Zeller
I agree that it would be optimal to ensure you will never get a nil, but I don't see why adding an if statement is a better solution then using try. Either approach should do the job.Montcalm
I guess I don't think having to repeat variable names is clearer or simpler in every situation (especially if long variable names are used), but the nice thing about try is that you can continue to chain together methods without having to break it up with an if. It really depends on the situation of course.Montcalm
This clever and clean, but still just a band-aid. I agree with Ed S. that you should do something different with the object so that it returned something other than nil (like an empty array).Oligarch
R
34

Simply do the following:

Array(phonelist).each do |phone|
  #deal with your phone
end

Array(my_variable) will ensure to return an array if my_variable is nil.

It doesn't create a new Array if my_variable is already an array, so it is safe and light to use it wherever you want !

Riel answered 19/9, 2013 at 12:1 Comment(1)
I like it. Simple and elegantThurman
Z
17

You're attempting to smack a band-aid on a larger problem.

Ruby has a concept of nil; can't get around it. If you are calling a method on nil, then you are assuming it is valid, i.e., your design assumes it to be valid. So the question really is: where is the hole in your design? Why is your assumption incorrect?

The problem here is not that you cannot call arbitrary methods on an object which does not support it; the problem is that your data is assumed to be valid when obviously that is not always the case.

But in my view (haml) I have - @myvar.phonelist.each do |phone| and if phonelist is empty, it throws a NoMethodError.

No. If phonelist is not an object which implements .each it throws an error. Very different.

You can always initialize it to an empty array if null, i.e., phonelist ||= [], but I would prefer a design which ensures valid data whenever possible.

Zeller answered 6/3, 2012 at 23:46 Comment(2)
Sometimes you don't control the data structures returned from apis. Things will be nil, the question is validMendenhall
Ok, then obviously you have to check for null in that situation. Don't see what that has to do with this question.Zeller
B
8

Can't believe no one has suggested this yet:

(@myvar.phonelist || []).each do |phone|
   ...

If phonelist is nil, the each will loop on the empty array, executing the block zero times.

HOWEVER, this will still throw an exception if phonelist is not an enumerable (e.g. an array).

Baruch answered 11/11, 2019 at 21:20 Comment(0)
C
2

If you get phonelist from a hash (e.g. parsed JSON file), you may want to use fetch with [] as the default.

phonelist = my_data.fetch('phonelist', [])
Carbone answered 5/1, 2016 at 2:29 Comment(0)
N
1

Simply make sure empty phonelist is a [], instead of a nil value.

Alternatively, a nil value is falsey in Ruby, so you can use nil-punning

if phonelist
   phonelist.each do |phone|
     ...
Nuthatch answered 16/3, 2018 at 16:46 Comment(0)
T
0

what i've seen done a lot is:

@myvar.phonelist.each do |phone|
  # ...
end unless @myvar.phonelist.nil?
Tyrelltyrian answered 20/1, 2023 at 19:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.