🎃

YAMLを読み込んでプロパティを生成しているRubyクラスにSorbetで型を当てる方法

2024/11/29に公開

背景

設定ファイルなどを書いた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