Save migrations and columns by storing multiple booleans in a single integer.
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 => :stupid
end
user = User.new(foo: true, insane: true)
user.seller == true
user.stupid? == false
user.my_bits == 3
- records bitfield_changes
user.bitfield_changes == {foo: [false, true]}
(alsofoo_was
/foo_change
/foo_changed?
) - adds scopes
User.foo.stupid.first
(deactivate withbitfield ..., scopes: false
) - builds sql
User.bitfield_sql(insane: true, stupid: false) == '(users.my_bits & 3) = 1'
- builds index-using sql with
bitfield ... , query_mode: :in_list
andUser.bitfield_sql(insane: true, stupid: 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, stupid: 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][:stupid] == 4
gem install bitfields
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
Update all users
User.seller.not_stupid.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]}
- [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] afaik it is not possible to have some bits true by default (without monkeypatching AR/see tests) -> choose a good naming like
xxx_on
/xxx_off
to use the default 'false' - Never do: "#{bitfield_sql(...)} AND #{bitfield_sql(...)}", merge both into one hash
- bit_operator is faster in most cases, use query_mode: :in_list sparingly
- 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
The query_mode: :in_list
is slower for most queries and scales miserably with the number of bits.
Stay with the default query-mode. Only use :in_list if your edge-case shows better performance.
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
- convenient named scope
User.with_bitfields(xxx: true, yyy: false)
Michael Grosser
[email protected]
License: MIT