Today I was working on a project where there was a class that looked (roughly) like this:
class LazyColor
def initialize(real_color_id, name)
@real_color_id = real_color_id
@name = name
end def method_missing(method, *args, &block)
if real_color.respond_to?(method)
real_color.public_send(method, *args)
else
super
end
end attr_reader :real_color_id, :name private def real_color
@real_color ||= Color.find(real_color_id)
end
So, if we send a message to a LazyColor
instance and it can’t answer, it will try to use the real_color
to respond for it. Let’s see an example:
real_color = Color.create(name: 'red', intensity: 20)
lazy_color = LazyColor.new(real_color.id, name: real_color.name)puts lazy_color.intensity
=> 10
As you can see, even though lazy_color
doesn’t answer to intensity, we were able to get the value from the real_color
, because the method_missing implementation of LazyColor
delegated to that real_color
. Now, imagine a scenario that we don’t know if we are going to have a LazyColor
, or a nil
value, so we want to take advantage of try, like this:
real_color = Color.create(name: 'red', intensity: 10)
lazy_color = LazyColor.new(real_color.id, name: real_color.name)puts lazy_color.try(:intensity) || 20
=> 20
The reason this gives us 20 is because how try is implemented, it basically invokes the public method of that object itself, without ever going through method_missing
.
I found this problem interesting because I have seen many projects where the code has to contemplate nil
objects. Specifically in our case, we were doing try
just in case instead of a lazy_color
we had a nil
object. By doing that, we actually caused a regression on how lazy_color
handles messages that are not part of its public interface.