Ruby encourages to do duck typing. Everything is an object and can send more or less messages to each other. That is why type checking is more a code smell. Very often duck typing can solve type checking. The object just behaves like expected.
Nil is special
There is an exception. In some cases the object could be NIL and the logic has to deal with it.
A Ruby on Rails example is about people having a bank account. The models:
class Person < ApplicationRecord has_one :bank_account end class BankAccount < ApplicationRecord has_many :transactions def total transactions.sum :value end def in_dispo? total.negative? end end
The bank account can sum the total balance and check if the account is in dispo. Since not each person wants to have a for some reason, the presence of the account is not validated.
At first dealing with a person having a bank account is straight forward:
alice = Person.create name: 'Alice', bank_account: BankAccount.new # Alice makes some transactions alice.bank_account.transactions.create value: 100 alice.bank_account.transactions.create value: -4.5 alice.bank_account.total.to_s # => "95.5" alice.bank_account.in_dispo? # => false
But for all people having no bank account, it is getting quite awkward to check for the presence of the bank account each time it has to be accessed:
bob = Person.create name: 'Bob' if bob.bank_account.present? bob.bank_account.total.to_s else "0.0" end if bob.bank_account.present? bob.bank_account.in_dispo? else false end
This is also a kind of type check. It is a check for Nil object.
Sure, the Delegation pattern, allowing to return nil, sometimes solves the issue. But if certain return values are expected (like 0.0 for accumulations or true/false for checking), the Nil object pattern comes in handy.
Nil object pattern
The basic idea behind the Nil object pattern is to return a default object that behaves like the expected object (in this example the bank account).
Then there is just one single point, where to check for the Nil type and then the object can receive the expected messages.
The persons bank account is either the associated bank account or the Nil object:
class Person < ApplicationRecord has_one :bank_account def bank_account super || NilBankAccount.new end end
The class NilBankAccount:
class NilBankAccount def total 0.0 end def in_dispo? false end end
By means of the Nil object, awkward Nil checks are not necessary any longer:
bob.bank_account.total.to_s # => "0.0" bob.bank_account.in_dispo? # => false