Bit Vector Preferences
Posted by Jim Morris on Tue Aug 07 23:53:09 -0700 2007
In my latest web project I potentially have a lot of boolean preferences, which I use for enabling or disabling various email notifications to users.
Rather than having to add a migration everytime I want to add a new
preference, I thought I would use the composed_of
feature in my model
and compose the boolean preferences from a bitvector. That way I can
simply modify my model to add new preferences rather than add new
columns to the database.
I also wanted this to be easy to add new boolean preferences, so I use some Macros (I guess you could also call it Meta-Programming) to do all the repetitive code.
The result is a little class in my Person model called Preferences, one integer field in my persons database called preferences, and a composed_of :preferences in the Person model, and of course the following class in the person.rb model.
All I need to do to add new preferences is add it to the @@bits
class variable, which is a Hash of the preference name as a symbol and
the bit it sets in the integer (actually the value of the bit, bit0 is
1, bit1 is 2 etc). The rest of the code is derived from the class
variable.
To make things easier I also add a predicate for each preference, so I
can access @person.preferences.comment_notifications?
to see if any
comment notifications are required for instance.
One other thing I do in the initialize method is set up defaults for
the preferences. This is only really needed if it is being added as an
after thought, and the column preferences
is NULL in the database.
Because composed_of
classes are immutable you must always create a
whole new one to update them, so I also allow initialize to be called
with a Hash, which can come straight from the controller. The last
case of initialize is being passed the integer from the database,
expanding it into the various boolean instance variables. The
preferences
method does the reverse and converts the boolean
instance variables into the bit vector. Calls to these are all taken
care of by ActiveRecord.
An example of it being called from the controller is...
@person.preferences= Person::Preferences.new(params[:preferences])
presuming you have a bunch of check boxes in your view which are passed in as part of the preferences hash.
Because I added this later I had one migration to initially add the new column...
add_column :people, :preferences, :integer
Thanks, I was looking exactly for this functionality and it required very little effort to put it in use.
Cool. You should mention the max amount of prefs that can be stored by this feature due to the length of an integer.