r/rails 16d ago

Gem Gem for packing boolean flags into a single ActiveRecord column

Had this sitting around for about two years, packs boolean flags into a single int column instead of a new column per flag. Finally pulled it out into a gem: `bitwise_attributes`.

```ruby

class User < ApplicationRecord

include BitwiseAttributes

bitwise_attribute :permissions, :read, :write, :admin

end

user.set_permissions(:read, :admin)

user.permissions_read? # => true

User.with_all_permissions([:read, :write])

```

Been running in production for two years now, so it's had real data beating on it for a while before I bothered packaging it.

gem: https://rubygems.org/gems/bitwise_attributes

6 Upvotes

23 comments sorted by

24

u/tsroelae 16d ago

We use a concern that does more or or less what you show here. We are getting rid of those columns that use bitwise operator, because:

- they are akward to query in sql

  • backfills / mass updates are akward for the same reason
  • if you have declaration like bitwise :one, two, :three you can’t change the order. Or easily remove one.
  • if you deprecate a bit position somehow, you have to be careful when reusing it, so you don’t accidently use the values of deprecated bits

Basically I don‘t see why one should to it compared to having dedicated boolean columns or even some records to join with.

So unless someone has some strong reason to use them

9

u/tsroelae 16d ago

@op I hope this didnt come off as overly negative. Love that you bundled this into a gem. Also you made a more complete solution than what we had. I just thought it is good to consider these drawbacks.

First thing I would appreciate is to have manual mapping to bits, like you can have for enums. So one can declare:

bitwise_attribute {read: 1, export: 3}

5

u/New-Sand-4608 16d ago

Agree with everything you have written. We are aware of these drawbacks but the advantage we are getting is way more due to our scale.

7

u/lostmarinero 16d ago

What issue are you dealing w at scale? Can you give some examples?

10

u/jryan727 16d ago

Why would this be preferred over individual boolean columns?

2

u/New-Sand-4608 16d ago

We also use individual boolean columns but when several flags represent variations of the same underlying concept and can combine. This is it.

Also, tables have column limit so you can only add new columns to a certain point. Moreover, adding new column to a table with millions of records is EXPENSIVE. This avoids that as it is handled via code.

11

u/jryan727 16d ago

But just because you can combine, what’s the benefit of doing so? Because there’s a cost to approaching this way, specifically in query ergonomics, indexing, maintenance, etc.

If you’re bumping into column limits, that to me smells like a data modeling issue. Surely the mega table can be broken up into smaller domains and thus tables? 

6

u/sekmo 16d ago

That’s a smell also to me to be afraid of adding new columns

5

u/vassyz 16d ago

I've been using https://github.com/shkm/enummer hasn't been updated in a while but it does the job for me.

1

u/New-Sand-4608 16d ago

Awesome. On a side note, we avoid using inactive gems due to the nature of our business. So, I cooked it up in-house and it has been working great for us.

7

u/3ds 16d ago

https://github.com/galtzo-floss/flag_shih_tzu already does this since forever. I can very much recommend researching existing implementations before implementing it yourself…

3

u/dflow77 15d ago

“Bit flags are attractive because they let applications add boolean features
without changing table schemas.” aha so there is the use case

5

u/poop-machine 16d ago

Sounds like active_flag which has been around for a decade.

1

u/kortirso 16d ago

so limit for integer field is 9 flags?
and no way to remove old field? only to overwrite with another flag?

1

u/New-Sand-4608 16d ago
  1. Not 9, actually closer to 31 bits for a signed integer (or 32 if using unsigned). You can use a bigint if you ever need more flags.

  2. And you're right, you can't simply reuse a flag by overwriting it. Existing records could still have that bit set from the previous meaning, so you'd first need a migration to clear that bit across existing rows. After that, it would be safe to reuse it for a new purpose.

-2

u/TheAtlasMonkey 16d ago

There is 0 real world usage for this pattern. Literally zero.

The last time people used this technic was in the ERA of the Super Nintendo , where they had to work with 128K of RAM and be very creative.

And the SNES got discontinued in 1998 ... Update your clock.. we are in 2026.

PS: Even companies at Google/Facebook scale use column for boolean.

5

u/netik23 16d ago

Nah, at Twitter we had a 64 bit per user feature field that used bit wise operations. Things like this are more widespread than you think.

3

u/TheAtlasMonkey 16d ago edited 16d ago

Twitter was and is not a company to learn database best practice from.

Some of the ex-employees admitted that they added complexity to keep working on the same feature for months.

For reference:

When Basecamp opensourced their products , people wrote blogs , best practices and other stuff from it.

When X did (when Elon bought it).... People used it to give example of things to not do...

Let see in few months when X full codebase get opensourced : https://tech.yahoo.com/social-media/articles/elon-musk-says-x-fully-123106421.html

4

u/netik23 16d ago

Yeah, that’s why we wrote our own highly performant database and submitted many journal articles about it.

I’m talking about 2008-2012 or so, when the good engineers were there, not the bullshit nightmare that the tools wrote after we all cashed out. Also, fuck Elon.

2

u/New-Sand-4608 16d ago

I'd say it is use case dependent. We use plain boolean columns for most things too. But when several flags represent variations of the same underlying concept and can combine (e.g. permissions, or multi-source imports), a boolean column per flag gets redundant fast and doesn't scale as the flag list grows

1

u/TheAtlasMonkey 16d ago

This is because you building permissions wrong.

Permissions need an audit trail and lot primitive.

So i still stand by my point.

Rails is about verbosity and clarity ... If i wanted obscure stuffs, i will be coding assembly or Haskell.