Ruby and Sorbet Module Loading Order

2023-06-14

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 a api_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.

coreyja weekly

My weekly newsletter tailored at developers who are eager to grow with me!
Every week will be unique, but expect topics focusing around Web Development and Rust