Settings plugin
February 24th, 2006
This information is now old. A new version of the plugin can be found here with lots of instructions and examples.
I have noticed that in most of my projects I end up with a settings table of some sort that keeps track of global properties of the application that I would rather not hard code. Having done this a few times I decided to make an elegant reuseable plugin, with a sprinkling of the Ruby magic known as meta programming.
Settings.admin_password = 'supersecret'
Settings.date_format = '%m %d, %Y'
Settings.foo = 'bar'
Now lets read them back:
Settings.foo # returns 'bar'
Changing an existing setting is the same as creating a new setting:
Settings.foo = 'super duper bar'
Decide you dont want to track a particular setting anymore?
Settings.destroy :foo
Settings.foo # Now gives a setting variable not found error.
Want a list of all the settings?
Settings.all # returns {'admin_password' => 'super_secret', 'date_format' => '%m %d, %Y'}
How do you use it?
- Install the plugin.
- Create the schema for the settings table
- Migrate schema into your database
ruby script/plugin install http://beautifulpixel.textdriven.com/svn/plugins/settings/
ruby script/generate settings_migration
rake migrate
And that’s it, really.
Enjoy!
June 28th, 2007 at 11:05 PM
Exactly what I needed – thanks!
June 28th, 2007 at 11:05 PM
Glad its what you need, if you have any feature requests be sure to let me know.
June 28th, 2007 at 11:05 PM
Great! Just what I was looking for. Could you provide an example of how set from a value in the envrionments files.
June 28th, 2007 at 11:05 PM
The whole point of this is to keep these details out of your source code. Perhaps you could setup the defaults in a migration simply by:
class CreateMySettings < ActiveRecord::Migration def self.up Settings.my_setting = 'foo' Settings.bar = 'abc123' end def self.down Settings.destroy :my_setting Settings.destroy :bar end endThen, with each instance of the app you can modify the DB values directly, or in a script/console session by setting a value. Ideally however you will build a page in your app that allows you to change whats in this table.
If you want to set them in envirmonment.rb I suggest just using a constant by do this at the bottom of the file
MY_SETTING = ‘fandoobius’
then it should be avaible throughout your app.
Now, having said all that, if you really really want to have a command that updates or creates a databse record everytime your application, you can simply put the assignment statement at the bottom of environment.rb:
Settings.my_setting = ‘fandoobius’
But if you are going to define it there, you really don;t need to store it in the database.
June 28th, 2007 at 11:05 PM
Good points. Thanks
June 28th, 2007 at 11:05 PM
I really like this plugin, but I ran into a slight problem when I ran something like this:
Now lets say I have 5000 users, Rails will go to the database 5000 times asking it the same each time
Solution? I was going to email you my modification which added some very light lazy loading (caching) but I didn’t find it. So I’ve posted the settings.rb file here http://orya.co.uk/pages/settings
The change has a side effect of also allowing you to access the settings like this:
June 28th, 2007 at 11:05 PM
Oops, that link got messed up, it’s
http://orya.co.uk/pages/settings
June 28th, 2007 at 11:05 PM
Very nice!
I hadn’t thought of the case of repeated queries, but a cache makes a lot of sense. I did add a reload method however, which should wipe and refill the cache (thanks to self.all) and then return itself, so if any of the Settings values change without going through the Settings object directly then you can just use:
Settings.reloador
Settings.reload.my_settingThank you for the awesome contribution!
SVN has been updated, reinstall the plugin to get the latest version.
June 28th, 2007 at 11:05 PM
Great stuff.
Hadn’t thought to add the reload in there, good call.
I can forsee using this plugin on many projects, thanks!
June 28th, 2007 at 11:05 PM
This is great. I haven’t used it yet, but it certainly does what I need.
As a future enhancement, I think you could add some sort of variation to handle personal settings. Therefore, an app could have global settings and personal settings, which could add to or override the global settings. All this using a similar concept as the Userstamp plugin.
I’m not that good in Rails/Ruby yet, but I would figure that using Userstamp as a model, it shouldn’t be too difficult.
Thanks!
June 28th, 2007 at 11:05 PM
If you want to expose Settings in the view, there are several options – you can pass individual settings as variables from the model, for instance. The one I like, though, is a simple helper in application_helper.rb that looks like this:
def get_settings(name)
Settings[name]
end
It might be nice if the plugin added some functionality to do this – in a better way, if there is one, certainly.
June 28th, 2007 at 11:05 PM
Nothing wrong with asking the Settings class directly in the view, in fact it does the exact same thing as you helper without that 3 lines of code.
June 28th, 2007 at 11:05 PM
As a future enhancement, I think you could add some sort of variation to handle personal settings. Therefore, an app could have global settings and personal settings, which could add to or override the global settings. All this using a similar concept as the Userstamp plugin.
June 28th, 2007 at 11:05 PM
Is it intentional that updating a setting adds a new record instead of just updating the value?
Settings.foo = ‘bar’ Settings.foo = ‘foobar’
This ends up as two records in my DB. id=1, var=foo, value=bar id=2, var=foo, value=foobar
Andreas
June 28th, 2007 at 11:05 PM
Sorry about that, new version posted on the svn repository. It should solve your problems. If its still broken in anyway please let me know.
June 28th, 2007 at 11:05 PM
I am unfamiliar with the Userstamp plugin and probably won’t get to adding this functionality myself. But anyone is welcome to make some changes and send me a patch.
June 28th, 2007 at 11:05 PM
Hi Alex,
I’ve tried your plugin the way you descried it, but when I do Settings.foo in one of my controllers, it gives me the following error
“uninitialized constant Settings”
b.t.w. I’ve tried to put your code in one of my models, but then I’m getting:
stack level too deep #{RAILS_ROOT}/app/models/application_setting.rb:57:in `[]’ #{RAILS_ROOT}/app/models/application_setting.rb:24:in `method_missing’ etc etc…
I’m a newbie b.t.w., so forgive me when the answers are self-evident.
Regards, Jeroen
June 28th, 2007 at 11:05 PM
Rails is not finding the plugin it seems. Did you install with the plugin script? Do you have the latest version of rails?
June 28th, 2007 at 11:05 PM
Hi Alex,
It was indeed my version of rails … was still on 1.1., sorry for the inconvenience.
Regards, Jeroen
p.s. Nice blog!
June 28th, 2007 at 11:05 PM
Thank you. You have helped someone more than you could know.
June 28th, 2007 at 11:05 PM
I would like to have default settings like this plugin have but I want to have the same settings to my Account model and User model.
Then when I get Settings.foo, for exemple, the plugin check if the user has the foo settings defined if not, check if the account has, and then if not, it get from the default value.
Is it possible?
Thanks for the great plugin
June 28th, 2007 at 11:05 PM
You’re probably tired of hearing this, but thanks for writing this plugin.
June 28th, 2007 at 11:05 PM
Oge: Thanks, it never gets old :)
Rafael: That’s outside what this this plugin is meant for. I don’t think this is the solution you need.
June 28th, 2007 at 11:05 PM
Could you please explain why the value column in the settings table always contains “
-”? I am trying to create scaffolding for editing the settings table and this “feature” kind of gets in the way atimes.June 28th, 2007 at 11:05 PM
It’s encoded YAML. This was done so any ruby object can be stored, like Arrays and Floats, rather than only Strings. If you are setting the field manually use some_obj.to_yaml for setting the value and YAML::load(som_obj.value) for reading the value.
And if all else fails look at the code for plugin, you should see what’s happening with YAML encoding.
June 28th, 2007 at 11:05 PM
Hi Alex,
This is possible a repeat question (Rafael), but looking at your code, and not fully understanding all of it, I wondered your opinion of someone extending it to a point where it would support user settings as well as general settings myuser.settings.autologin = ‘on’, for example.
Would I be better off starting over or extending your class?
June 28th, 2007 at 11:05 PM
You are welcome to try and extend the Settings class. Although I won’t incorporate this feature into this version of the plugin. Maybe a different version can be released called UserSettings or something, but I want to keep this plugin unbloated.
With an added user_id it shouldnt be too hard to extend. However, I’m not what the best syntax to set and retrieve settings is since sessions are only available in controllers/views and the model will have no way to know who is logged in. Perhaps you could set a class variable for the Settings object in an app wide before filter?
If you want to roll your own and want use the same Settings.some_foo syntax, study up on how method_missing works, because that is where all the magic happens.
Here’s the basic rundown of what happens when you retrieve a setting.
Not too complicated once you understand how method missing works.
June 28th, 2007 at 11:05 PM
Hi have a problem with rake migrate, it trhow: table “comments” already exists
Perhaps I don´t have some plugin not installed?
June 28th, 2007 at 11:05 PM
That doesn’t sound like it has anything to do with the settings plugin because it doesn’t make a table called comments. You clearly have something else going on. Looks like you are trying to create a table called ‘comments’ twice and the DB doesn’t like that.
June 28th, 2007 at 11:05 PM
Problem solved. I’m newbie so I didnt know about migrations. Rake migrate was trying to run scripts of table creation already existent in the db/migrate directory. I leave only the add_settings table and works fine. Thanks. (Excuse my english)
June 28th, 2007 at 11:05 PM The following changes are suggested to enable access to the created_at and updated_at values. First, the updated_at was not being maintained for me so I changed the update_all command in the self.[]= method to: (perhaps there is a better solution than this)
Next I extracted the find out of the self.[] and then reused it in two new methods (get_created_at and get_updated_at). The following four methods should replace the self.[] method.
def self.get_record(var_name) var_name = var_name.to_s if var = find(:first, :conditions => ['var = ?', var_name]) @cache[var_name] = var.value var else nil end end def self.get_created_at(var_name) (var = get_record(var_name)) ? var.created_at : nil end def self.get_updated_at(var_name) (var = get_record(var_name)) ? var.updated_at : nil end def self.[](var_name) #retrieve a setting var_name = var_name.to_s return @cache[var_name] if @cache[var_name] #return cached value (var = get_record(var_name)) ? var.value : nil endJune 28th, 2007 at 11:05 PM
Sorry, I just realized that I don’t have the latest version so some of the previous comment needs to be mapped to the new YAML versions. The basic idea is the same though.
June 28th, 2007 at 11:05 PM
I have adressed this issue in a slightly different way. Adding class level methods for getting the
created_atandupdated_atseems to break an object oriented design pattern for me.So instead, if you want other accessible attributes than just the value, you can now use the
Settings.object(var_name)method:So if you need more than just the value, simply instantiate the setting object. This will also allow you to add your own fields to setting object if you desire.
Thanks for pointing out this functionality hole.
June 28th, 2007 at 11:05 PM
I add this to my ApplicationHelper for an added feature.
Then, in my view, I have the option to specify a default if the setting does not exist: setting_for(‘quick_find_frequency’, ‘3’)
June 28th, 2007 at 11:05 PM
It seems a bit more complex that it needs to be, though. Why not just do this directly in your view?
It seems, clearer and shorter. Either way you have to specify the settings and a default.
June 28th, 2007 at 11:05 PM
defaults in environment.rb (rails 1.2.3 and rails edge) don’t work for me.
It seems plugins are loaded BEFORE environment.rb.
So I fixed it.
Here’s the patch:
(don’t know hoy to make it show correctly formatted here, sorry)
==============================Index: lib/settings.rb =================================================================== --- lib/settings.rb (revisión: 151) +++ lib/settings.rb (copia de trabajo) @@ -1,5 +1,6 @@ -class Settings < ActiveRecord::Base - @@defaults = (defined?(SettingsDefaults) ? SettingsDefaults::DEFAULTS : {}).with_indifferent_access +class Settings < ActiveRecord::Base + # This doesn't work (at least in Rails Edge r6518). + #@@defaults = (defined?(SettingsDefaults) ? SettingsDefaults::DEFAULTS : {}).with_indifferent_access class SettingNotFound < RuntimeError; end @@ -43,7 +44,9 @@ end #retrieve a setting value by [] notation - def self.[](var_name) + def self.[](var_name) + # Ensure defaults are readed. Just once. + @@defaults ||= (defined?(SettingsDefaults) ? SettingsDefaults::DEFAULTS : {}).with_indifferent_access #retrieve a setting var_name = var_name.to_sJune 28th, 2007 at 11:05 PM
Thanks Diego, I have rolled in your patch to the plugin trunk!.