5 Façons Inattendues d'Utiliser Rails.app.creds
Rails 8.2 a introduit Rails.app.creds comme moyen unifié d’accéder aux credentials depuis ENV et les fichiers chiffrés. Mais caché dans l’implémentation se trouve quelque chose de plus puissant : CombinedConfiguration est une chaîne de responsabilité pour la configuration. Chaque backend retourne une valeur ou passe au suivant.
Ce n’est pas juste une recherche de credentials. C’est un middleware de configuration composable.
Voici cinq patterns qui exploitent cette architecture.
1. Feature Flags Sans Service
La plupart des apps n’ont pas besoin de LaunchDarkly. Vous avez besoin d’un moyen de basculer les fonctionnalités qui soit versionné et modifiable en développement.
# config/credentials.yml.enc
features:
new_checkout: false
dark_mode: true
ai_summaries: false
Accédez-y comme n’importe quel credential :
if Rails.app.creds.option(:features, :new_checkout, default: false)
render_new_checkout
else
render_legacy_checkout
end
La magie : ENV remplace les credentials. Activez une fonctionnalité localement sans toucher au fichier chiffré :
FEATURES__NEW_CHECKOUT=true bin/rails server
Besoin de tester une fonctionnalité en staging ? Définissez la variable ENV dans votre config de déploiement. Pas de changement de code, pas d’édition de credentials, pas de deploy.
Le rendre ergonomique
Enveloppez-le dans un helper :
# app/helpers/feature_flags_helper.rb
module FeatureFlagsHelper
def feature?(name)
Rails.app.creds.option(:features, name, default: false).to_s == "true"
end
end
# Utilisation
<% if feature?(:new_checkout) %>
<%= render "checkout/new" %>
<% end %>
Vous avez maintenant des feature flags qui sont :
- Versionnés (dans credentials)
- Modifiables par environnement (via ENV)
- Sans dépendances externes
- Gratuits
2. Fichiers de Surcharge pour Développeurs
Chaque équipe a ce problème : “Comment tester avec des credentials différents localement sans les commiter ?”
Créez un fichier local de surcharge ignoré par git :
# 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, "Manquant : #{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
# Insérer avec la plus haute priorité
if Rails.env.development?
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
DeveloperOverrides.new,
Rails.app.envs,
Rails.app.credentials
)
end
Ajoutez à .gitignore :
.secrets.local.yml
Maintenant n’importe quel développeur peut créer son propre fichier de surcharge :
# .secrets.local.yml (ignoré par git)
stripe:
secret_key: sk_test_ma_cle_de_test_personnelle
openai:
api_key: sk-ma-propre-cle-openai
features:
experimental_ui: true
Plus d’incidents “j’ai accidentellement commité ma clé API”. Plus de “laissez-moi éditer temporairement credentials.yml.enc”. Chaque développeur a son propre sandbox.
3. Configuration Scopée par Requête
Et si les admins pouvaient surcharger la configuration par requête ? Utile pour le debugging, le support client ou les tests 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) # Retourne toujours nil pour continuer la chaîne si non trouvé
end
def option(*keys, default: nil)
Current.config_overrides&.dig(*keys)
end
def keys
Current.config_overrides&.keys || []
end
def reload
# No-op, nettoyé par requête
end
end
Connectez-le :
# config/initializers/credentials.rb
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
RequestScopedConfiguration.new,
Rails.app.envs,
Rails.app.credentials
)
Maintenant dans un contrôleur :
class Admin::DebugController < ApplicationController
before_action :require_admin
def impersonate_config
# Utiliser temporairement le mode test Stripe pour cette session admin
Current.config_overrides = {
stripe: { test_mode: true },
features: { debug_panel: true }
}
redirect_back fallback_location: root_path, notice: "Mode debug activé"
end
end
Le reste de votre app ne sait pas et ne s’en soucie pas. Elle appelle juste Rails.app.creds.option(:stripe, :test_mode) et obtient la bonne valeur pour la requête actuelle.
4. Wrapper de Journal d’Audit
Les exigences de conformité mandatent souvent la journalisation des accès aux credentials sensibles. Avec CombinedConfiguration, vous pouvez envelopper n’importe quel backend avec du logging :
# 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
Enveloppez vos credentials de production :
# 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
Chaque accès aux credentials est maintenant journalisé :
{"event":"credential_access","method":"require","keys":"stripe.secret_key","file":"app/services/payment_service.rb","line":42,"timestamp":"2026-01-05T10:30:00Z"}
Vous pouvez alimenter cela vers votre SIEM, configurer des alertes pour des patterns d’accès inhabituels, ou satisfaire les auditeurs qui veulent une preuve des contrôles d’accès.
5. Configuration Programmée
Et si la configuration pouvait changer selon le temps ? Prix du Black Friday. Thèmes de fêtes. Fenêtres de maintenance programmées.
# 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 } # Cache plus court pendant le trafic élevé
}
),
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, "Manquant : #{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
# Pourrait recharger depuis la base de données ou une source externe
end
end
Connectez-le à la chaîne :
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
ScheduledConfiguration.new, # Surcharges basées sur le temps d'abord
Rails.app.envs,
Rails.app.credentials
)
Le Black Friday, Rails.app.creds.option(:pricing, :discount_percent) retourne automatiquement 30. Pas de deploy nécessaire. Quand la période se termine, ça retombe sur la valeur par défaut.
Votre code de tarification n’a pas besoin de connaître le Black Friday :
def apply_discount(price)
discount = Rails.app.creds.option(:pricing, :discount_percent, default: 0)
price * (1 - discount / 100.0)
end
Le Pattern
Les cinq exemples partagent la même insight : CombinedConfiguration est un middleware pour la configuration. Le pattern chaîne de responsabilité vous permet de :
- Superposer des comportements sans modifier le code existant
- Composer des fonctionnalités en ordonnant les backends
- Séparer les responsabilités entre stockage, contrôle d’accès et logique métier
L’ordre compte :
ActiveSupport::CombinedConfiguration.new(
RequestScopedConfiguration.new, # Plus haute priorité : surcharges par requête
ScheduledConfiguration.new, # Règles basées sur le temps
DeveloperOverrides.new, # Développement local
Rails.app.envs, # Variables d'environnement
AuditedConfiguration.new( # Enveloppé pour le logging
Rails.app.credentials
)
)
La première valeur non-nil gagne. Chaque backend peut soit gérer la requête, soit la passer plus bas dans la chaîne.
Conclusion
Rails.app.creds ressemble à une simple API de commodité. Mais CombinedConfiguration est une brique de base pour des systèmes de configuration sophistiqués :
- Feature flags sans services externes
- Sandboxes développeur sans conflits de credentials
- Debugging scopé par requête
- Journal d’audit prêt pour la conformité
- Configuration basée sur le temps sans deploys
Le meilleur ? Votre code applicatif reste propre. Il appelle juste Rails.app.creds.option(:whatever) et la couche de configuration gère la complexité.
Voir PR #56404 pour l’implémentation.