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!

4 Comments:

  • Most excellent article! This is a very elegant way of handling settings. Thanks. I had never used method_missing, this is a great use of it.

    By Blogger Wes Rogers, at 1:47 PM  

  • Great article on an configuration solution I was trying to get working in my app.

    Trying to review and play with what you have here.

    Is the method_missing supposed to allow the creation of columns dynamically? Not sure I understand what it is doing.

    While playing with it in console, I tried (color does not exist as a column)

    s=Setting
    s.color="blue"
    s.save
    I get true as a response...
    and if I query s.color while in console, I see "blue" as a response but it doesn't save it to the database even though I got a true back.

    Can you explain

    1) what it the method_missing doing?

    2) is it giving me the ability to add "color" on the fly? I am not able to do this to other Models in console

    3) if it is doing this, why is the save not actually happening and creating a new column for "color"?

    Thanks!

    By Anonymous Anonymous, at 2:43 AM  

  • Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now keep it up!

    By Anonymous Anonymous, at 4:19 AM  

  • Hola,
    Yo empeze haciendo el mismo que tu, pero despues solo puse asi:

    before_create :check_if_unique
    def check_if_unique
    SiteSetting.all.size < 1
    end

    Para mi esta bien, pero tu abordaje es muy guapa!

    By Blogger Guga, at 7:33 AM  

Post a Comment

<< Home