11.15.2007

Loading ActionMailer SMTP settings from the database


Here is a simple way to load your SMTP settings from the database instead of having to call ActionMailer::Base.smtp_settings= in environment.rb, development.rb or production.rb. Using the database to store these setting, while a bit slower, allow one to easily allow an administration to change the configuration without having to restart the Rails application.

The first approach I used was to just override the self.smtp_settings method in my ActionMailer::Base subclass. But that didn't work due to cattr_accessor not working as expected. So I had to also add class_inheritable_accessor :smtp_settings.

So I did the following :
class StuffNotifier < ActionMailer::Base
class_inheritable_accessor :smtp_settings

def self.smtp_settings
Hash.new {|h, k|
Preference["smtp.#{k.to_s}"]
}
end
end
I use Hash.new with a Proc because the stuff the Preference[] method access the database, if I construct an Hash with all the value loaded from the database, then the Hash will be constructed many times as ActionMailer::Base access self.smtp_settings many times when sending an email. Using an empty hash with a default Proc solved my problem. I also prepend "smtp." so it retrieves the "smtp.host" value from my system preference table.

If somebody knows of a better way to do it, feel free to add a comment or a link.




Storing System Preferences in the Database

Here is a simple way to store system preferences in the database for a Ruby on Rails application. This simple solution offers no caching, so accessing the same preference many times will result in many database request. Suggestions for per request caching are welcomed.

Using the code bellow, one can access preferences like this :
admin_email= Preference["admin_email"]
First, let's start with a migration that will create a table in which we can store many types of preferences :
class CreatePreferences < ActiveRecord::Migration
def self.up
create_table :preferences do |t|
t.column :setting, :string
t.column :type, :string
t.column :value, :string
end
end

def self.down
drop_table :preferences
end
end
Now, we create the model. Because we want to be able to get many types of preferences (and eventually validate their value in different ways), we create a base Preference class and many subclasses for all the types of preferences we want to have. Some subclass will override the value and value= methods to convert the value to the proper type :
class Preference < ActiveRecord::Base
validates_presence_of :setting

def self.[](setting)
setting= self.find_by_setting(setting)
setting.nil? ? nil : setting.value
end
end

class UrlPreference < Preference
end

class EmailPreference < Preference
end

class StringPreference < Preference
end

class IntegerPreference < Preference
def value()
self[:value].nil? ? nil : self[:value].to_i
end

def value=(v)
self[:value]= v.nil? ? nil : v.to_s
end
end
Note that we could add validation for the value attribute in UrlPreference and EmailPreference to validate the format of the value.

AdSense Links