5 Formas Inesperadas de Usar Rails.app.creds
Rails 8.2 introdujo Rails.app.creds como una forma unificada de acceder a credenciales desde ENV y archivos encriptados. Pero escondido en la implementación hay algo más poderoso: CombinedConfiguration es una cadena de responsabilidad para configuración. Cada backend devuelve un valor o lo pasa al siguiente.
Esto no es solo búsqueda de credenciales. Es middleware de configuración componible.
Aquí hay cinco patrones que aprovechan esta arquitectura.
1. Feature Flags Sin un Servicio
La mayoría de las apps no necesitan LaunchDarkly. Necesitas una forma de alternar funcionalidades que esté controlada por versiones y sea sobrescribible en desarrollo.
# config/credentials.yml.enc
features:
new_checkout: false
dark_mode: true
ai_summaries: false
Accede a ellas como cualquier credencial:
if Rails.app.creds.option(:features, :new_checkout, default: false)
render_new_checkout
else
render_legacy_checkout
end
La magia: ENV sobrescribe las credenciales. Habilita una funcionalidad localmente sin tocar el archivo encriptado:
FEATURES__NEW_CHECKOUT=true bin/rails server
¿Necesitas probar una funcionalidad en staging? Configura la variable ENV en tu configuración de despliegue. Sin cambios de código, sin editar credenciales, sin deploy.
Haciéndolo ergonómico
Envuélvelo en 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
# Uso
<% if feature?(:new_checkout) %>
<%= render "checkout/new" %>
<% end %>
Ahora tienes feature flags que son:
- Controlados por versiones (en credentials)
- Sobrescribibles por entorno (vía ENV)
- Sin dependencias externas
- Gratis
2. Archivos de Sobrescritura para Desarrolladores
Todos los equipos tienen este problema: “¿Cómo pruebo con diferentes credenciales localmente sin commitearlas?”
Crea un archivo local de sobrescritura ignorado por 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, "Faltante: #{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
# Insertar con la más alta prioridad
if Rails.env.development?
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
DeveloperOverrides.new,
Rails.app.envs,
Rails.app.credentials
)
end
Agrega a .gitignore:
.secrets.local.yml
Ahora cualquier desarrollador puede crear su propio archivo de sobrescritura:
# .secrets.local.yml (ignorado por git)
stripe:
secret_key: sk_test_mi_clave_de_prueba_personal
openai:
api_key: sk-mi-propia-clave-openai
features:
experimental_ui: true
No más incidentes de “accidentalmente comiteé mi API key”. No más “déjame editar temporalmente credentials.yml.enc”. Cada desarrollador tiene su propio sandbox.
3. Configuración con Alcance por Request
¿Qué tal si los admins pudieran sobrescribir la configuración por request? Útil para debugging, soporte al cliente o pruebas 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) # Siempre retorna nil para continuar la cadena si no se encuentra
end
def option(*keys, default: nil)
Current.config_overrides&.dig(*keys)
end
def keys
Current.config_overrides&.keys || []
end
def reload
# No-op, se limpia por request
end
end
Conéctalo:
# config/initializers/credentials.rb
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
RequestScopedConfiguration.new,
Rails.app.envs,
Rails.app.credentials
)
Ahora en un controlador:
class Admin::DebugController < ApplicationController
before_action :require_admin
def impersonate_config
# Usar temporalmente el modo de prueba de Stripe para esta sesión de admin
Current.config_overrides = {
stripe: { test_mode: true },
features: { debug_panel: true }
}
redirect_back fallback_location: root_path, notice: "Modo debug habilitado"
end
end
El resto de tu app no sabe ni le importa. Solo llama a Rails.app.creds.option(:stripe, :test_mode) y obtiene el valor correcto para el request actual.
4. Wrapper de Registro de Auditoría
Los requisitos de cumplimiento a menudo exigen registrar el acceso a credenciales sensibles. Con CombinedConfiguration, puedes envolver cualquier backend con 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
Envuelve tus credenciales de producción:
# 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
Cada acceso a credenciales ahora se registra:
{"event":"credential_access","method":"require","keys":"stripe.secret_key","file":"app/services/payment_service.rb","line":42,"timestamp":"2026-01-05T10:30:00Z"}
Puedes alimentar esto a tu SIEM, configurar alertas para patrones de acceso inusuales, o satisfacer a los auditores que quieren prueba de controles de acceso.
5. Configuración Programada
¿Qué tal si la configuración pudiera cambiar basada en el tiempo? Precios de Black Friday. Temas festivos. Ventanas de mantenimiento programado.
# 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 más corto durante alto tráfico
}
),
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, "Faltante: #{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
# Podría recargar desde base de datos o fuente externa
end
end
Conéctalo a la cadena:
Rails.app.creds = ActiveSupport::CombinedConfiguration.new(
ScheduledConfiguration.new, # Sobrescrituras basadas en tiempo primero
Rails.app.envs,
Rails.app.credentials
)
En Black Friday, Rails.app.creds.option(:pricing, :discount_percent) automáticamente retorna 30. Sin necesidad de deploy. Cuando el horario termina, cae al valor por defecto.
Tu código de precios no necesita saber sobre Black Friday:
def apply_discount(price)
discount = Rails.app.creds.option(:pricing, :discount_percent, default: 0)
price * (1 - discount / 100.0)
end
El Patrón
Los cinco ejemplos comparten la misma perspectiva: CombinedConfiguration es middleware para configuración. El patrón de cadena de responsabilidad te permite:
- Agregar comportamientos en capas sin modificar código existente
- Componer funcionalidades ordenando backends
- Separar responsabilidades entre almacenamiento, control de acceso y lógica de negocio
El orden importa:
ActiveSupport::CombinedConfiguration.new(
RequestScopedConfiguration.new, # Mayor prioridad: sobrescrituras por request
ScheduledConfiguration.new, # Reglas basadas en tiempo
DeveloperOverrides.new, # Desarrollo local
Rails.app.envs, # Variables de entorno
AuditedConfiguration.new( # Envuelto para logging
Rails.app.credentials
)
)
El primer valor no-nil gana. Cada backend puede manejar la solicitud o pasarla hacia abajo en la cadena.
Conclusión
Rails.app.creds parece una API de conveniencia simple. Pero CombinedConfiguration es un bloque de construcción para sistemas de configuración sofisticados:
- Feature flags sin servicios externos
- Sandboxes de desarrollador sin conflictos de credenciales
- Debugging con alcance por request
- Registro de auditoría listo para cumplimiento
- Configuración basada en tiempo sin deploys
¿La mejor parte? Tu código de aplicación permanece limpio. Solo llama a Rails.app.creds.option(:whatever) y la capa de configuración maneja la complejidad.
Mira el PR #56404 para la implementación.