{"id":13879942,"url":"https://github.com/grosser/bitfields","last_synced_at":"2025-04-12T19:41:49.587Z","repository":{"id":831900,"uuid":"549732","full_name":"grosser/bitfields","owner":"grosser","description":"n Booleans = 1 Integer, saves columns and migrations.","archived":false,"fork":false,"pushed_at":"2020-04-04T00:08:20.000Z","size":196,"stargazers_count":223,"open_issues_count":2,"forks_count":29,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-03T22:08:50.287Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/grosser.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-03-06T12:39:29.000Z","updated_at":"2024-10-16T05:02:32.000Z","dependencies_parsed_at":"2022-07-05T17:34:11.115Z","dependency_job_id":null,"html_url":"https://github.com/grosser/bitfields","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grosser%2Fbitfields","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grosser%2Fbitfields/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grosser%2Fbitfields/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/grosser%2Fbitfields/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/grosser","download_url":"https://codeload.github.com/grosser/bitfields/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248625075,"owners_count":21135510,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-06T08:02:40.033Z","updated_at":"2025-04-12T19:41:49.565Z","avatar_url":"https://github.com/grosser.png","language":"Ruby","readme":"Save migrations and columns by storing multiple booleans in a single integer.\u003cbr/\u003e\ne.g. true-false-false = 1, false-true-false = 2,  true-false-true = 5 (1,2,4,8,..)\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  include Bitfields\n  bitfield :my_bits, 1 =\u003e :seller, 2 =\u003e :insane, 4 =\u003e :sensible\nend\n\nuser = User.new(seller: true, insane: true)\nuser.seller # =\u003e true\nuser.sensible? # =\u003e false\nuser.my_bits # =\u003e 3\n```\n\n - records bitfield_changes `user.bitfield_changes # =\u003e {\"seller\" =\u003e [false, true], \"insane\" =\u003e [false, true]}` (also `seller_was` / `seller_change` / `seller_changed?` / `seller_became_true?` / `seller_became_false?`)\n   - Individual added methods (i.e, `seller_was`, `seller_changed?`, etc..) can be deactivated with `bitfield ..., added_instance_methods: false`\n   - **Note**: ActiveRecord 5.2 changes the behavior of `_was` and `_changed?` methods when used in the context of an `after_save` callback.\n     - ActiveRecord 5.1 will use the use the values that were _just_ changed.\n     - ActiveRecord 5.2, however, will return the current value for `_was` and `false` for `_changed?` since the previous changes have been persisted.\n - adds scopes `User.seller.sensible.first` (deactivate with `bitfield ..., scopes: false`)\n - builds sql `User.bitfield_sql(insane: true, sensible: false) # =\u003e '(users.my_bits \u0026 6) = 1'`\n - builds sql with OR condition `User.bitfield_sql({ insane: true, sensible: true }, query_mode: :bit_operator_or) # =\u003e '(users.my_bits \u0026 2) = 2 OR (users.bits \u0026 4) = 4'`\n - builds index-using sql with `bitfield ... , query_mode: :in_list` and `User.bitfield_sql(insane: true, sensible: false) # =\u003e 'users.my_bits IN (2, 3)'` (2 and 1+2) often slower than :bit_operator sql especially for high number of bits\n - builds update sql `User.set_bitfield_sql(insane: true, sensible: false) == 'my_bits = (my_bits | 6) - 4'`\n - **faster sql than any other bitfield lib** through combination of multiple bits into a single sql statement\n - gives access to bits `User.bitfields[:my_bits][:sensible] # =\u003e 4`\n - converts hash to bits `User.bitfield_bits(seller: true) # =\u003e 1`\n\nInstall\n=======\n\n```\ngem install bitfields\n```\n\n### Migration\nALWAYS set a default, bitfield queries will not work for NULL\n\n```ruby\nt.integer :my_bits, default: 0, null: false\n# OR\nadd_column :users, :my_bits, :integer, default: 0, null: false\n```\n\nInstance Methods\n================\n\n### Global Bitfield Methods\n| Method Name        | Example (`user = User.new(seller: true, insane: true`)  | Result                                                      |\n|--------------------|---------------------------------------------------------|-------------------------------------------------------------|\n| `bitfield_values`  | `user.bitfield_values`                                  | `{\"seller\" =\u003e true, \"insane\" =\u003e true, \"sensible\" =\u003e false}` |\n| `bitfield_changes` | `user.bitfield_changes`                                 | `{\"seller\" =\u003e [false, true], \"insane\" =\u003e [false, true]}`    |\n\n### Individual Bit Methods\n#### Model Getters / Setters\n| Method Name    | Example (`user = User.new`) | Result  |\n|----------------|-----------------------------|---------|\n| `#{bit_name}`  | `user.seller`               | `false` |\n| `#{bit_name}=` | `user.seller = true`        | `true`  |\n| `#{bit_name}?` | `user.seller?`              | `true`  |\n\n#### Dirty Methods:\n\nSome, not all, [`ActiveRecord::AttributeMethods::Dirty`](https://api.rubyonrails.org/v5.1.7/classes/ActiveRecord/AttributeMethods/Dirty.html) and [`ActiveModel::Dirty`](https://api.rubyonrails.org/v5.1.7/classes/ActiveModel/Dirty.html) methods can be used on each bitfield:\n\n##### Before Model Persistence\n| Method Name                        | Example (`user = User.new`)        | Result          |\n|------------------------------------|------------------------------------|-----------------|\n| `#{bit_name}_was`                  | `user.seller_was`                  | `false`         |\n| `#{bit_name}_in_database`          | `user.seller_in_database`          | `false`         |\n| `#{bit_name}_change`               | `user.seller_change`               | `[false, true]` |\n| `#{bit_name}_change_to_be_saved`   | `user.seller_change_to_be_saved`   | `[false, true]` |\n| `#{bit_name}_changed?`             | `user.seller_changed?`             | `true`          |\n| `will_save_change_to_#{bit_name}?` | `user.will_save_change_to_seller?` | `true`          |\n| `#{bit_name}_became_true?`         | `user.seller_became_true?`         | `true`          |\n| `#{bit_name}_became_false?`        | `user.seller_became_false?`        | `false`         |\n\n\n##### After Model Persistence\n| Method Name                    | Example (`user = User.create(seller: true)`)      | Result          |\n|--------------------------------|---------------------------------------------------|-----------------|\n| `#{bit_name}_before_last_save` | `user.seller_before_last_save`                    | `false`         |\n| `saved_change_to_#{bit_name}`  | `user.saved_change_to_seller`                     | `[false, true]` |\n| `saved_change_to_#{bit_name}?` | `user.saved_change_to_seller?`                    | `true`          |\n\n  - **Note**: These methods are dynamically defined for each bitfield, and function separately from the real `ActiveRecord::AttributeMethods::Dirty`/`ActiveModel::Dirty` methods. As such, generic methods (e.g. `attribute_before_last_save(:attribute)`) will not work.\n\nExamples\n========\nUpdate all users\n\n```ruby\nUser.seller.not_sensible.update_all(User.set_bitfield_sql(seller: true, insane: true))\n```\n\nDelete the shop when a user is no longer a seller\n\n```ruby\nbefore_save :delete_shop, if: -\u003e { |u| u.seller_change == [true, false] }\n```\n\nList fields and their respective values\n\n```ruby\nuser = User.new(insane: true)\nuser.bitfield_values(:my_bits) # =\u003e { seller: false, insane: true, sensible: false }\n```\n\nTIPS\n====\n - [Upgrading] in version 0.2.2 the first field(when not given as hash) used bit 2 -\u003e add a bogus field in first position\n - [Defaults for new records] set via db migration or name the bit foo_off to avoid confusion, setting via after_initialize [does not work](https://github.com/grosser/bitfields/commit/2170dc546e2c4f1187089909a80e8602631d0796) \n - It is slow to do: `#{bitfield_sql(...)} AND #{bitfield_sql(...)}`, merge both into one hash\n - bit_operator is faster in most cases, use `query_mode: :in_list` sparingly\n - Standard mysql integer is 4 byte -\u003e 32 bitfields\n - If you are lazy or bad at math you can also do `bitfields :bits, :foo, :bar, :baz`\n - If you are want more readability and reduce clutter you can do `bitfields 2**0 =\u003e :foo, 2**1 =\u003e :bar, 2**32 =\u003e :baz`\n\nQuery-mode Benchmark\n=========\nThe `query_mode: :in_list` is slower for most queries and scales miserably with the number of bits.\u003cbr/\u003e\n*Stay with the default query-mode*. Only use :in_list if your edge-case shows better performance.\n\n![performance](http://chart.apis.google.com/chart?chtt=bit-operator+vs+IN+--+with+index\u0026chd=s:CEGIKNPRUW,DEHJLOQSVX,CFHKMPSYXZ,DHJMPSVYbe,DHLPRVZbfi,FKOUZeinsx,FLQWbglqw2,HNTZfkqw19,BDEGHJLMOP,BDEGIKLNOQ,BDFGIKLNPQ,BDFGILMNPR,BDFHJKMOQR,BDFHJLMOQS,BDFHJLNPRT,BDFHJLNPRT\u0026chxt=x,y\u0026chxl=0:|100K|200K|300K|400K|500K|600K|700K|800K|900K|1000K|1:|0|1441.671ms\u0026cht=lc\u0026chs=600x500\u0026chdl=2bits+%28in%29|3bits+%28in%29|4bits+%28in%29|6bits+%28in%29|8bits+%28in%29|10bits+%28in%29|12bits+%28in%29|14bits+%28in%29|2bits+%28bit%29|3bits+%28bit%29|4bits+%28bit%29|6bits+%28bit%29|8bits+%28bit%29|10bits+%28bit%29|12bits+%28bit%29|14bits+%28bit%29\u0026chco=0000ff,0000ee,0000dd,0000cc,0000bb,0000aa,000099,000088,ff0000,ee0000,dd0000,cc0000,bb0000,aa0000,990000,880000)\n\nTesting With RSpec\n=========\n\nTo assert that a specific flag is a bitfield flag and has the `active?`, `active`, and `active=` methods and behavior use the following matcher:\n\n````ruby\nrequire 'bitfields/rspec'\n\ndescribe User do\n  it { should have_a_bitfield :active }\nend\n````\n\nTODO\n====\n - convenient named scope `User.with_bitfields(xxx: true, yyy: false)`\n\nAuthors\n=======\n### [Contributors](http://github.com/grosser/bitfields/contributors)\n - [Hellekin O. Wolf](https://github.com/hellekin)\n - [John Wilkinson](https://github.com/jcwilk)\n - [PeppyHeppy](https://github.com/peppyheppy)\n - [kmcbride](https://github.com/kmcbride)\n - [Justin Aiken](https://github.com/JustinAiken)\n - [szTheory](https://github.com/szTheory)\n - [Reed G. Law](https://github.com/reedlaw)\n - [Rael Gugelmin Cunha](https://github.com/reedlaw)\n - [Alan Wong](https://github.com/naganowl)\n - [Andrew Bates](https://github.com/a-bates)\n - [Shirish Pampoorickal](https://github.com/shirish-pampoorickal)\n - [Sergey Kojin](https://github.com/skojin)\n\n[Michael Grosser](http://grosser.it)\u003cbr/\u003e\nmichael@grosser.it\u003cbr/\u003e\nLicense: MIT\u003cbr/\u003e\n[![Build Status](https://travis-ci.org/grosser/bitfields.png)](https://travis-ci.org/grosser/bitfields)\n","funding_links":[],"categories":["Ruby","ORM/ODM Extensions"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrosser%2Fbitfields","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgrosser%2Fbitfields","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgrosser%2Fbitfields/lists"}