67

I have a string in Ruby on which I'm calling the strip method to remove the leading and trailing whitespace. e.g.

s = "12345 "
s.strip

However if the string is empty nil I get the following error.

NoMethodError: undefined method `strip' for nil:NilClass

I'm using Ruby 1.9 so whats the easiest way to check if the value is nil before calling the strip method?

Update:

I tried this on an element in an array but got the same problem:

data[2][1][6].nil? ? data[2][1][6] : data[2][1][6].split(":")[1].strip
user513951
  • 12,445
  • 7
  • 65
  • 82
user1513388
  • 7,165
  • 14
  • 69
  • 111
  • 1
    this has been asked in one way or another dozens of times: http://stackoverflow.com/questions/5429790/is-there-a-clean-way-to-avoid-calling-a-method-on-nil-in-a-nested-params-hash – tokland Sep 14 '12 at 13:36
  • 2
    note to original poster: the String is not empty as you claim, which would be `s = ""`, it is nil. It doesn't yet exist. If it was empty you could check with: `s.strip unless s.empty?` – three Sep 14 '12 at 14:08
  • 1
    If the string is empty, you wouldn't get that error. – sawa Sep 14 '12 at 15:33
  • For your second question, add [`dig`](https://stackoverflow.com/a/34623680/513951): `data.dig(2, 1, 6)&.split(":")[1]&.strip`. If any part of the data is missing or ill-formed, the whole expression returns `nil` with no error. – user513951 Aug 06 '20 at 21:16

10 Answers10

129

Ruby 2.3.0 added a safe navigation operator (&.) that checks for nil before calling a method.

s&.strip

If s is nil, this expressions returns nil instead of raising NoMethodError.

user513951
  • 12,445
  • 7
  • 65
  • 82
  • 1
    Yes - this is the only way to make Ruby act somewhat like a proper web-scripting language. If only there were a way to set this globally for all cases where this annoying and useless behavior appears - and to "implicitly convert" all values to strings by default, where needed (like puts statements). – JosephK Jun 15 '17 at 08:26
  • 2
    First time I've seen this- super useful – Yarin Nov 16 '17 at 14:28
  • 1
    @JosephK: Have you used Ruby? `IO#puts` indeed calls `to_s` on all of its arguments, eg. `puts(5)`. Also, try `class NilClass; def method_missing(*a);nil;end;end` – sondra.kinsey May 19 '18 at 13:07
  • 1
    I cannot count the number of times I have gotten failures on summing elements, puts, etc due to Ruby not behaving like Ecmascript would behave - make the sensible choice - given there are rarely cases where "nil" should not be treated as "" or zero (depending on context). – JosephK Dec 10 '18 at 07:42
  • 1
    @JosephK `nil` (or `null`) has been called, among other things, [the worst mistake of computer science](https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/). – user513951 Dec 13 '18 at 04:00
17

You can use method try from ActiveSupport (Rails library)

gem install activesupport

require 'active_support/core_ext/object/try'
s.try(:strip)

or you can use my gem tryit which gives extra facilities:

gem install tryit

s.try { strip }
megas
  • 21,401
  • 12
  • 79
  • 130
11

If you don't mind the extra object being created, either of these work:

"#{s}".strip
s.to_s.strip

Without extra object:

s && s.strip
s.strip if s
Michael Kohl
  • 66,324
  • 14
  • 138
  • 158
9

I guess the easiest method would be the following:

s.strip if s
lukad
  • 17,287
  • 3
  • 36
  • 53
  • 3
    Not really convenient if you try to chain things. – Victor Moroz Sep 14 '12 at 14:15
  • What is needed is a way to universally say, "If I call a method on nil, just return nil" so we don't have to clutter up our code with this stuff. IOW, we need "scripting language" functionality. Maybe throw something into the console at dev-level, just to help troubleshoot if/when you get nil unexpectedly. – JosephK Jul 01 '16 at 16:48
  • 1
    @JosephK Check out Jessie Sielaff's answer. What you're looking for was added in Ruby 2.3. It's called the _safe navigation operator_. – lukad Sep 09 '16 at 12:11
  • @lukad - Thanks. Now if there was just a way to make this the default behavior. – JosephK Sep 26 '16 at 08:24
3

I'd opt for a solution where s can never be nil to start with.

You can use the || operator to pass a default value if some_method returns a falsy value:

s = some_method || '' # default to an empty string on falsy return value
s.strip

Or if s is already assigned you can use ||= which does the same thing:

s ||= '' # set s to an empty string if s is falsy
s.strip

Providing default scenario's for the absence of a parameters or variables is a good way to keep your code clean, because you don't have to mix logic with variable checking.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
2

Method which works for me (I know, I should never pollute pristine Object space, but it's so convenient that I will take a risk):

class Object
  def unless_nil(default = nil, &block)
    nil? ? default : block[self]
  end
end

p "123".unless_nil(&:length) #=> 3
p nil.unless_nil("-", &:length) #=> "-"

In your particular case it could be:

data[2][1][6].unless_nil { |x| x.split(":")[1].unless_nil(&:strip) }

Victor Moroz
  • 9,167
  • 1
  • 19
  • 23
2

ActiveSupport comes with a method for that : try. For example, an_object.try :strip will return nil if an_object is nil, but will proceed otherwise. The syntax is the same as send. Cf active_support_core_extensions.html#try.

ksol
  • 11,835
  • 5
  • 37
  • 64
2

If you want to avoid the error that appears in the question:

s.to_s.strip
sawa
  • 165,429
  • 45
  • 277
  • 381
1

To complete the options shown here, there is the "Existence Check Shorthand", recommended in the Ruby Style Guide:

Use &&= to preprocess variables that may or may not exist. Using &&= will change the value only if it exists [means, is not nil], removing the need to check its existence with if.

So in your case you would do:

s = "12345 "
s &&= s.strip
tanius
  • 14,003
  • 3
  • 51
  • 63
0

Simply put:

s = s.nil? ? s : s.strip

Tl;dr Check if s is nil, then return s, otherwise, strip it.

Eugene
  • 4,829
  • 1
  • 24
  • 49
  • @lukad has a simpler answer than this even though. – Eugene Sep 14 '12 at 13:26
  • Thanks - Just tried this on an element within an array combined with split. It still seems to fail though: . puts data[2][1][6].nil? ? data[2][1][6] : data[2][1][6].split(":")[1].strip . NoMethodError: undefined method `strip' for nil:NilClass – user1513388 Sep 14 '12 at 13:41
  • What does `data[2][1][6].split(":")` return? Seems like it has only one item. – lukad Sep 14 '12 at 13:52