PreferenceFu
Even though 37signals discourages letting users set too many preferences, and instead advocates creating very opinionated software, let’s face it - a lot of applications need to be tailored to the end user and restrict or enable certain functionality based on an individuals rights and/or preferences. Most of the time, this is done with boolean variables. I’m sure you’ve seen @user.admin? before, but what if you want to go further than just a couple of boolean fields and develop a lightweight ACL or just have a number of yes/no’s setup for a user?
After seeing some tables in my database get chocked full of tinyint columns, I decided to port over some functionality I’d written for an ACL system and abstract it into an easy to use and implement ActiveRecord plugin. I looked over the efforts of Jim Morris in using composed_of to achieve this, but due to the immutability of the composed_of value object, I decided to ditch that approach.
So by combining my experience with the ACL I developed and Jim’s suggestions, I wrote PreferenceFu to alleviate the need of creating migrations whenever I needed a new boolean column.
class User < ActiveRecord::Base
has_preferences :send_email, :change_theme, :delete_user, :create_user
end
All that’s required to give me those 4 boolean fields is one integer column. Now I can scale horizontally and add more boolean columns without the need for additional fields in the table.
Setting privileges:
@user.prefs[:delete_user] = true
Getting an array of checkboxes to work with PreferenceFu:
@user.prefs = params[:preferences]
To use:
if @user.prefs[:delete_user]
...
Find it on Github
Brennan Falkner said
Mar 24, 2008 @ 11:28 PM
Oh my, another Brennan. I guess I may have to start using my full name in comments. :( Your seem to be using this for authorization, but gearing it for preferences. Seems like you could create something a bit more geared for that if it's what you really want.weepy said
Mar 25, 2008 @ 04:57 AM
Interesting idea. It's useful as long as u don't want to query on the preferencesBrennan said
Mar 25, 2008 @ 09:14 AM
@weepy You should be able to use bitwise and in the find conditions to query based on preferences. User.find(:all, :conditions => ["(preferences_col & ?) != 0", preference_id]Mark Wilden said
Mar 25, 2008 @ 10:49 AM
This looks great, but I don't think it's about preferences, per se. On the one hand, it doesn't help with nonboolean preferences; on the other hand, mapping ActiveRecord attributes to bitfields could be very useful in other areas. ///arkjro said
Mar 25, 2008 @ 11:12 AM
I like the simplicity of this, but it seems like you'd have to be very careful not to remove or re-order any of the elements in your declaration. I wonder if forcing yourself to declare the bit index would help make that more obvious, since I can't think of a good way to avoid it altogether.Brennan said
Mar 25, 2008 @ 11:57 AM
@Mark: You're right, this really be used for anything that requires a boolean field, and not necessarily for any one concern (i.e., preferences, ACL, etc.). @Jro: The main reason I didn't using a hash to list out potential fields (which would have let me easily list out the default values of each as key/value pairs was due to the lack of order in 1.8's hashes. I was thinking I might want to dump a YAML file and cross check against that upon compilation to handle additions/removals.Alex said
Mar 27, 2008 @ 10:15 PM
you should expand this to save more than just booleans.Daniel Higginbotham said
Mar 31, 2008 @ 09:54 AM
If you're using mysql and you want to just have boolean preferences, it would probably be wiser to use an ENUM field. There's a great Rails plugin for that. I'm sure other db's have something similar.Daniel Higginbotham said
Mar 31, 2008 @ 09:54 AM
If you're using mysql and you want to just have boolean preferences, it would probably be wiser to use an ENUM field. There's a great Rails plugin for that. I'm sure other db's have something similar.Martin Karlsch said
Apr 08, 2008 @ 06:42 PM
Nice work! Really helps with endless boolean values. @Daniel H. .. you would need an extra enum column for each boolean property. I think mysql enums are really good for enumerations like status = open,running,finished,closed but for boolean properties I would choose preference_fu ;-) @Brennan .. found one major bug inside your current implementation (and fixed it :-) I would have cloned your repo on github but sadly I do not have an account yet. You are assigning a value to @@preference_options inside the has_preferences call. (And you included the method in ActiveRecord::Base. For that reason any model which derives from AR:Base shares @@preference_options ... baaang ... Simple fix: def has_preferences(*options) .. snip .. class_eval do class << self alias_method_chain :instantiate, :preferences attr_accessor :preference_options end end config = { :column => 'preferences' } idx = 0; self.preference_options = {} options.each do |pref| self.preference_options[2**idx] = { :key => pref.to_sym, :default => false } idx += 1 end snip .. endMartin Karlsch said
Apr 08, 2008 @ 06:44 PM
ahhh .. formating got lost .. sorryuvnkrndi said
Apr 24, 2008 @ 06:20 AM
[URL=http://jdhfsycz.com]gduohjnv[/URL] vxbjwphz oobogypn http://wdasnuaf.com qpsrueye wpanwokmRSS feed for comments on this post
Leave a Comment