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?
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.
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.
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.
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.
(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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With