This post was originally published on my blog: How to Check if a Variable is Defined in Ruby
Ruby provides a handy defined?(expression)
keyword that tests if the expression
refers to anything recognizable. The expression
can be an object, a variable that's initialized, method name, etc. If Ruby can't resolve the expression, it returns nil
. Otherwise, it returns a string describing the expression.
Here are some examples of using defined?
with different types of expressions. Note that a variable set to nil
is still initialized and recognized by ruby.
RSpec.describe 'Defined' do
it 'tests if the local variable is defined' do
name = 'Akshay'
expect(defined? name).to eq('local-variable')
expect(defined? b).to eq(nil)
expect(defined? nil).to eq('nil')
expect(defined? String).to eq('constant')
expect(defined? 1).to eq('expression')
end
it 'ensures that a variable set to nil is still recognized' do
name = nil
expect(defined? name).to eq('local-variable')
end
end
Using with conditional assignment
Sometimes, you want to lazily evaluate some code, only once. That is, do nothing if a variable exists but initialize it if it doesn't. The idiomatic ruby approach is to use the ||=
operator.
def result
@result ||= calculate_result
end
def calculate_result
puts '>>> heavy calculation here.. should happen only once'
100
end
it 'lazy-evaluates the calculate_result operation once' do
expect(result).to eq(100)
expect(result).to eq(100)
end
# Output
>>> heavy calculation here.. should happen only once
Just make sure you don't use it with an operation that can return nil
or boolean value false
as the result. Otherwise, it will invoke the calculate_result
every time, eliminating the benefit of ||=
operator.
For example, if you change the calculate_result
method above to return false
(or nil
) instead of 100, ruby will call calculate_result
each time result
is called.
def calculate_result
puts '>>> heavy calculation here.. should happen only once'
false # or nil
end
# Output
>>> heavy calculation here.. should happen only once
>>> heavy calculation here.. should happen only once
The defined?
method comes in handy in such cases. Change the result
method so it first checks if the @result
variable is defined.
def result
return @result if defined? @result
@result = calculate_result
end
Don't use defined?
to check hash keys
A common mistake is to use defined?
to verify if a hash key is defined. For example,
def check_hash_key
hash = {}
defined?(hash['key']) ? 'unexpected!' : 'not defined'
end
it 'does not return false for non-existing hash key' do
expect(check_hash_key).to eq('unexpected!')
end
This is because it returns the string method
, which ruby evaluates to true
in a boolean expression.
it 'returns method for non-existing hash key' do
data = {}
expect(defined? data['key']).to eq('method')
end
The idiomatic ruby solution to check if a key exists in a hash is to use any of the following methods: has_key?
, key?
, include?
, or member?
data = {}
expect(data.key?('key')).to eq(false)
Use parenthesis when using defined?
You don't always need to use them, but it's highly recommended due to the low precedence of defined?
keyword. For example, if you want to check if a variable exists and it's greater than zero, you might write something like this.
it 'has low precedence' do
result = 10
expect(defined? result && result > 0).to eq(true)
end
# Fails!
# expected: true
# got: "expression"
Adding parentheses results in a better check, and it's also very clear. The following test passes with flying colors.
it 'has low precedence' do
result = 10
expect(defined? result && result > 0).to eq('expression')
expect(defined?(result) && result > 0).to eq(true)
end
Top comments (0)