Singleton Settings Model in Rails App
Recently I had a requirement in an application I was working on to add an internal Settings model. By my own standards, I wanted it to be very lightweight and flexible. There are several gems that provide such drop-in functionality, such as https://github.com/Squeegy/rails-settings or https://github.com/railsjedi/rails_config. However, I felt this added an unnecessary dependency to our project for a seemingly simple feature.
Instead, I ventured to create our own ActiveRecord Based Settings Model. I went in knowing that it needed to be lightweight and flexible. The following is a brief walkthrough of how I went about implementing a Settings model that ultimately has one record in the Settings table and leverages Ruby's dynamic programming to create attributes when required.
The Migration
class CreateSettings < ActiveRecord::Migration def change create_table :settings do |t| t.text :preferences t.timestamps end end end
Pretty straightforward here. Only one column in addition to the timestamps, :preferences. This will be a serialized field allowing me to enter in key value pairs in a hash.
The Pseudo-Singleton
I wanted to ensure that there would never be more than one record in the Settings table, but didn't feel the single Settings object would be accessed often enough to warrant a proper Singleton instantiation. Instead, I added the following to the Settings Model:
class Settings < ActiveRecord::Base ... before_create :confirm_singularity ... def confirm_singularity raise Exception.new("A Setting record already exists") if Settings.count > 0 end end
This simple bit of code ensures that an exception will be raised if an attempt to create a Settings beyond the first is made.
Leveraging Method Missing
I wanted to allow that single Settings preferences Hash to not rely on predefined keys. Rather, it would be far more flexible for any key to be set throughout the lifetime of the application. To achieve this I relied upon method_missing:
def self.method_missing(method, *args) method_name = method.to_s super(method, *args) rescue NoMethodError settings = self.first_or_create if method_name =~ /=$/ name = method_name.gsub('=', '') value = args.first settings.preferences[name.to_sym] = value settings.save else settings.preferences[method_name.to_sym] end end
In this method, when a method that is not found is caught by the 'NoMethodError'. Once caught, I first retrieve or create the settings record. This allows for anyone to assign settings without needing to worry about whether a Setting record has been created or not.
From there I employ a regex to determine whether the method sent to Settings is a setter and thus contains a '='. If it is then I parse the name of the method from the method, and assign the value of the key as the first of args. Then I access the settings.preferences Hash and add the key value pair and subsequently save the settings record.
In the case that the method sent to Settings is a getter, then the else statement simple retrieves and returns the value of the key from settings.preferences.
Conclusion
After completing this task of implementing a flexible Settings model. I felt far more comfortable with Ruby's dynamic/meta programming. Of course there is an overhead cost to dynamic programming, but I feel that for something as innocuous as application settings it was worth it.
I encourage anyone reading this to offer comments and criticisms or other examples of where you've done something similar.













