RailsでSettingslogicから最小限の変更でconfig_forに移行する
背景
ハコベル株式会社システム開発部 一般貨物運送手配システムG の坂東です。普段はサーバーサイドエンジニアとして、ハコベル配車管理の開発を行っています。
ハコベル配車管理のバックエンド処理は、Ruby on Railsで構築されています。このRailsアプリケーションにおける諸々の設定管理には、従来はSettingslogicというgemを使用していました。
Settingslogicではアプリケーションの設定を簡単に取得し、利用することができます。
具体的には以下のようなyamlファイルを用意し、
# config/application.yml
defaults: &defaults
hoge: 1
foo:
bar: "sample"
次のようなSettingsクラスを用意して、設定ファイルを読み込みます。
# app/models/settings.rb
class Settings < Settingslogic
source Rails.root.join('config', 'application.yml')
namespace ::Rails.env
end
すると以下のようなメソッドチェーンで、ネストした設定値にアクセスすることができるようになります。
Settings.hoge #=> 1
Settings.foo.bar #=> "sample"
しかしRubyのバージョンアップに伴い、この方法ではエラーが出るようになりました。Ruby 3.1からはPsychというyamlを読み込むgemに破壊的変更があり、Settingslogicが正常に動作しないのです。
詳しくは、こちらの記事が参考になります。
Rails 6.1のままRuby 3.2にアップデートし、YJITの恩恵を受ける
なお、この記事では以下のバージョンを仮定します。
- Ruby: 3.2.2
- Rails: 6.1.7
課題
Settingslogicにパッチを当てて運用を続けることも考えましたが、最新コミットが執筆時点では9年前で使い続けるのに不安があったため、今回は別の方法を考えることにしました。
チームの状況を踏まえると、アプリケーション影響やQAの工数を最小限に抑えたかったので、従来の使用感を変えずに最小限の変更で移行できることを重視して対応しました。
選択肢としては以下の2つを考えました。
- Rails標準のconfig_forを使う
- その他のgemを使う
Settingslogicからその他のgemに移行するにしてもメンテナンス切れの問題はついて回るし、Rails標準の機能で実現できればベストと考えたため、前者の方法を採用しました。
しかし1つ問題がありました。素直にconfig_forを使用するとHash likeな使い方になってしまうため、従来のメソッドチェーンでの呼び方から書き換える必要があるのです。
例えば先ほどのyamlファイルをconfig_forで読み出すと、以下のように値を参照することになります。
config = Rails.application.config_for(Rails.root.join('config/application.yml'))
config.hoge #=> 1
config.foo[:bar] #=> "sample"
解決策
この問題を解決するため、Settingsクラスの実装を工夫しました。
以下が実装例です。
class Settings
def self.load_config
config ||= convert_to_ordered_options(Rails.application.config_for(Rails.root.join('config/application.yml')))
config.each_key do |key|
define_singleton_method(key) do
config[key]
end
end
end
def self.convert_to_ordered_options(hash)
ordered_options = ActiveSupport::OrderedOptions.new
hash.each do |key, value|
ordered_options[key] = if value.is_a?(Hash)
convert_to_ordered_options(value)
else
value
end
end
ordered_options
end
private_class_method :convert_to_ordered_options
private_class_method :load_config
load_config
end
この実装では、Settings
クラスに load_config
メソッドを導入し、設定の読み込みとメソッドの生成を行っています。
ActiveSupport::OrderedOptionsを使用すると、ハッシュの要素をメソッドで取得できるようになるのがポイントです。
convert_to_ordered_options
メソッドでは、Rails.application.config_for
でハッシュとして読み込まれたyamlファイルの内容を、再帰的に OrderedOptions
に変換します。
そしてconfigのkeyに対してdefine_singleton_method
を適用することで、Settingsクラスに対してconfigのkeyに対応したメソッドを生成できます。
このようにして、ネストした設定値をSettingsクラスのメソッドチェーンで呼び出すことが可能になります。
Settings.hoge #=> 1
Settings.foo.bar #=> "sample"
よって当初の目的通り、Settingslogicに近い使用感のまま、Settingsクラスを移行することができました!
まとめ
この記事では、Rails標準のconfig_forを使いつつ、メソッドチェーンで設定値を読み出すための実装の工夫についてご紹介しました。
もし既存のrailsアプリケーションでSettingslogicを使っていて、gemの移行を考えている方はぜひ参考にしてみてください。
参考文献
「物流の次を発明する」をミッションに物流のシェアリングプラットフォームを運営する、ハコベル株式会社 開発チームのテックブログです! 【エンジニア積極採用中】t.hacobell.com/blog/career
Discussion