Bitfields
n Booleans = 1 Integer, saves columns and migrations.
Install / Use
/learn @grosser/BitfieldsREADME
Save migrations and columns by storing multiple booleans in a single integer.<br/> e.g. true-false-false = 1, false-true-false = 2, true-false-true = 5 (1,2,4,8,..)
class User < ActiveRecord::Base
include Bitfields
bitfield :my_bits, 1 => :seller, 2 => :insane, 4 => :sensible
end
user = User.new(seller: true, insane: true)
user.seller # => true
user.sensible? # => false
user.my_bits # => 3
- records bitfield_changes
user.bitfield_changes # => {"seller" => [false, true], "insane" => [false, true]}(alsoseller_was/seller_change/seller_changed?/seller_became_true?/seller_became_false?)- Individual added methods (i.e,
seller_was,seller_changed?, etc..) can be deactivated withbitfield ..., added_instance_methods: false - Note: ActiveRecord 5.2 changes the behavior of
_wasand_changed?methods when used in the context of anafter_savecallback.- ActiveRecord 5.1 will use the use the values that were just changed.
- ActiveRecord 5.2, however, will return the current value for
_wasandfalsefor_changed?since the previous changes have been persisted.
- Individual added methods (i.e,
- adds scopes
User.seller.sensible.first(deactivate withbitfield ..., scopes: false) - builds sql
User.bitfield_sql(insane: true, sensible: false) # => '(users.my_bits & 6) = 1' - builds sql with OR condition
User.bitfield_sql({ insane: true, sensible: true }, query_mode: :bit_operator_or) # => '(users.my_bits & 2) = 2 OR (users.bits & 4) = 4' - builds index-using sql with
bitfield ... , query_mode: :in_listandUser.bitfield_sql(insane: true, sensible: false) # => 'users.my_bits IN (2, 3)'(2 and 1+2) often slower than :bit_operator sql especially for high number of bits - builds update sql
User.set_bitfield_sql(insane: true, sensible: false) == 'my_bits = (my_bits | 6) - 4' - faster sql than any other bitfield lib through combination of multiple bits into a single sql statement
- gives access to bits
User.bitfields[:my_bits][:sensible] # => 4 - converts hash to bits
User.bitfield_bits(seller: true) # => 1
Install
gem install bitfields
Migration
ALWAYS set a default, bitfield queries will not work for NULL
t.integer :my_bits, default: 0, null: false
# OR
add_column :users, :my_bits, :integer, default: 0, null: false
Instance Methods
Global Bitfield Methods
| Method Name | Example (user = User.new(seller: true, insane: true) | Result |
|--------------------|---------------------------------------------------------|-------------------------------------------------------------|
| bitfield_values | user.bitfield_values | {"seller" => true, "insane" => true, "sensible" => false} |
| bitfield_changes | user.bitfield_changes | {"seller" => [false, true], "insane" => [false, true]} |
Individual Bit Methods
Model Getters / Setters
| Method Name | Example (user = User.new) | Result |
|----------------|-----------------------------|---------|
| #{bit_name} | user.seller | false |
| #{bit_name}= | user.seller = true | true |
| #{bit_name}? | user.seller? | true |
Dirty Methods:
Some, not all, ActiveRecord::AttributeMethods::Dirty and ActiveModel::Dirty methods can be used on each bitfield:
Before Model Persistence
| Method Name | Example (user = User.new) | Result |
|------------------------------------|------------------------------------|-----------------|
| #{bit_name}_was | user.seller_was | false |
| #{bit_name}_in_database | user.seller_in_database | false |
| #{bit_name}_change | user.seller_change | [false, true] |
| #{bit_name}_change_to_be_saved | user.seller_change_to_be_saved | [false, true] |
| #{bit_name}_changed? | user.seller_changed? | true |
| will_save_change_to_#{bit_name}? | user.will_save_change_to_seller? | true |
| #{bit_name}_became_true? | user.seller_became_true? | true |
| #{bit_name}_became_false? | user.seller_became_false? | false |
After Model Persistence
| Method Name | Example (user = User.create(seller: true)) | Result |
|--------------------------------|---------------------------------------------------|-----------------|
| #{bit_name}_before_last_save | user.seller_before_last_save | false |
| saved_change_to_#{bit_name} | user.saved_change_to_seller | [false, true] |
| saved_change_to_#{bit_name}? | user.saved_change_to_seller? | true |
- Note: These methods are dynamically defined for each bitfield, and function separately from the real
ActiveRecord::AttributeMethods::Dirty/ActiveModel::Dirtymethods. As such, generic methods (e.g.attribute_before_last_save(:attribute)) will not work.
Examples
Update all users
User.seller.not_sensible.update_all(User.set_bitfield_sql(seller: true, insane: true))
Delete the shop when a user is no longer a seller
before_save :delete_shop, if: -> { |u| u.seller_change == [true, false] }
List fields and their respective values
user = User.new(insane: true)
user.bitfield_values(:my_bits) # => { seller: false, insane: true, sensible: false }
TIPS
- [Upgrading] in version 0.2.2 the first field(when not given as hash) used bit 2 -> add a bogus field in first position
- [Defaults for new records] set via db migration or name the bit foo_off to avoid confusion, setting via after_initialize does not work
- It is slow to do:
#{bitfield_sql(...)} AND #{bitfield_sql(...)}, merge both into one hash - bit_operator is faster in most cases, use
query_mode: :in_listsparingly - Standard mysql integer is 4 byte -> 32 bitfields
- If you are lazy or bad at math you can also do
bitfields :bits, :foo, :bar, :baz - If you are want more readability and reduce clutter you can do
bitfields 2**0 => :foo, 2**1 => :bar, 2**32 => :baz
Query-mode Benchmark
The query_mode: :in_list is slower for most queries and scales miserably with the number of bits.<br/>
Stay with the default query-mode. Only use :in_list if your edge-case shows better performance.
Testing With RSpec
To assert that a specific flag is a bitfield flag and has the active?, active, and active= methods and behavior use the following matcher:
require 'bitfields/rspec'
describe User do
it { should have_a_bitfield :active }
end
TODO
- convenient named scope
User.with_bitfields(xxx: true, yyy: false)
Authors
Contributors
- Hellekin O. Wolf
- John Wilkinson
- PeppyHeppy
- kmcbride
- Justin Aiken
- szTheory
- Reed G. Law
- Rael Gugelmin Cunha
- Alan Wong
- Andrew Bates
- Shirish Pampoorickal
- Sergey Kojin
Michael Grosser<br/>
michael@grosser.it<br/>
License: MIT<br/>

Related Skills
node-connect
339.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.8kCommit, push, and open a PR
