<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Wolfmans Howlings: Bit Vector Preferences</title>
    <link>http://blog.wolfman.com/articles/2007/08/07/bit-vector-preferences</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description>A programmers Blog about Ruby, Rails and a few other issues</description>
    <item>
      <title>Bit Vector Preferences</title>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Rather than having to add a migration everytime I want to add a new
preference, I thought I would use the &lt;code&gt;composed_of&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;div class="typocode"&gt;&lt;pre&gt;&lt;code class="typocode_ruby "&gt;&lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Person&lt;/span&gt; &lt;span class="punct"&gt;&amp;lt;&lt;/span&gt; &lt;span class="constant"&gt;ActiveRecord&lt;/span&gt;&lt;span class="punct"&gt;::&lt;/span&gt;&lt;span class="constant"&gt;Base&lt;/span&gt;

  &lt;span class="comment"&gt;#...&lt;/span&gt;

  &lt;span class="comment"&gt;# place to store bit vector preferences&lt;/span&gt;
  &lt;span class="comment"&gt;# to add a new preference:-&lt;/span&gt;
  &lt;span class="comment"&gt;#   add symbol of preference to @@bits with bit allocation&lt;/span&gt;
  &lt;span class="comment"&gt;#   update initialize defaults if initial default is true&lt;/span&gt;
  &lt;span class="keyword"&gt;class &lt;/span&gt;&lt;span class="class"&gt;Preferences&lt;/span&gt;
    &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="punct"&gt;{&lt;/span&gt;
      &lt;span class="symbol"&gt;:comment_notifications&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="number"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;
      &lt;span class="symbol"&gt;:friendship_notifications&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="number"&gt;2&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;
      &lt;span class="symbol"&gt;:event_notifications&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="number"&gt;4&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;
      &lt;span class="symbol"&gt;:misc_notifications&lt;/span&gt; &lt;span class="punct"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="number"&gt;8&lt;/span&gt; &lt;span class="punct"&gt;}&lt;/span&gt;

    &lt;span class="comment"&gt;# create a reader for each preference&lt;/span&gt;
    &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each_key&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
      &lt;span class="ident"&gt;attr_reader&lt;/span&gt; &lt;span class="ident"&gt;a&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="comment"&gt;# Initialize from integer or Hash&lt;/span&gt;
    &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;initialize&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;prefs&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
      &lt;span class="keyword"&gt;if&lt;/span&gt; &lt;span class="ident"&gt;prefs&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;nil?&lt;/span&gt;
        &lt;span class="comment"&gt;# set the defaults to false if not been set before&lt;/span&gt;
        &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
          &lt;span class="ident"&gt;instance_variable_set&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;@&lt;span class="expr"&gt;#{a}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="constant"&gt;false&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;  
        &lt;span class="keyword"&gt;end&lt;/span&gt;
        &lt;span class="comment"&gt;# override default here        &lt;/span&gt;
        &lt;span class="attribute"&gt;@comment_notifications&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;true&lt;/span&gt;
        &lt;span class="attribute"&gt;@event_notifications&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="constant"&gt;true&lt;/span&gt;
      &lt;span class="keyword"&gt;elsif&lt;/span&gt; &lt;span class="ident"&gt;prefs&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;is_a?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;Hash&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
        &lt;span class="comment"&gt;# initialize from parameter Hash, and default to false if absent from hash&lt;/span&gt;
        &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
          &lt;span class="ident"&gt;instance_variable_set&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;@&lt;span class="expr"&gt;#{a}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="constant"&gt;false&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;  
        &lt;span class="keyword"&gt;end&lt;/span&gt;

        &lt;span class="ident"&gt;prefs&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;k&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt;&lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
          &lt;span class="keyword"&gt;raise&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="constant"&gt;ArgumentError&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="punct"&gt;&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;Unknown preference &lt;span class="expr"&gt;#{k}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt; &lt;span class="keyword"&gt;unless&lt;/span&gt; &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;has_key?&lt;/span&gt;&lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;k&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_sym&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
          &lt;span class="ident"&gt;instance_variable_set&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;@&lt;span class="expr"&gt;#{k}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="constant"&gt;true&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="keyword"&gt;if&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt; &lt;span class="punct"&gt;==&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;1&lt;/span&gt;&lt;span class="punct"&gt;'&lt;/span&gt;
        &lt;span class="keyword"&gt;end&lt;/span&gt;
      &lt;span class="keyword"&gt;else&lt;/span&gt;
        &lt;span class="comment"&gt;# create from integer bit vector&lt;/span&gt;
        &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
          &lt;span class="ident"&gt;instance_variable_set&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;@&lt;span class="expr"&gt;#{a}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;,&lt;/span&gt; &lt;span class="punct"&gt;(&lt;/span&gt;&lt;span class="ident"&gt;prefs&lt;/span&gt; &lt;span class="punct"&gt;&amp;amp;&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt; &lt;span class="punct"&gt;!=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span class="constant"&gt;true&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="constant"&gt;false&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;  
        &lt;span class="keyword"&gt;end&lt;/span&gt;
      &lt;span class="keyword"&gt;end&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="comment"&gt;# returns bit vector of preferences&lt;/span&gt;
    &lt;span class="keyword"&gt;def &lt;/span&gt;&lt;span class="method"&gt;preferences&lt;/span&gt;
      &lt;span class="ident"&gt;bv&lt;/span&gt;&lt;span class="punct"&gt;=&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;
      &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
        &lt;span class="ident"&gt;bv&lt;/span&gt; &lt;span class="punct"&gt;|=&lt;/span&gt; &lt;span class="ident"&gt;instance_variable_get&lt;/span&gt;&lt;span class="punct"&gt;(&amp;quot;&lt;/span&gt;&lt;span class="string"&gt;@&lt;span class="expr"&gt;#{a}&lt;/span&gt;&lt;/span&gt;&lt;span class="punct"&gt;&amp;quot;)&lt;/span&gt; &lt;span class="punct"&gt;?&lt;/span&gt; &lt;span class="ident"&gt;v&lt;/span&gt; &lt;span class="punct"&gt;:&lt;/span&gt; &lt;span class="number"&gt;0&lt;/span&gt;  
      &lt;span class="keyword"&gt;end&lt;/span&gt;
      &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="ident"&gt;bv&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;

    &lt;span class="comment"&gt;# create a predicate for each preference&lt;/span&gt;
    &lt;span class="attribute"&gt;@@bits&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;each_key&lt;/span&gt; &lt;span class="keyword"&gt;do&lt;/span&gt; &lt;span class="punct"&gt;|&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;|&lt;/span&gt;
      &lt;span class="ident"&gt;alias_method&lt;/span&gt;&lt;span class="punct"&gt;((&lt;/span&gt;&lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;.&lt;/span&gt;&lt;span class="ident"&gt;to_s&lt;/span&gt; &lt;span class="punct"&gt;+&lt;/span&gt; &lt;span class="punct"&gt;'&lt;/span&gt;&lt;span class="string"&gt;?&lt;/span&gt;&lt;span class="punct"&gt;').&lt;/span&gt;&lt;span class="ident"&gt;to_sym&lt;/span&gt;&lt;span class="punct"&gt;,&lt;/span&gt; &lt;span class="ident"&gt;a&lt;/span&gt;&lt;span class="punct"&gt;)&lt;/span&gt;
    &lt;span class="keyword"&gt;end&lt;/span&gt;
  &lt;span class="keyword"&gt;end&lt;/span&gt;

  &lt;span class="comment"&gt;# access preferences as bit vector&lt;/span&gt;
  &lt;span class="ident"&gt;composed_of&lt;/span&gt; &lt;span class="symbol"&gt;:preferences&lt;/span&gt;

  &lt;span class="comment"&gt;#...&lt;/span&gt;

&lt;span class="keyword"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All I need to do to add new preferences is add it to the &lt;code&gt;@@bits&lt;/code&gt;
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.&lt;/p&gt;

&lt;p&gt;To make things easier I also add a predicate for each preference, so I
can access &lt;code&gt;@person.preferences.comment_notifications?&lt;/code&gt; to see if any
comment notifications are required for instance.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;preferences&lt;/code&gt; is NULL in the database.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;composed_of&lt;/code&gt; 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
&lt;code&gt;preferences&lt;/code&gt; method does the reverse and converts the boolean
instance variables into the bit vector. Calls to these are all taken
care of by ActiveRecord.&lt;/p&gt;

&lt;p&gt;An example of it being called from the controller is...&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;@person.preferences= Person::Preferences.new(params[:preferences])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;presuming you have a bunch of check boxes in your view which are
passed in as part of the preferences hash.&lt;/p&gt;

&lt;p&gt;Because I added this later I had one migration to initially add the
new column...&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;add_column :people, :preferences, :integer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href="http://technorati.com/tag/composed_of" rel="tag"&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <pubDate>Tue, 07 Aug 2007 23:53:09 -0700</pubDate>
      <guid isPermaLink="false">urn:uuid:4fb543c1-0bf5-4881-9fc5-aabe2d0789c2</guid>
      <author>Jim Morris</author>
      <link>http://blog.wolfman.com/articles/2007/08/07/bit-vector-preferences</link>
      <category>Rails</category>
      <category>rails</category>
      <category>preferences</category>
      <category>composed_of</category>
      <category>bitvector</category>
      <trackback:ping>http://blog.wolfman.com/articles/trackback/338</trackback:ping>
    </item>
    <item>
      <title>"Bit Vector Preferences" by Alex</title>
      <description>&lt;p&gt;Cool. You should mention the max amount of prefs that can be stored by this feature due to the length of an integer.&lt;/p&gt;</description>
      <pubDate>Fri, 05 Sep 2008 00:52:19 -0700</pubDate>
      <guid isPermaLink="false">urn:uuid:155e836d-3f09-4866-ac43-98d679e5c61a</guid>
      <link>http://blog.wolfman.com/articles/2007/08/07/bit-vector-preferences#comment-257</link>
    </item>
    <item>
      <title>"Bit Vector Preferences" by Uri Lewin</title>
      <description>&lt;p&gt;Thanks, I was looking exactly for this functionality and it required very little effort to put it in use.&lt;/p&gt;</description>
      <pubDate>Thu, 05 Jun 2008 13:04:21 -0700</pubDate>
      <guid isPermaLink="false">urn:uuid:6918a7dd-4a68-45d0-9b4e-62e5af8e9ae3</guid>
      <link>http://blog.wolfman.com/articles/2007/08/07/bit-vector-preferences#comment-219</link>
    </item>
  </channel>
</rss>
