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
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:
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).
In Ruby, nil is a special value that denotes the absence of any value. Nil is an object of NilClass.
By default methods in Ruby are empty, and an empty method returns, by default, nil.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With