YOSHINO日記

プログラミングに関すること

継承したクラスに共通の設定クラスを作成する

共通の設定ファイルをどこに書くべきか?

Configure services that reference other services · rails/activestorage@e550339 · GitHub

以下のようにserviceクラスを継承したクラスがある時を考えます。

  • gcs < service
  • s3 < service
  • mirror < service
  • disk < service

各クラスで設定ファイルを書くときには、

  1. serviceクラス内に書いてしまう
  2. 設定用に別クラスとして作成する

の2通りのことが考えられると思います。

上記で上げたコミットは、1=>2へのリファクタリングを行っています。

設定用に別クラスを定義したほうが美しいように思いました。

具体的な設定用のクラス

class ActiveStorage::Service::Configurator #:nodoc:
  def initialize(service_name, configurations)
    @service_name, @configurations = service_name.to_sym, configurations.symbolize_keys
  end

  def build
    service_class.build(service_config.except(:service), @configurations)
  end

  private
    def service_class
      resolve service_class_name
    end

    def service_class_name
      service_config.fetch :service do
        raise "Missing Active Storage `service: …` configuration for #{service_config.inspect}"
      end
    end

    def service_config
      @configurations.fetch @service_name do
        raise "Missing configuration for the #{@service_name.inspect} Active Storage service. Configurations available for #{@configurations.keys.inspect}"
      end
    end

    def resolve(service_class_name)
      require "active_storage/service/#{service_class_name.to_s.downcase}_service"
      ActiveStorage::Service.const_get(:"#{service_class_name}Service")
    end
end

初期化でservice_name(string), configurations(hash)からインスタンス変数を作成します。

ポイント(個人的に勉強になった点)

1.const_get を使って、Serviceを継承したクラスにアクセスする

const_getの使い方は以下の質問が参考になりました。 How does Ruby's Object#const_get actually work? - Stack Overflow

2.設定用のconfigrations(hash)が必要なkeyが設定されていなければerrorを返す

 @configurations.fetch @service_name do
    raise "Missing configuration for the #{@service_name.inspect} Active Storage service. Configurations available for #{@configurations.keys.inspect}"
  end

fetch (Hash) - Rubyリファレンス

fetchメソッドにブロックを渡すと、キーが存在しないときはブロックの戻り値を返します。ブロック引数kには引数keyで指定したキーが入ります。

ブロックの中でraiseしてあげれば、必要なキーが無い時にエラーを生じさせることができます。

その後大きくリファクタリングがあったので追記

このコミットでConfiguratorクラスが大きくリファクタリングされました。

Clarify how a service can build other composed services · rails/activestorage@4d292fc · GitHub

ポイントは、

class Yactivestorage::Service
  class Yactivestorage::IntegrityError < StandardError; end

  extend ActiveSupport::Autoload
  autoload :Configurator

  def self.configure(service_name, configurations)
    Configurator.build(service_name, configurations)
  end

  def self.build(configurator:, service: nil, **service_config) #:nodoc:
    new(**service_config)
  end

Serviceクラスの中にAutoloadを使って、Configuratorを呼び出していることです。

Serviceクラスのように自身のメソッドの中で、全てのメソッドがConfiguratorを使うわけではない場合、 呼び出される時にだけロードされるAutoloadを使うのは良いことです。