🎃
YAMLを読み込んでプロパティを生成しているRubyクラスにSorbetで型を当てる方法
背景
設定ファイルなどを書いたYAMLファイルを、動的に読み込んでRubyのクラスを生成することが稀にあります。
例えば、以下のようなconfig.ymlがあるとして、Configクラスを生成しているとします。
# config.yml
domain: example.com
api:
endpoint: https://api.example.com
batch:
interval: 60
使う側のコードは以下のようなイメージです。
Config.api.endpoint # => "https://api.example.com"
Rubyの型チェッカーであるSorbetでは、静的に型をチェックする際に、プロパティなどにも型が定義されている必要があります。
RubyのコードにはConfigクラスのプロパティ情報はないため、Sorbetでは型エラーになってしまいます。
解決手順
1. RBIファイルを作成し、プロパティを定義する
まずは、YAMLファイルの構造をRBIファイルとしてクラスのプロパティをメソッドで定義してみます。
# typed: true
class Config
class << self
extend T::Sig
def domain; end
def api; end
def batch; end
end
end
これでプロパティの存在は定義することができました。
2. 値がネストしているところは、Typed Structsを使う
値がネストしているところは、XxxTypesのような型解決のためだけのジュールを定義してまとめてみます。
その中ではプロパティの型を定義できるTyped Structsを使います。
module ConfigTypes
class Api < T::Struct
prop :endpoint, String
end
class Batch < T::Struct
prop :interval, Integer
end
end
3. Configクラスのメソッドにsigを追加して型をつなげる
Configクラスのメソッドにsigで型を当てます。
# typed: true
module ConfigTypes
class Api < T::Struct
prop :endpoint, String
end
class Batch < T::Struct
prop :interval, Integer
end
end
class Config
class << self
extend T::Sig
sig { returns(String) }
def domain; end
sig { returns(ConfigTypes::Api) }
def api; end
sig { returns(ConfigTypes::Batch) }
def batch; end
end
end
これにより、使う側では型が当たるようになります。
Discussion