How to implement a singleton model

(I agree with @user43685 and disagree with @Derek P -- there are lots of good reasons to keep site-wide data in the database instead of a yaml file. For example: your settings will be available on all web servers (if you have multiple web servers); changes to your settings will be ACID; you don't have to spend time implementing a YAML wrapper etc. etc.)

In rails, this is easy enough to implement, you just have to remember that your model should be a "singleton" in database terms, not in ruby object terms.

The easiest way to implement this is:

  1. Add a new model, with one column for each property you need
  2. Add a special column called "singleton_guard", and validate that it is always equal to "0", and mark it as unique (this will enforce that there is only one row in the database for this table)
  3. Add a static helper method to the model class to load the singleton row

So the migration should look something like this:

create_table :app_settings do |t|
  t.integer  :singleton_guard
  t.datetime :config_property1
  t.datetime :config_property2
  ...

  t.timestamps
end
add_index(:app_settings, :singleton_guard, :unique => true)

And the model class should look something like this:

class AppSettings < ActiveRecord::Base
  # The "singleton_guard" column is a unique column which must always be set to '0'
  # This ensures that only one AppSettings row is created
  validates_inclusion_of :singleton_guard, :in => [0]

  def self.instance
    # there will be only one row, and its ID must be '1'
    begin
      find(1)
    rescue ActiveRecord::RecordNotFound
      # slight race condition here, but it will only happen once
      row = AppSettings.new
      row.singleton_guard = 0
      row.save!
      row
    end
  end
end

In Rails >= 3.2.1 you should be able to replace the body of the "instance" getter with a call to "first_or_create!" like so:

def self.instance
  first_or_create!(singleton_guard: 0)
end

I disagree with common opinion - there is nothing wrong with reading a property out of the database. You can read the database value and freeze if you'd like, however there could be more flexible alternatives to simple freezing.

How is YAML different from database? .. same drill - external to application code persistent setting.

Nice thing about the database approach is that it can be changed on the fly in more or less secure way (not opening and overwriting files directly). Another nice thing is it can be shared across the network between cluster nodes (if properly implemented).

The question however remains what would be the proper way to implement such a setting using ActiveRecord.