How to Check if a Variable is Defined in Ruby
Tags: ruby
Published: 2022-08-10 03:20:00

_This post was originally published on my blog: [How to Check if a Variable is Defined in Ruby
](https://www.akshaykhot.com/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. 

```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
```

<!--more-->

### 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. 

```ruby
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. 

```ruby
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. 

```ruby
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, 

```ruby
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. 

```ruby
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?`

```ruby
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.

```ruby
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. 

```ruby
it 'has low precedence' do
	result = 10
	expect(defined? result && result > 0).to eq('expression')
	expect(defined?(result) && result > 0).to eq(true)
end
```