Simple bitmask attribute support for ActiveRecord 3+
Transparent manipulation of bitmask attributes for ActiveRecord, based on the bitmask-attribute gem, which has been dormant since 2009. This updated gem work with Rails 3 and up (including Rails 3.1).
The best way to install is with RubyGems:
$ [sudo] gem install bitmask_attributes
Or better still, just add it to your Gemfile:
gem 'bitmask_attributes'
Simply declare an existing integer column as a bitmask with its possible values.
class User < ActiveRecord::Base
bitmask :roles, :as => [:writer, :publisher, :editor, :proofreader]
end
You can then modify the column using the declared values without resorting to manual bitmasks.
user = User.create(:name => "Bruce", :roles => [:publisher, :editor])
user.roles
# => [:publisher, :editor]
user.roles << :writer
user.roles
# => [:publisher, :editor, :writer]
It's easy to find out if a record has a given value:
user.roles?(:editor)
# => true
You can check for multiple values (uses an and
boolean):
user.roles?(:editor, :publisher)
# => true
user.roles?(:editor, :proofreader)
# => false
Or, just check if any values are present:
user.roles?
# => true
You can get the list of values for any given attribute:
User.values_for_roles
# => [:writer, :publisher, :editor, :proofreader]
A couple useful named scopes are also generated when you use
bitmask
:
User.with_roles
# => (all users with roles)
User.with_roles(:editor)
# => (all editors)
User.with_roles(:editor, :writer)
# => (all users who are BOTH editors and writers)
User.with_any_roles(:editor, :writer)
# => (all users who are editors OR writers)
User.with_exact_roles(:writer)
# => (all users who are ONLY writers)
User.with_exact_roles(:writer, :editor)
# => (all users who are BOTH editors and writers and nothing else)
Find records without any bitmask set:
User.without_roles
# => (all users without a role)
User.no_roles
# => (all users without a role)
Find records without specific attributes:
User.without_roles(:editor)
# => (all users who are not editors)
User.without_roles(:writer, :editor)
# => (all users who are NEITHER writers nor editors)
Note that "without" supports one or more attribute arguments, and the "no" method does not support arguments. And "withexact" without arguments is alias for "no_"
You can add your own methods to the bitmasked attributes (similar to named scopes):
bitmask :other_attribute, :as => [:value1, :value2] do
def worked?
true
end
end
user = User.first
user.other_attribute.worked?
# => true
By default, bitmasks support the potential for the underlying integer value to be null. However, if you have created a field that is guaranteed never to be null, you can simplify the SQL query conditions by declaring ":null => false" in the definition:
bitmask :never_null_attributes,:as => [:value1, :value2], :null => false
It is common to use web forms to set bitmask bits using checkboxes. If the various bits each are represented by a checkbox and the user unchecks them all, the resulting "params" posted to the controller will be missing. When this happens, a controller will need to ensure that a "params" hash entry has an empty array or a call to "update_attributes" will not change the attribute. For example:
In model...
class SomeModel < ActiveRecord::Base
bitmask :some_attribute, :as => [:value1, :value2]
end
In view...
<input type="checkbox" name="some_model[some_attribute][]" value="value1"/>
<input type="checkbox" name="some_model[some_attribute][]" value="value2"/>
In controller...
def update
@some_model = SomeModel.find(params[:id])
params[:some_attribute] ||= []
@some_model.update_attributes(params)
end
As an alternative, you may provide a special symbol representing "zero":
In model...
class SomeModel < ActiveRecord::Base
bitmask :some_attribute, :as => [:value1, :value2], :zero_value => :none
end
In view...
<input type="checkbox" name="some_model[some_attribute][]" value="value1"/>
<input type="checkbox" name="some_model[some_attribute][]" value="value2"/>
<input type="hidden" name="some_model[some_attribute][]" value="none"/>
In controller...
def update
@some_model = SomeModel.find(params[:id])
@some_model.update_attributes(params)
end
This technique can be particularly useful for both forms and web services where setting the attribute in question may be optionally included or not such that the controller setting of an empty array in the first example would not be correct.
IMPORTANT: Once you have data using a bitmask, don't change the order
of the values, remove any values, or insert any new values in the :as
array anywhere except at the end. You won't like the results.
git checkout -b new-feature
)bundle install
then bundle exec rake
)git commit -am "Created new feature"
)git push origin new-feature
)Thanks to Bruce Williams and the following contributors of the bitmask-attribute plugin:
Copyright (c) 2007-2009 Bruce Williams & 2011-2012 Joel Moss. See LICENSE for details.