Sonntag, 25. Januar 2015

Bitmask the boolean attributes!

Some database tables suffer from excessive boolean attributes. Especially when most are likely to be NULL.
Merging the boolean attributes into one single bitmasked integer attribute is a solution. Then the integer value represents multiple flags (booleans) bitwise. Metaphorical spoken: several boolean attributes are stacked into the value of one integer attribute
The advantages for bitmasking boolean attributes are:
  1. much lower attribute baggage: 1 integer (usually 4 bytes) can take up to 32 boolean attributes
  2. flexible database structure (a new boolean attribute does not require a database migration)
  3. readable semantic values (the values are meaningful compared to the boolean true/ false)
  4. dynamic access to the bitmask values, which feels more Rubyish
Although there are various pros, the downsides also have to be considered. Bitmasking definitely does not fit for every use case, like most patterns. Abusing it introduces more pain than relief, because it also:
  1. reduces readability in the database layer (the stacked integer value is hiding the meaningful attribute names)
  2. introduces a (very small) Ruby layer for processing the stacked values (back and forth)
  3. means to work around attribute representations, which rely on the database data type (like simple_form gem)
The gem bitmask_attribute lightens the workload for dealing with the bitmasking logic. It provides a nice API to deal with. So instead of a migration with only a few boolean attributes:
create_table :people do |t| 
  t.string :name
  t.boolean :product_owner, null: false, default: false
  t.boolean :developer, null: false, default: false
  t.boolean :scrum_master, null: false, default: false
  t.boolean :sales, null: false, default: false
end 
the same model can be refactored to:
create_table :people do |t| 
  t.string :name
  t.integer :roles
end 
The appropriate model:
class Person < ActiveRecord::Base
  bitmask :roles, 
    as: [:scrum_master, :product_owner, :developer, :sales]
end
and by now the bitmask can be used:
Person.values_for_roles
=> [:scrum_master, :product_owner, :developer, :sales]
person = Person.create name: 'Bob', 
  roles: [:scrum_master, :developer]
person.roles
=> [:scrum_master, :developer]
person.roles << :sales
person.roles
=> [:scrum_master, :developer, :sales]
person.roles? :developer
=> true
If the bitmask has to be represented by check boxes in the Ruby on Rails view:
<% Person.values_for_roles.each do |role| %>
  <%= check_box_tag 'person[roles][]', role, 
        @person.roles.include?(role), id: role %>
  <%= label_tag role, role.to_s.humanize %>
<% end %>
generates the HTML:








Using bitmask_attribute requires to think about the pros and cons in the forefront, because migrating the production data back to boolean attributes is awkward. Furthermore, when it comes to boolean attributes representing several states (especially when the combination of boolean attributes respresent those states), a state machine should be considered.
Further articles of interest:

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.19