DBConfig

Database-backed configuration store for Rails with automatic type conversion, default values, and high-performance eager loading.

Features

  • Type-safe storage: Auto-detects and converts strings, integers, floats, booleans, arrays, hashes, and nil

  • Eager loading: Cache frequently accessed configs for near-zero database overhead

  • Simple API: get/read, get!, set/write, update, delete, exist?, fetch methods

Installation & Setup

# Gemfile
gem "db_config"
bundle install
rails generate db_config:install
rails db:migrate

Avo Integration

[!TIP] If you're using Avo, this gem ships with a pre-configured resource out of the box that should be visible on the menu as "Configurations". If you're using Avo Pro with a custom menu, you can render the resource using:

resource :db_config

Usage

Set any data type - auto-detected and preserved

DBConfig.set(:max_users, 1000)
DBConfig.write(:site_title, "My App") # write is an alias for set
DBConfig.set(:enabled, true)
DBConfig.set(:rate, 0.05)
DBConfig.set(:tags, ["ruby", "rails"])
DBConfig.set(:config, {api: "https://api.example.com", timeout: 30})
DBConfig.set(:feature, nil)

Get with type preservation

DBConfig.get(:max_users)    # => 1000 (Integer)
DBConfig.read(:enabled)     # => true (Boolean) - alias for get

# Get missing config (safe - returns nil)
DBConfig.get(:missing_key)                # => nil

# Get missing config with get! (raises error)
DBConfig.get!(:missing_key)               # => raises DBConfig::NotFoundError

# Check if a config exists
DBConfig.exist?(:page_size)               # => true or false

# Fetch with block - stores block result if key doesn't exist
DBConfig.fetch(:page_size) { 25 }         # => 25 (stores if not found)

# Use || operator for fallback values
DBConfig.get(:page_size) || 25            # => 25 if :page_size not set

Exception Handling

# Strict get - raises exception if not found
begin
  value = DBConfig.get!(:api_token)
rescue DBConfig::NotFoundError => e
  Rails.logger.warn "Missing config: #{e.message}"
  value = "default_token"
end

Managing Configurations

# Update configurations with type safety
DBConfig.set(:max_users, 1000)                              # Set value and type to Integer
DBConfig.update(:max_users, value: 500)                     # Update value keeping same type

DBConfig.set(:site_enabled, "true")                         # Set value and type to String
DBConfig.update(:site_enabled, type: "Boolean")             # Convert "true" string to boolean true

DBConfig.set(:cache_ttl, 3600)                              # Set value and type to Integer
DBConfig.update(:cache_ttl, value: 4600, eager_load: true)  # Update value and enable eager loading

DBConfig.delete(:old_setting)                               # => true if deleted, false if not found

⚡ Eager Loading

[!NOTE] Eager loaded configs are cached per request and automatically synced when changed

# Mark configs for eager loading (loaded once per request, cached)
DBConfig.set(:api_key, "secret123")
DBConfig.update(:api_key, eager_load: true)   # Enable eager loading

# Now served from cache (no database query)
DBConfig.get(:api_key)                         # Served from cache

DBConfig.update(:api_key, eager_load: false)  # Disable eager loading

[!TIP] Good for: API keys, site settings, frequently accessed configs Avoid: Rarely used configs, large data, user-specific settings

Supported Types

Auto-detects and preserves: String, Integer, Float, Boolean, Array, Hash, nil

# All types preserved exactly
DBConfig.set(:string, "hello")           # => "hello" (String)
DBConfig.set(:integer, 42)               # => 42 (Integer)
DBConfig.set(:float, 3.14)               # => 3.14 (Float)
DBConfig.set(:boolean, true)             # => true (Boolean)
DBConfig.set(:array, [1, 2, 3])          # => [1, 2, 3] (Array)
DBConfig.set(:hash, {a: 1})              # => {a: 1} (Hash)
DBConfig.set(:null, nil)                 # => nil (NilClass)

API Reference

Reading Methods

Method Description Raises Errors Example
get(key) Safe retrieval, returns nil if not found No DBConfig.get(:api_key)
get!(key) Strict retrieval Yes, NotFoundError DBConfig.get!(:api_key)
read(key) Alias for get() No DBConfig.read(:api_key)
exist?(key) Check if key exists No DBConfig.exist?(:api_key)
fetch(key, &block) Get or store block result No DBConfig.fetch(:size) { 25 }

Fallback Pattern: Use the || operator to provide fallback values when keys don't exist:

# Recommended pattern for fallback values
page_size = DBConfig.get(:page_size) || 25

Writing Methods

Method Description Example
set(key, value) Store configuration DBConfig.set(:api_key, "secret")
write(key, value) Alias for set() DBConfig.write(:api_key, "secret")
update(key, **options) Update existing config DBConfig.update(:key, value: 42)
delete(key) Remove configuration DBConfig.delete(:old_key)

Method Details

DBConfig.fetch(key, &block)

Gets the value if it exists, or executes the block and stores the result if it doesn't.

# If key exists, returns existing value (block not executed)
DBConfig.set(:api_timeout, 30)
timeout = DBConfig.fetch(:api_timeout) { 60 }    # => 30 (existing value)

# If key doesn't exist, executes block and stores result
cache_size = DBConfig.fetch(:cache_size) { 100 } # => 100 (stores 100)
cache_size = DBConfig.fetch(:cache_size) { 200 } # => 100 (returns stored value)

# Works with any data type
features = DBConfig.fetch(:features) { ["feature1", "feature2"] }
config = DBConfig.fetch(:api_config) { {endpoint: "api.com", timeout: 30} }

# Returns nil if no block given and key doesn't exist
DBConfig.fetch(:missing_key)  # => nil

DBConfig.update(key, **options)

Updates configuration entry with intelligent type conversion and validation.

Parameters:

  • key (Symbol/String) - Configuration key (must exist)
  • **options - Keyword arguments for updates:
    • value: Any - New value to store (with type compatibility checking)
    • type: String - Target type for conversion ("String", "Integer", "Float", "Boolean", "Array", "Hash", "NilClass")
    • eager_load: Boolean - Enable/disable eager loading

Returns: The updated value in its final type

Type Compatibility:

  • When updating value: New values can change type automatically (e.g., "hello" → 42 changes String to Integer)
  • When updating type: Only compatible conversions are allowed (e.g., "123" → Integer, "hello" → Integer fails)
  • Incompatible type conversions raise ArgumentError with detailed message

Examples:

# Update value (can change type automatically)
DBConfig.set(:config_value, "hello")
DBConfig.update(:config_value, value: 42)    # String → Integer (automatic)

# Change type explicitly (requires compatibility)
DBConfig.set(:enabled, "true")              # "true" is set as a string
DBConfig.update(:enabled, type: "Boolean")  # "true" → true (compatible conversion)

# Update multiple attributes
DBConfig.update(:cache_size, value: 1000, eager_load: true)

# Examples from the specification
DBConfig.update(:key, eager_load: true)
DBConfig.update(:key, type: "Boolean")      # Only works if current value is compatible
DBConfig.update(:key, eager_load: true, value: true)  # ✅ Always works

# Fails only on incompatible TYPE conversions (not value updates)
DBConfig.set(:username, "admin")
DBConfig.update(:username, type: "Integer")  # ❌ Fails: "admin" can't become Integer
DBConfig.update(:username, value: 123)       # ✅ Works: new value can be any type

Raises:

  • DBConfig::NotFoundError if key doesn't exist
  • ArgumentError for invalid types or incompatible conversions

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/avo-hq/db_config.

License

The gem is available as open source under the terms of the MIT License.