DEV Community

Cover image for Stop Redeploying for Config Changes: The 5-Minute Rails Key-Value Store
Zil Norvilis
Zil Norvilis

Posted on

Stop Redeploying for Config Changes: The 5-Minute Rails Key-Value Store

We have all been there. You are five minutes into a dinner reservation when your phone buzzes. The marketing team wants to enable the "Holiday Sale Banner" right now, or an external API is down and you need to switch the app into a partial maintenance mode.

If your configuration is hardcoded in Ruby files or locked inside Environment Variables, making that change requires a commit, a build, and a deploy. That’s a 15-minute process for a 1-second change.

There is a better way. By leveraging ActiveRecord, we can create a lightweight, dynamic Key-Value store right inside your application database.

Here is how to implement a dictionary-style System Setting interface in Rails.

The Setup

We don't need a heavy gem like rails-settings-cached for simple use cases. We just need a table to hold keys and values.

1. The Migration

First, let's create a table to store our settings. We use text for the value to allow for long strings (like announcement messages) and ensure the key is unique so we don't get duplicates.

# db/migrate/20260115225500_create_system_settings.rb
class CreateSystemSettings < ActiveRecord::Migration[8.0]
  def change
    create_table :system_settings do |t|
      t.string :key, null: false
      t.text :value

      t.timestamps
    end
    add_index :system_settings, :key, unique: true
  end
end
Enter fullscreen mode Exit fullscreen mode

2. The Model (With Syntax Sugar)

This is where the magic happens. A standard Rails model would force you to write verbose queries like SystemSetting.find_by(key: 'promo'). We can do better.

By defining self.[] and self.[]= class methods, we can treat our database table like a standard Ruby Hash.

# app/models/system_setting.rb
class SystemSetting < ApplicationRecord
  validates :key, presence: true, uniqueness: true

  # The Getter
  # Usage: SystemSetting[:maintenance_mode]
  def self.[](key)
    find_by(key: key)&.value
  end

  # The Setter
  # Usage: SystemSetting[:maintenance_mode] = "on"
  def self.[]=(key, value)
    setting = find_or_initialize_by(key: key)
    setting.value = value
    setting.save!
  end
end
Enter fullscreen mode Exit fullscreen mode

How to Use It

Because we mapped the array accessors, the API is incredibly clean. You can use it anywhere in your controllers, views, or background jobs.

Setting a value (Console or Admin Panel):

SystemSetting[:global_announcement] = "Maintenance scheduled for 10 PM EST."
Enter fullscreen mode Exit fullscreen mode

Reading a value (In your Application Layout):

<% if SystemSetting[:global_announcement].present? %>
  <div class="alert alert-warning">
    <%= SystemSetting[:global_announcement] %>
  </div>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Real-World Use Cases

1. The "Kill Switch"

Third-party integrations fail. If your SMS provider goes down, you don't want your background workers retrying thousands of times and crashing your Redis instance.

def send_sms
  return if SystemSetting[:sms_enabled] == "false"
  # ... execute sending logic
end
Enter fullscreen mode Exit fullscreen mode

2. Dynamic Feature Flags

Want to let a specific feature go live at a specific time without a deploy?

if SystemSetting[:beta_features_active] == "true"
  render :new_dashboard
else
  render :old_dashboard
end
Enter fullscreen mode Exit fullscreen mode

Leveling Up: Adding Caching

The implementation above is great, but it has one flaw: it hits the database every time you ask for a setting. If you put this in your application header, you are adding a SQL query to every single page load.

Since these settings rarely change, they are perfect candidates for Rails.cache. Here is the production-ready version:

class SystemSetting < ApplicationRecord
  validates :key, presence: true, uniqueness: true
  after_commit :clear_cache

  def self.[](key)
    Rails.cache.fetch("system_setting:#{key}") do
      find_by(key: key)&.value
    end
  end

  def self.[]=(key, value)
    setting = find_or_initialize_by(key: key)
    setting.value = value
    setting.save!
  end

  private

  def clear_cache
    Rails.cache.delete("system_setting:#{key}")
  end
end
Enter fullscreen mode Exit fullscreen mode

Now, the database is only queried once. Subsequent requests pull instantly from memory (Redis/Memcached), and the cache is automatically busted whenever you update the record.

Summary

You don't always need complex infrastructure for configuration. Sometimes, a simple database table and a little bit of Ruby syntax sugar are all you need to make your application more flexible and your life easier.

Deploy the code once. Change the settings whenever you want.

Top comments (0)