Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a singleton model

I have a site in rails and want to have site-wide settings. One part of my app can notify the admin by SMS if a specific event happens. This is an example of a feature that I want configurable via the site-wide settings.

So I was thinking I should have a Setting model or something. It needs to be a model because I want to be able to has_many :contacts for the SMS notification.

The problem is that there can only be one post in the database for the settings model. So I was thinking of using a Singleton model but that only prevents new object to be created right?

Would I still need to create getter and setter methods for each attribute like so:

def self.attribute=(param)   Model.first.attribute = param end  def self.attribute   Model.first.attribute end 

Is it perhaps not best-practice to use Model.attribute directly but always create an instance of it and use that?

What should I do here?

like image 894
Fredrik Avatar asked Dec 30 '08 03:12

Fredrik


People also ask

How can Singleton be implemented?

To implement a Singleton pattern, we have different approaches but all of them have the following common concepts. Private constructor to restrict instantiation of the class from other classes. Private static variable of the same class that is the only instance of the class.

What is the best way to implement a singleton class?

Eager initialization: In eager initialization, the instance of Singleton Class is created at the time of class loading, this is the easiest method to create a Singleton class. By making the constructor as private you are not allowing other class to create a new instance of the class you want to create the Singleton.

What is singleton class and how it is implemented?

In Java, Singleton is a design pattern that ensures that a class can only have one object. To create a singleton class, a class must implement the following properties: Create a private constructor of the class to restrict object creation outside of the class.

What are some ways to implement a Singleton in java?

The most popular approach is to implement a Singleton by creating a regular class and making sure it has: A private constructor. A static field containing its only instance. A static factory method for obtaining the instance.


2 Answers

(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 
like image 154
Rich Avatar answered Sep 19 '22 15:09

Rich


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.

like image 35
user43685 Avatar answered Sep 19 '22 15:09

user43685