The Null Object Pattern — The Ruby way

Merdan Durdiyev
kode-art
Published in
3 min readMay 25, 2024

Intro

You might have come across similar errors as shown in the article image above. Something like: “undefined method ‘username’ for nil:NilClass”.

All it says is that you can’t apply the method ‘username’ to the object that is missing. That means either you misspelled the method name or the object you are looking for (with the given ID) was deleted from the database. So, this is the exact place where you can use the Null object pattern and avoid such horror screens.

The Null object pattern is a behavioral design pattern that helps eliminate the need for null checks in your code. It is an extremely helpful tool that can be used to minimize the harm of Nil exceptions and make your code more declarative and DRY.

In short, it is a great hack that can save you from endlessly calling ‘present?’ or ‘try?’ on ActiveRecord objects.

The Null object can represent the actual object in the case of its absence so that the consuming code does not need to handle the absence of the actual object explicitly and perform null checks.

So, let’s get started …

Implementing the Null Object Pattern in Ruby

A simple, yet informative way to introduce this pattern to you would be to consider a general scenario. This would be a scenario where we are printing out a user’s username, email, or first name. The user with the provided id has already been deleted from the database.

# Case 1
@user_1 = User.find_by(email: "deleted.user@mymail.com")
@user_1.first_name

# Case 2
@user_2 = User.find(34)
@user_2.username

And in these situations, all you get is this error:

# Case 1
undefined method 'first_name' for nil:NilClass

# Case 2
undefined method 'username' for nil:NilClass

Of course, this is not the end of the world. You have a couple of ways to avoid getting errors. And here they are:

# Solution 1 / using 'try' method.
@user.try(:first_name)


# Solution 2 / using a conditional operator.
if @user.exists?
@user.first_name
end

These kinds of checks can quickly turn your art code into a clutter of checks and conditional operators. There is a better way that lets us do it once and use it everywhere, which makes the code an elegant one in contrast to what we had.

Let’s define a ‘DeletedUser’ class that will substitute any user object that is missing in the database. This class will have the same common attributes/methods that will let us imitate the actual User object.

class DeletedUser
def first_name
"Unknown first name"
end

def last_name
"Unknown last name"
end

def username
"Unknown username"
end

def email
"already.deleted.user@mail.com"
end
end

Earlier in the code, remember that we did not get what we expected from the user, the user did not respect our request and now we found a way to tame it. If the user is not found, we initialize the ‘@user_1' and ‘@user_2’ variables as an instance of our new class, the ‘DeletedUser’ object.

# Case 1
@user_1 = User.find_by(email: "deleted@user.com") || DeletedUser.new
@user_1.first_name #=> "Unknown first name"
@user_1.email #=> "already.deleted.user@mail.com"

# Case 2
@user_2 = User.find(34) || DeletedUser.new
@user_2.username #=> "Unknown username"

This way we can be sure that the object will always respond to the methods we expect it to respond to.

Pros and Cons

The Null object pattern is particularly useful in the following scenario:

  • Default values: When you want to provide default values or behavior for missing objects.

Be careful, because sometimes this pattern is:

  • Deceitful: It can make errors/bugs appear as normal program execution.

Conclusion

The Null object pattern is a powerful tool for handling missing objects in a clean and maintainable way. By creating null objects that imitate the behavior of real objects, you simplify your code and make it easier for testing. You can consider this pattern when you need to gracefully handle the absence of required objects.

The Null object pattern is useful beyond use cases where queries to a database don’t return data.

So, this is it for the Null Object Pattern. Use it somewhere to make your code prettier and more meaningful.

Stay safe, stay hungry!

Photo by Nick Fewings on Unsplash

--

--