Computers, Open-source, Ruby, Software

Storing Data Using Numerical Representation

Numerical representation is a great way to store a set of boolean values in a single database column. Lets say that you have a user that has multiple roles. We want to store all of a user’s roles inside a single database column. This single-column approach may be more desirable than a separate column to track each role for the user. We can condense this data into a single column using the numerical representation technique.

If you have ever had to work with Unix permissions, you have already been exposed to numerical representation. A file in Unix can have read, write, and/or execute permissions for a user, group, or other. These permissions are represented in notation octal notation. The permissions are as follows:

  • execute: 1 (Base 2: 001)
  • read: 2 (Base 2: 010)
  • write: 4 (Base 2: 100)

If a user has a single permission on a record, then the numerical representation for that data is stored, as you would expect. “Read” would be stored as “2”. To store a combination of permissions, you sum the separate numerical representations. “Read” and “Write” would be stored as “6”. This works because each value is a power of 2, and so has an unambiguous representation. Take the position in the array, and power 2 to this integer. You will end up with the constants 0, 1, 2, 4, 8, 16, etc. Using powers of 2, bitwise operators can help us easily calculate whether a given integer is included in the sum.

So how can we represent this in Ruby? Lets take a look at our scenario from earlier. Lets represent our user model as:

# app/models/user.rb

# == Schema Info
#
# Table name: users
#
#  role                  :integer(11)
class User < ActiveRecord::Base
  # roles
  EXECUTE, READ, WRITE = 1, 2, 4
end

To keep track of the roles, I have defined them as constants, with numerical values that are powers of 2. We can create a custom setter for the role that takes a collection of these constants. These constants have a numerical value that we can sum together to get the integer to store:

# app/models/user.rb

# == Schema Info
#
# Table name: users
#
#  role                  :integer(11)
class User < ActiveRecord::Base
  # roles
  EXECUTE, READ, WRITE = 1, 2, 4

  # Example usage: role=([User::EXECUTE, User::READ])
  # Sets role column in database to sum of array
  def role=(roles)
    super(Array(roles).inject(&:+))
  end
end

In the role= method, we cast our values to an array, then we use inject to sum them all together. The super method provided by ActiveRecord is then called to set the role column in the database to this integer.

Now that our setter is complete, we can use a bitwise operation to determine if our sum contains our constant. Using a bitwise OR operator, if the sum bitwise or’ed against a constant returns the sum, then the constant is included. To express this in code would look like this:

# app/models/user.rb

# == Schema Info
#
# Table name: users
#
#  role                  :integer(11)
class User < ActiveRecord::Base
  # roles
  EXECUTE, READ, WRITE = 1, 2, 4

  # Example usage: has_role?(User::EXECUTE)
  # Returns boolean
  def has_role?(permission)
    (role | self.class.const_get(permission)) == role
  end

  # Example usage: role=([User::EXECUTE, User::READ])
  # Sets role column in database to sum of array
  def role=(roles)
    super(Array(roles).inject(&:+))
  end
end

Here is an example usage:

user = User.first #=> #
user.has_role?(User::READ) #=> false
user.role=([User::READ]) #=> 2
user.has_role?(User::READ) #=> true

There you have it: A simple and easy way to store multiple boolean values in a single column. This solution is very scalable too. As new roles are introduced to the system, just add them as a constant with the next power of 2 value. No database changes are needed to add additional columns. This calculation is even available in SQL when using PostgreSQL, and other database solutions that allow bitwise operators:

select (5 | 1) = 5; -- true
select (5 | 2) = 5; -- false
Advertisements

One thought on “Storing Data Using Numerical Representation

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s