Modern Rails applications demand sophisticated configuration management to handle complex business requirements. I’ll share practical techniques I’ve developed over years of building enterprise Rails systems.
Configuration Storage and Access
The foundation of any configuration system is reliable storage. While Rails’ config/credentials.yml is suitable for basic needs, advanced systems require more flexibility. I prefer using a combination of database and cache:
class Configuration < ApplicationRecord
validates :key, presence: true, uniqueness: { scope: :namespace }
serialize :value, JSON
after_commit :clear_cache
def self.get(key, namespace = 'default')
Rails.cache.fetch("config:#{namespace}:#{key}") do
find_by(key: key, namespace: namespace)&.value || default_for(key)
end
end
private
def clear_cache
Rails.cache.delete("config:#{namespace}:#{key}")
end
end
Runtime Updates and Validation
Configuration changes during runtime require careful handling. I implement strict validation rules and type checking:
module ConfigurationValidation
def validate_config(key, value)
schema = config_schemas[key]
return true unless schema
validator = JSONSchemer.schema(schema)
validator.valid?(value)
end
private
def config_schemas
{
'email.smtp_settings' => {
type: 'object',
required: ['address', 'port'],
properties: {
address: { type: 'string' },
port: { type: 'integer' }
}
}
}
end
end
Feature Flags and Toggle Management
Feature flags enable gradual rollouts and A/B testing. Here’s my implementation that supports percentage-based rollouts:
class FeatureToggle
def initialize(feature_key)
@key = feature_key
@config = Configuration.get("features.#{feature_key}")
end
def enabled?(context = {})
return false unless @config
case @config['type']
when 'boolean'
@config['enabled']
when 'percentage'
user_in_percentage?(context[:user_id])
when 'gradual'
time_enabled?
end
end
private
def user_in_percentage?(user_id)
return false unless user_id
percentage = @config['percentage'] || 0
Zlib.crc32("#{@key}:#{user_id}") % 100 < percentage
end
end
Environment-Specific Settings
Different environments often require distinct configurations. I create a flexible system that handles environment overrides:
class EnvironmentConfig
def self.for(key)
env = Rails.env
config = Configuration.get(key)
env_config = Configuration.get("#{env}.#{key}")
return config unless env_config
config.deep_merge(env_config)
end
end
Configuration Versioning
Tracking configuration changes is crucial for debugging and compliance. I implement versioning with PaperTrail:
class Configuration < ApplicationRecord
has_paper_trail
def revert_to!(version)
transaction do
paper_trail.revert_to(version)
clear_cache
ConfigurationRevertJob.perform_later(id, version.id)
end
end
end
Access Control
Secure configuration management requires granular access control. I implement role-based permissions:
class ConfigurationPolicy
attr_reader :user, :config
def initialize(user, config)
@user = user
@config = config
end
def update?
return true if user.admin?
allowed_namespaces = user.configuration_permissions
allowed_namespaces.include?(config.namespace)
end
end
Audit Logging
Comprehensive audit trails help track who changed what and when:
class ConfigurationAudit < ApplicationRecord
belongs_to :user
belongs_to :configuration
validates :action, presence: true
validates :changes, presence: true
serialize :changes, JSON
def self.log(config, user, action)
create!(
configuration: config,
user: user,
action: action,
changes: config.saved_changes,
ip_address: Current.ip_address
)
end
end
Dynamic Configuration Loading
I create a system that loads configurations dynamically without requiring application restarts:
module DynamicConfig
extend self
def method_missing(method_name, *args)
key = method_name.to_s
return super unless config_exists?(key)
define_singleton_method(method_name) do
Configuration.get(key)
end
send(method_name)
end
private
def config_exists?(key)
Rails.cache.fetch("config_exists:#{key}") do
Configuration.exists?(key: key)
end
end
end
Configuration Dependencies
Some settings depend on others. I handle these relationships explicitly:
class ConfigurationDependency
def self.validate_dependencies(config)
dependencies = config.dependencies
return true if dependencies.blank?
dependencies.all? do |dep_key|
Configuration.get(dep_key).present?
end
end
end
Configuration Encryption
Sensitive configuration values require encryption:
module ConfigurationEncryption
extend ActiveSupport::Concern
included do
attr_encrypted :value,
key: Rails.application.credentials.config_encryption_key,
algorithm: 'aes-256-gcm',
encode: true
end
end
Configuration Templates
I create templates for common configuration patterns:
class ConfigurationTemplate
def self.create_from_template(template_name, namespace)
template = templates[template_name]
return unless template
template.each do |key, value|
Configuration.create!(
key: key,
value: value,
namespace: namespace
)
end
end
private
def self.templates
{
email_provider: {
'smtp_address' => '',
'smtp_port' => 587,
'smtp_authentication' => 'plain'
}
}
end
end
These techniques form a robust configuration management system. The key is building flexible, maintainable components that work together seamlessly. Regular monitoring and updates ensure the system evolves with application needs.
Through my experience, I’ve found that successful configuration management requires balancing flexibility with security. The system should be easy to use while maintaining strict controls over sensitive data.
Remember to implement proper error handling and logging throughout the system. Configuration issues can be challenging to debug without detailed logs.
Consider adding a web interface for managing configurations, but ensure it’s properly secured and audited. This makes the system more accessible to non-technical team members while maintaining control.
Regular backups of configuration data are essential. I recommend implementing automated backup procedures and testing restoration processes periodically.
The configuration system should scale with your application. Design components to handle increased load and complexity as your application grows.