Tools
Tools: Stop Redeploying for Config Changes: The 5-Minute Rails Key-Value Store
2026-01-17
0 views
admin
The Setup ## 1. The Migration ## 2. The Model (With Syntax Sugar) ## How to Use It ## Real-World Use Cases ## 1. The "Kill Switch" ## 2. Dynamic Feature Flags ## Leveling Up: Adding Caching ## Summary 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. 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. 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. 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. 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): Reading a value (In your Application Layout): 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. Want to let a specific feature go live at a specific time without a deploy? 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: 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. 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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse COMMAND_BLOCK:
# 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 COMMAND_BLOCK:
# 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 COMMAND_BLOCK:
# 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 COMMAND_BLOCK:
# 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 COMMAND_BLOCK:
# 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 COMMAND_BLOCK:
# 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 CODE_BLOCK:
SystemSetting[:global_announcement] = "Maintenance scheduled for 10 PM EST." Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
SystemSetting[:global_announcement] = "Maintenance scheduled for 10 PM EST." CODE_BLOCK:
SystemSetting[:global_announcement] = "Maintenance scheduled for 10 PM EST." CODE_BLOCK:
<% if SystemSetting[:global_announcement].present? %> <div class="alert alert-warning"> <%= SystemSetting[:global_announcement] %> </div>
<% end %> Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
<% if SystemSetting[:global_announcement].present? %> <div class="alert alert-warning"> <%= SystemSetting[:global_announcement] %> </div>
<% end %> CODE_BLOCK:
<% if SystemSetting[:global_announcement].present? %> <div class="alert alert-warning"> <%= SystemSetting[:global_announcement] %> </div>
<% end %> COMMAND_BLOCK:
def send_sms return if SystemSetting[:sms_enabled] == "false" # ... execute sending logic
end Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
def send_sms return if SystemSetting[:sms_enabled] == "false" # ... execute sending logic
end COMMAND_BLOCK:
def send_sms return if SystemSetting[:sms_enabled] == "false" # ... execute sending logic
end CODE_BLOCK:
if SystemSetting[:beta_features_active] == "true" render :new_dashboard
else render :old_dashboard
end Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
if SystemSetting[:beta_features_active] == "true" render :new_dashboard
else render :old_dashboard
end CODE_BLOCK:
if SystemSetting[:beta_features_active] == "true" render :new_dashboard
else render :old_dashboard
end CODE_BLOCK:
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 CODE_BLOCK:
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 CODE_BLOCK:
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
how-totutorialguidedev.toaiswitchdatabase