Today I learned that Sorbet interfaces do some magic to override how Ruby module and method overloading usually works. This is great, lets take a look!
We had a base class lets call it SomeBaseClass
and it implemented a method like api_options
. So we had the following and OurClass#api_options
was 'correctly' using the implementation from SomeBaseClass
class OurClass < SomeBaseClass
include ApiInterface
end
But what was interesting is ApiInterface
was defined to need aapi_options
method, which looks like this
sig { abstract.returns(ApiOptions) }
def api_options; end
Without all the Sorbet stuff in the sig
that's an empty method definition which would just return nil
everytime. Without Sorbet we'd expect OurClass#api_options
to return nil since this version would be the last one defined.
However, Sorbet is doing the 'right' thing and calling the actual implementation from our base class! It kind of has to, or else it's interfaces would be hard to work with. But its good to confirm, and write down for next time I run into this!
I dug a bit and found some code to replace a method and some to call the original implementation, so I assume some combination of these is how Sorbet pulls off this trick. Maybe I'll dig deeper and turn this into a full post in the future.