16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Railsの設定ファイルはYamlよりRubyの方が良いと思う

Posted at

Railsアプリケーションで設定値をまとめるためのgemとして次のようなものがよく使われています。

上記3つのgemは細かい違いはあれど、基本的に Yaml ファイルに設定値を記述しておけば Ruby のコード内でグローバル定数のように扱えるというものです。
これらの gem はよく出来ていて、私も何度か使ってきたのですが、だんだんなぜYamlで頑張っているのだろうかという気持ちになりました。

例えば、 次の Yaml を見てください。

user:
  password_expiry_period: <%= 6.months %>

これは、ユーザーのパスワードの有効期限を設定する項目です。この値はコード中では次のようにして使います。

class User < ApplicationRecord
  def expired?
    # password_changed_at カラムに前回パスワードを変更した日時が記録してある
    password_changed_at < Time.current.ago(Settings.user.password_expiry_period)
  end
end

このコードでも特に不自由なく動くと思いますが、もし password_expiry_period の部分を設定値にせずにべた書きするとしたら次のように書くのが自然ではないでしょうか?

password_changed_at < 6.months.ago

そのため、私としては設定値を使う場合でも次のように書けると嬉しいです。

password_changed_at < Settings.user.password_expiry_period.ago

しかし、このような書き方はエラーになります。 Yaml の password_expiry_period: <%= 6.months %> の ERB 部分は先に評価されて Fixnum15552000 として扱われるため、 ActiveSupport::Duration ではないからです。

つまり、次のようになります。

> 6.months
6 months
> 6.months.class
=> ActiveSupport::Duration
> 6.months.to_i
=> 15552000
> Settings.user.password_expiry_period
=> 15552000
> Settings.user.password_expiry_period.class
=> Fixnum
> Settings.user.password_expiry_period.ago # 本当はこう書きたいがエラーになる
NoMethodError: undefined method `ago' for 15552000:Fixnum
> Time.current.ago(Settings.user.password_expiry_period) # 仕方なくこう書く
=> Mon, 04 Jul 2016 17:24:16 JST +09:00

そもそも設定ファイルでは ERB を使わずに Yaml の機能だけで書くべきという考えもあると思いますが、今回のように設定ファイル内でRubyの便利な機能を使いたいケースはありますし、どうせ設定ファイルの内容は Ruby のコード内で使うものなので、設定ファイルもRubyのコードとして定義した方が素直なのではないかと思います。

dry-configurable で設定ファイルを作成する

前置きが長くなりましたが、私の意見としては、 gem の内部設定を記述する時等によく見かける configure を使って Rails アプリケーションの設定も定義してしまおうということです。
このロジックは自前で作ってもいいですが、 dry-configurable という gem が便利なのでこれを使います。

dry-configurable の基本的な使い方はリンク先のドキュメントをご覧ください。ここでは Rails アプリケーションにおける設定ファイルとしての活用方法を説明します。

まず、 app/initializers/settings.rb を次の内容で作成します。

require_dependency Rails.root.join('config/settings/default.rb')
require_dependency Rails.root.join("config/settings/#{Rails.env}.rb")

config/settings 内に次のファイルを作成します。

config/settings
├── default.rb
├── development.rb
├── production.rb
└── test.rb
# config/settings/default.rb
class Settings
  extend Dry::Configurable

  setting :user do
    setting :password_expiry_period, 6.months
  end
end

# config/settings/development.rb
Settings.configure do |config|
  # 環境毎に値を変えたい場合は次のように定義します
  # config.user.password_expiry_period = 6.months
end

これを用いると、 User モデルのコードは次のようになります。

class User < ApplicationRecord
  def expired?
    # password_changed_at カラムに前回パスワードを変更した日時が記録してある
    password_changed_at < Settings.config.user.password_expiry_period.ago
  end
end

考えていたことができるようになりました! :tada:

今回作成した Settings クラスはご覧の通りただの Ruby のクラスなので、 Yaml + ERB では定義できなかった設定も定義できるようになります。
例えば、 lambda です。

user:
  after_signed_in_logic: <%= -> { redirect_to root_path } %>
# これは動かない
Settings.user.after_signed_in_logic.call
class Settings
  extend Dry::Configurable

  setting :user do
    setting :after_signed_in_logic, -> { redirect_to root_path }
  end
end

# 動く!
Settings.config.user.after_signed_in_logic.call

(サインイン後の処理をこのように記述するのは適切ではないかもしれませんが) Yaml + ERB よりも Ruby の方が表現力が高いことは明らかです。

Yamlの表現力に不満を覚えている方は、以上の方法をお試しください。

16
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?