Jesús Dugarte.

Saturday, May 13, 2006

Settings model for a Rails application

(Puedes ver la versión original de este post en español aquí)

I was looking for a Rails plugin to handle the settings (preferences, options, etc.) of a web application I’m working on.

In the Rails wiki I found this ConfigurationGenerator that looked pretty neat, and was exactly what I wanted, but I couldn’t find the code anywhere, nor have I received an answer to a question a posted in the Rails forum about this generator.

Then I found this other Settings plugin that looked great, but it wasn’t quite what I needed.

So I decided to make my own model to handle the settings of my web application, based in what I’ve already seen in these plugins.

What I wanted was a table with a unique row and a column for every option in the settings. This would let me save every option with the right data type and size. And I wanted to be able to access any setting, from any place in the application, with something as simple as:
Settings.option

I started with the settings for the application name and a "about us" text for the home page. The migration looked like:

class AddSettings < ActiveRecord::Migration

def self.up

create_table :settings do |table|
table.column :aplication, :string, :limit => 100
table.column :about_us, :text
table.column :updated_at, :datetime
end

execute "Insert into settings (aplication, about_us) Values ('Web app.', 'We are...')"

end

def self.down
drop_table :settings
end

end

This table will always have a unique, so one row is inserted automatically in the migration, with samples values. The real values will be assigned in the application.

The model looked like:


class Setting < ActiveRecord::Base

validates_presence_of : aplication, :about_us
private_class_method :new, :create, :destroy, :destroy_all, :delete, :delete_all

@@s = find(:first)

def self.method_missing(method, *args)
option = method.to_s
if option.include? '='
# Set the value
var_name = option.gsub('=', '')
value = args.first
@@s[var_name] = value
else
# Get the value
@@s[option]
end
end

def self.save
@@s.save
end

def self.update_attributes(attributes)
@@s.update_attributes(attributes)
end

def self.errors
@@s.errors
end

end

I would use the class directly, without an instance of it, so I had to avoid the creation of new objects with the line private_class_method :new, :create. To this list I added :destroy, :destroy_all, :delete, :delete_all because I certainly didn’t want that unique row to be deleted. This list can be extended to add any other method we want to deny access to.

I don’t want to go to the database every time I need a setting, so I use a class variable to cache the setting values the first time the class was used: @@s = find(:first)

The methods save, update_attributes and errors are defined as class methods, with self, to be able to called them without the need of an instance of the class.

You can assign the setting’s value one by one, and call the save method at the end, so there would be only one update operation in the database. Or you can call the update_attributes with a hash as parameter, with all the settings values to save (when saving from a form, for example). The method errors is defined to be able to use the error_messages_for 'settings' helper in a form.

To edit the settings you can use something like the following code. In the appropriate controller:

def settings
@settings = Settings
end

def save_settings
@settings = Settings
if @settings.update_attributes(params[:settings])
flash[:notice] = 'Settings successfully saved'
redirect_to :action => "index"
else
render :action => "settings"
end
end

And in the view:

<h1>Settings</h1>
<br>
<%= start_form_tag :action => 'save_settings' %>
<%= error_messages_for 'settings' %>
<p><label for="settings_aplication">Application:</label><br/>
<%= text_field_tag 'settings[aplication]', @settings.aplication %></p>
<p><label for="settings_about_us">About us:</label><br/>
<%= text_area_tag "settings[about_us]", @settings.about_us %></p>
<br>
<%= submit_tag 'Save' %>
<%= end_form_tag %>

This is what suits my need the best way so far. If there is a better way to do this I’d be happy to hear it.

Like this article? Digg it!
... read more