Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally execute block in Ruby if value is not nil? (aka Smalltalk's ifNotNilDo:)

In Smalltalk there is the method ifNotNilDo: It is used like this:

database getUser ifNotNilDo: [:user | Mail sendTo: user ]

On objects that are not nil the block is executed, passing the object itself as a parameter. The implementation in class UndefinedObject (Smalltalk's equivalent of Ruby's NilClass) simply does nothing. That way, if getting the user resulted in a nil object, nothing would happen.

I am not aware of something similar for Ruby, so I rolled out my own solution. It goes like this:

class Object
  def not_nil
    yield(self)
  end
end

class NilClass
  def not_nil
    # do nothing
  end
end

It could be used like this:

users = {:peter => "[email protected]", :roger => "[email protected]" }
users[:roger].not_nil {|u| Mail.send(u) }

This saves us from accessing the hash twice

Mail.send(users[:roger]) if users[:roger]

... or using a temp-variable:

u = users[:roger]
Mail.send(u) if u

Update:

People are starting to suggest solutions based on hash-operations, and also accessing the hash twice. My question is not directly hash-related.

Imagine instead that the first operation is not a hash-access and also expensive. Like:

RemoteUserRepo.find_user(:roger).not_nil {|u| Mail.send(u) }

(end-of-update)

My questions are:

  • Am I wrong to re-invent this idiom?
  • Is there something like this (or better) supported in Ruby out-of-the-box?
  • Or is there another, shorter, more elegant way to do it?
like image 863
Sebastian N. Avatar asked Oct 26 '14 20:10

Sebastian N.


People also ask

How do you check if a variable is nil in Ruby?

That's the easy part. In Ruby, you can check if an object is nil, just by calling the nil? on the object... even if the object is nil. That's quite logical if you think about it :) Side note : in Ruby, by convention, every method that ends with a question mark is designed to return a boolean (true or false).

What is nil in Ruby?

In Ruby, nil is a special value that denotes the absence of any value. Nil is an object of NilClass.

Does Ruby return nil by default?

By default methods in Ruby are empty, and an empty method returns, by default, nil.


1 Answers

In ActiveSupport there is try method. https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb

data = { a: [1,2,3] }
data[:a].try(:first)
#=> 1
data[:b].try(:first)
#=> nil
data[:b].first
#=> Exception

Under the hood it is implemented close to yours solution. For any object but nil it will "send a message" (in terms of Smalltalk) with attributes.

# object.rb
def try(*a, &b)
  if a.empty? && block_given?
    yield self
  else
    public_send(*a, &b) if respond_to?(a.first)
  end
end

# nilclass
def try(*args)
  nil
end

About your questions

Am I wrong to re-invent this idiom?

Rails guys have made something similar

Is there something like this (or better) supported in Ruby out-of-the-box?

No, Ruby doesn't support it out-of-the-box

Or is there another, shorter, more elegant way to do it?

In my opinion it has a problem: programmer should control data. One should know what kind of data he has and handle each type and each case, or raise an error. In your case it is valid for all data types but NilClass. What can lead to bugs that will very hard to debug.

I prefer to use old-fashioned

Mail.send(users[:roger]) if users[:roger]
# or
users[:roger] && Mail.send(users[:roger])
# or use caching if needed
like image 84
fl00r Avatar answered Oct 01 '22 02:10

fl00r