こちらは「ええな〜コード」の記念すべき第一本目の記事です。「ええな〜コード」とはOSSのコードから勉強・参考になる箇所を抜粋して、記事として紹介するという趣旨の不定期シリーズです。
今回はよくある設定値(configure)について「ええな〜」と思うコードを見つけたので紹介していこうと思います。
OSSの紹介
今回、コードを読んでいくのはこちらのOSSです。
stitches
読み方は「スティッチ」で良いでしょうか。ちなみに、あの青色のエイリアンとは全く関係がありません。stitches
はWEBフレークワークでRails
ライクの書き方でシンプルなマイクロサービスAPIを作成することが出来ます。7年前から開発されており、かなり御長寿なOSSとなっています。stitches
では設置値をブロック引数を経由して設定することが出来ます。
GitHub - stitchfix/stitches: Create a Microservice in Rails with minimal ceremony
Stitches.configure do |config| config.max_cache_ttl = 5 # seconds config.max_cache_size = 100 # how many keys to cache end
どのようにして設置値を記録しているか
さて、ここからが本題です。実際にコードを見ていきます。まずはlib直下のstitches.rb
から。ここでは関連ファイルを読み込んでいるだけでした。
stitches/railtie.rb at main · stitchfix/stitches · GitHub
require 'stitches_norailtie' require 'stitches/railtie'
※ちなみにrailtie
(れいるてぃず)はrails
のコアライブラリの名称だそうです。
先にネタバレになってしまいますが、設定値の記録はstitches_norailtie.rb
によって行われています。長いので部分的に省略しますが、重要なのはmodule Stiches
と定義された関数です。
stitches/stitches_norailtie.rb at main · stitchfix/stitches · GitHub
module Stitches def self.configure(&block) block.(configuration) end def self.configuration @configuration ||= Configuration.new end end require 'stitches/configuration' # : # : require 'stitches/valid_mime_type'
Stitches
モジュールに定義されたconfigure
関数の呼び出し方は先ほど、記載したサンプルコードの通りです。
Stitches.configure do |config| config.max_cache_ttl = 5 # seconds config.max_cache_size = 100 # how many keys to cache end
configure
関数が呼び出されることで、ブロック実行の引数にconfiguration
関数の実行結果が受け渡されます。つまりdo |config|
のconfig
に渡されるのは@configuration
です。初期値が||=
演算子によって束縛されるのでConfiguration
クラスのインスタンスということになります。
ここで重要なのはConfiguration
クラスのインスタンスが@configuration
に記録されたという点です。ここだけ覚えておいてください。
moduleにself.関数名を定義すると何が起きるか
先ほどのコードを見てみるとmodule Stitches
の内部でself.configure
と何やら見慣れない定義の仕方をしています。
module Stitches def self.configure(&block) block.(configuration) end end
こうするとシングルトンクラスのようにモジュールを使うことが出来ます。以下のサンプルをご覧ください。
module Sample def self.set_name(name) @name = name end def self.puts_name puts "登録された名前: #{@name}さん" end end Sample.set_name('OKB') Sample.puts_name()
実行結果
登録された名前: OKBさん
インスタンスを作った訳ではないのに@name
にOKBが保持されたままになっており、puts
で出力が出来てしまいました。stitches
にもこの仕組みが使われています。よくあるテクニックですが使い方を間違えると、とんでもないことを引き起こす諸刃の剣なので、使い方には注意が必要です。
Configurationクラスの処理
@configuration
に記録されたConfiguration
クラスのインスタンスは設定値を記録するための責務を担っています。自分だったら先ほどのStitches
モジュールにそのまま、それぞれの設定値に対応したインスタンス変数を定義してしまうでしょう。。。
stitches/configuration.rb at main · stitchfix/stitches · GitHub
# 長くなるため部分的に割愛 class Stitches::Configuration def initialize reset_to_defaults! end # Mainly for testing, this resets all configuration to the default value def reset_to_defaults! @allowlist_regexp = nil @custom_http_auth_scheme = UnsetString.new("custom_http_auth_scheme") @env_var_to_hold_api_client_primary_key = NonNullString.new("env_var_to_hold_api_client_primary_key","STITCHES_API_CLIENT_ID") @env_var_to_hold_api_client= NonNullString.new("env_var_to_hold_api_client","STITCHES_API_CLIENT") @max_cache_ttl = NonNullInteger.new("max_cache_ttl", 0) @max_cache_size = NonNullInteger.new("max_cache_size", 0) @disabled_key_leniency_in_seconds = ActiveSupport::Duration.days(3) @disabled_key_leniency_error_log_threshold_in_seconds = ActiveSupport::Duration.days(2) end # : # : end
すでにお気づきの方もいるでしょうが、このクラスでインスタンス変数として定義されている値が先程のconfigure
関数で設定できる項目名に対応しています。それぞれの項目の初期値をインスタンス作成時に記録後、ブロック引数経由でユーザーが必要な項目だけ上書き可能な作りになっています。それぞれの項目にゲッター・セッターが定義されているので、config.max_cache_ttl = 5
のような記述が出来るわけですね。
# getter def max_cache_ttl @max_cache_ttl.to_i end # setter def max_cache_ttl=(new_max_cache_ttl) @max_cache_ttl = NonNullInteger.new("max_cache_ttl", new_max_cache_ttl) end
ここで面白いのが、記録する値の制約、バリデーションをそれぞれクラスとして表現している点です。3つのクラスが独自に定義されています。
- NonNullInteger
- NonNullString
- UnsetString
別にセッターでnil
の場合にraise
させたりすれば良いのですが、このようにクラスとして定義しておくことで、判定の共通化、メソッドの定義といった利点が生まれます。ええな〜。
# private化して外部に公開しないというのもええな〜 private class NonNullInteger def initialize(name, value) # バリデーション処理 unless value.is_a?(Integer) raise "#{name} must be an Integer, not a #{value.class}" end @value = value end def to_i @value end alias to_integer to_i end
こうしてブロック引数で渡された処理が、クラスのインスタンス変数に記録されているわけですね。非常に洗礼された見事なコードでした。
まとめ
今回、紹介したStitches
での設定値(configure)の作り方のまとめです。
- 設定値はブロック引数を経由して受け取る
- モジュールに
self.関数名
を定義することで設定値を記録するシングルトンクラスを作成している - 設定値の項目一覧と記録は別クラス(Configuration)へ責務を分割している
- 設定値はそれぞれセッターを使用して上書きが可能
- 設定値の制約、バリデーションは独自のクラスを定義することで表現している
少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。