Rails.app.credsの5つの意外な使い方
Rails 8.2は、ENVと暗号化ファイルからクレデンシャルにアクセスする統一された方法としてRails.app.credsを導入しました。しかし、実装の中にはもっと強力なものが隠されています:CombinedConfigurationは設定のための責任連鎖パターンです。各バックエンドは値を返すか、次に渡します。
これは単なるクレデンシャル検索ではありません。コンポーザブルな設定ミドルウェアです。
このアーキテクチャを活用する5つのパターンを紹介します。
1. サービス不要のフィーチャーフラグ
ほとんどのアプリはLaunchDarklyを必要としません。必要なのは、バージョン管理され、開発環境で上書き可能な機能トグルの方法です。
# config/credentials.yml.enc
features:
new_checkout: false
dark_mode: true
ai_summaries: false
他のクレデンシャルと同様にアクセスします:
if Rails.app.creds.option(:features, :new_checkout, default: false)
render_new_checkout
else
render_legacy_checkout
end
魔法:ENVがクレデンシャルを上書きします。暗号化ファイルを触らずにローカルで機能を有効にできます:
FEATURES__NEW_CHECKOUT=true bin/rails server
ステージングで機能をテストする必要がありますか?デプロイ設定でENV変数を設定するだけです。コード変更なし、クレデンシャル編集なし、デプロイなし。
使いやすくする
ヘルパーでラップします:
# app/helpers/feature_flags_helper.rb
module FeatureFlagsHelper
def feature?(name)
Rails.app.creds.option(:features, name, default: false).to_s == "true"
end
end
# 使用方法
<% if feature?(:new_checkout) %>
<%= render "checkout/new" %>
<% end %>
これでフィーチャーフラグは:
- バージョン管理されている(credentialsで)
- 環境ごとに上書き可能(ENV経由)
- 外部依存なし
- 無料
2. 開発者オーバーライドファイル
すべてのチームがこの問題を抱えています:「コミットせずにローカルで異なるクレデンシャルでテストするにはどうすればいい?」
gitignoreされたローカルオーバーライドファイルを作成します:
# config/initializers/developer_overrides.rb
class DeveloperOverrides
def initialize
path = Rails.root.join(".secrets.local.yml")
@config = path.exist? ? YAML.load_file(path).deep_symbolize_keys : {}
end
def require(*keys)
option(*keys) || raise(KeyError, "見つかりません: #{keys.join('.')}")
end
def option(*keys, default: nil)
value = keys.reduce(@config) { |h, k| h.is_a?(Hash) ? h[k] : nil }
value.nil? ? default : value
end
def keys
flatten_keys(@config)
end
def reload
@config = YAML.load_file(Rails.root.join(".secrets.local.yml")).deep_symbolize_keys
rescue Errno::ENOENT
@config = {}
end
private
def flatten_keys(hash, prefix = [])
hash.flat_map do |k, v|
v.is_a?(Hash) ? flatten_keys(v, prefix + [k]) : [prefix + [k]]
end
end
end
# 最高優先度で挿入
if Rails.env.development?
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
DeveloperOverrides.new,
Rails.app.envs,
Rails.app.credentials
)
end
.gitignoreに追加:
.secrets.local.yml
これで開発者は自分のオーバーライドファイルを作成できます:
# .secrets.local.yml(gitignoreされている)
stripe:
secret_key: sk_test_my_personal_test_key
openai:
api_key: sk-my-own-openai-key
features:
experimental_ui: true
「うっかりAPIキーをコミットしてしまった」インシデントはもうありません。「一時的にcredentials.yml.encを編集させて」もありません。各開発者が自分のサンドボックスを持ちます。
3. リクエストスコープ設定
管理者がリクエストごとに設定を上書きできたらどうでしょう?デバッグ、カスタマーサポート、A/Bテストに便利です。
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :config_overrides
end
# lib/request_scoped_configuration.rb
class RequestScopedConfiguration
def require(*keys)
option(*keys) # 見つからない場合はnilを返してチェーンを続行
end
def option(*keys, default: nil)
Current.config_overrides&.dig(*keys)
end
def keys
Current.config_overrides&.keys || []
end
def reload
# No-op、リクエストごとにクリア
end
end
接続します:
# config/initializers/credentials.rb
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
RequestScopedConfiguration.new,
Rails.app.envs,
Rails.app.credentials
)
コントローラーで:
class Admin::DebugController < ApplicationController
before_action :require_admin
def impersonate_config
# この管理セッションで一時的にStripeテストモードを使用
Current.config_overrides = {
stripe: { test_mode: true },
features: { debug_panel: true }
}
redirect_back fallback_location: root_path, notice: "デバッグモードが有効になりました"
end
end
アプリの残りの部分は知りもせず、気にもしません。Rails.app.creds.option(:stripe, :test_mode)を呼び出すだけで、現在のリクエストに適した値を取得できます。
4. 監査ログラッパー
コンプライアンス要件では、機密クレデンシャルへのアクセスのログ記録が求められることがよくあります。CombinedConfigurationを使えば、任意のバックエンドをログでラップできます:
# lib/audited_configuration.rb
class AuditedConfiguration
def initialize(backend, logger: Rails.logger)
@backend = backend
@logger = logger
end
def require(*keys)
log_access("require", keys)
@backend.require(*keys)
end
def option(*keys, default: nil)
log_access("option", keys)
@backend.option(*keys, default: default)
end
def keys
@backend.keys
end
def reload
@backend.reload
end
private
def log_access(method, keys)
location = caller_locations(2, 1).first
@logger.info({
event: "credential_access",
method: method,
keys: keys.join("."),
file: location.path,
line: location.lineno,
timestamp: Time.current.iso8601
}.to_json)
end
end
本番クレデンシャルをラップします:
# config/initializers/credentials.rb
if Rails.env.production?
audited_creds = AuditedConfiguration.new(Rails.app.credentials)
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
Rails.app.envs,
audited_creds
)
end
すべてのクレデンシャルアクセスがログに記録されます:
{"event":"credential_access","method":"require","keys":"stripe.secret_key","file":"app/services/payment_service.rb","line":42,"timestamp":"2026-01-05T10:30:00Z"}
これをSIEMに送信したり、異常なアクセスパターンのアラートを設定したり、アクセス制御の証拠を求める監査人を満足させることができます。
5. スケジュール設定
設定が時間に基づいて変更できたらどうでしょう?ブラックフライデーの価格。ホリデーテーマ。計画されたメンテナンスウィンドウ。
# lib/scheduled_configuration.rb
class ScheduledConfiguration
Schedule = Data.define(:range, :config)
SCHEDULES = [
Schedule.new(
range: Time.zone.parse("2026-11-27")..Time.zone.parse("2026-12-01"),
config: {
pricing: { discount_percent: 30 },
features: { sale_banner: true },
cache: { ttl: 60 } # 高トラフィック時は短いキャッシュ
}
),
Schedule.new(
range: Time.zone.parse("2026-12-24")..Time.zone.parse("2026-12-26"),
config: {
features: { holiday_theme: true },
support: { auto_reply: true }
}
)
]
def require(*keys)
option(*keys) || raise(KeyError, "見つかりません: #{keys.join('.')}")
end
def option(*keys, default: nil)
active = SCHEDULES.find { |s| s.range.cover?(Time.current) }
return default unless active
keys.reduce(active.config) { |h, k| h.is_a?(Hash) ? h[k] : nil }
end
def keys
SCHEDULES.flat_map { |s| s.config.keys }.uniq
end
def reload
# データベースや外部ソースからリロードも可能
end
end
チェーンに接続します:
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
ScheduledConfiguration.new, # 時間ベースのオーバーライドを最初に
Rails.app.envs,
Rails.app.credentials
)
ブラックフライデーには、Rails.app.creds.option(:pricing, :discount_percent)が自動的に30を返します。デプロイ不要。スケジュールが終了すると、デフォルトにフォールバックします。
価格設定コードはブラックフライデーを知る必要がありません:
def apply_discount(price)
discount = Rails.app.creds.option(:pricing, :discount_percent, default: 0)
price * (1 - discount / 100.0)
end
パターン
5つの例すべてが同じ洞察を共有しています:CombinedConfigurationは設定のためのミドルウェアです。責任連鎖パターンにより:
- 既存のコードを変更せずに振る舞いをレイヤー化
- バックエンドの順序付けで機能を合成
- ストレージ、アクセス制御、ビジネスロジック間で関心を分離
順序が重要です:
ActiveSupport::CombinedConfiguration.new(
RequestScopedConfiguration.new, # 最高優先度:リクエストごとのオーバーライド
ScheduledConfiguration.new, # 時間ベースのルール
DeveloperOverrides.new, # ローカル開発
Rails.app.envs, # 環境変数
AuditedConfiguration.new( # ログ用にラップ
Rails.app.credentials
)
)
最初の非nil値が勝ちます。各バックエンドはリクエストを処理するか、チェーンを下に渡すことができます。
まとめ
Rails.app.credsはシンプルな便利APIのように見えます。しかしCombinedConfigurationは洗練された設定システムの構築ブロックです:
- 外部サービス不要のフィーチャーフラグ
- クレデンシャルの競合なしの開発者サンドボックス
- リクエストスコープのデバッグ
- コンプライアンス対応の監査ログ
- デプロイなしの時間ベース設定
最大のメリットは?アプリケーションコードがクリーンなまま。Rails.app.creds.option(:whatever)を呼び出すだけで、設定レイヤーが複雑さを処理します。
実装についてはPR #56404を参照してください。