activerecord-bitwise 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +2 -0
- data/BUGS.md +67 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +347 -0
- data/REQUIREMENTS.md +16 -0
- data/certs/activerecord-bitwise-public_cert.pem +25 -0
- data/lib/active_record/bitwise/bitwise_validator.rb +26 -0
- data/lib/active_record/bitwise/version.rb +9 -0
- data/lib/active_record/bitwise.rb +711 -0
- data/lib/activerecord-bitwise.rb +3 -0
- data/lib/tapioca/dsl/compilers/activerecord_bitwise.rb +207 -0
- data.tar.gz.sig +0 -0
- metadata +278 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f2db5d69a1805305617f0bced7ced57dcd3b959142ead14fc5fb9e80fb9c3364
|
|
4
|
+
data.tar.gz: dd16bc01efa39ecef84a7e471d727101d0a45894473e6ae804210716f4ba1f54
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c6c31868a2d2c846251a122c15c06125ea3b8643d7ff1d51abd2cd3d4a0abf0fca576ac46d714d45ab7ebd095e3959b47d9969146b2c9e372ae3508a8da6d602
|
|
7
|
+
data.tar.gz: 0b42006caffe45f55027c47a6e531e51f4a4987f69dde6540cbc9eb7b964ee897373bd878782b10104904a3d950d4f1e6892539ca0f5bf5111e47302c9e69fda
|
checksums.yaml.gz.sig
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
s�]��q�į�9'��i���5e�_�@ξ1�v2sO2�ơJM��n=I����TB�)�Y�����m�
|
|
2
|
+
�\��ׅw���Ĥ`���噫�+�1+vz�NH��{Z4�k��w����c�Aun[�M����-mf��0�[��1��WX�_�!/Q[���y��g~F�jO�0��?��xz����RA��O�{Sі�M���lS\�am�&����e����Fb��wE��L�o�,��%�Y�Z��n�*�n4EF�&:�D�8n\�︨I�iw/��ѣ��A8--��'�H�|�zo���A�c�B��I@�v!���k��C�I���~-F�L�S�%���X�"3�oXz� E/G�_ä
|
data/BUGS.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Active Defects
|
|
2
|
+
|
|
3
|
+
No active defects! All documented issues have been successfully resolved and exhaustively verified via type checking, linting, and 100.0% line and branch test coverage.
|
|
4
|
+
|
|
5
|
+
## Resolved Defects
|
|
6
|
+
|
|
7
|
+
| ID | Related REQ | Description | Resolution |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| BUG-SORBET-001 | AR_BITWISE-REQ-002 | Requiring the gem (`require 'activerecord-bitwise'`) in an external application or standalone script without manually requiring `sorbet-runtime` beforehand crashes with `NameError: uninitialized constant BitwiseValidator::T` because `lib/active_record/bitwise/bitwise_validator.rb` is loaded before `sorbet-runtime` is required, and does not require it itself. | **Fixed:** Explicitly required `sorbet-runtime` in the gem's entry points (`lib/active_record/bitwise.rb` and `lib/active_record/bitwise/bitwise_validator.rb`). |
|
|
10
|
+
| BUG-THREAD-001 | AR_BITWISE-REQ-005 | Instance-level atomic methods (`add_role!`, `remove_role!`) read back the DB value via `pluck` after the class-level `update_all`, then write it into the instance's attribute cache. Between the `update_all` and the `pluck`, another concurrent thread could modify the same row, causing the in-memory state to diverge from the actual database state. | **Fixed:** Wrapped atomic modifications in transaction blocks with explicit ActiveRecord `.lock(true)` on `pluck` read-back. |
|
|
11
|
+
| BUG-BANG-001 | AR_BITWISE-REQ-001 | The `method_name!` (bang) methods use `save!` which is **not** atomic — it performs a full ActiveRecord save cycle. If a validation on another attribute fails, the bang method will raise `ActiveRecord::RecordInvalid`. | **Mitigated:** Documented non-atomic save cycle behavior in YARD `@note` documentation. |
|
|
12
|
+
| BUG-IVAR-001 | AR_BITWISE-REQ-007 | The `@_bitwise_raw_value` instance variable is set directly on Ruby Array objects via `instance_variable_set`. This is a fragile pattern that pollutes core Ruby objects with gem-specific state. | **Mitigated:** Documented as a known architectural limitation with high regression risk for refactoring. |
|
|
13
|
+
| BUG-READONLY-001 | AR_BITWISE-REQ-001 | The `where` poisoning defense (`check_bitwise_query!`) does not catch Arel attribute node queries. A developer could bypass the guard via `User.where(User.arel_table[:roles].eq(1))`. | **Mitigated:** Documented Arel-based bypass guard behavior in YARD `@note` documentation. |
|
|
14
|
+
| BUG-SQL-004 | AR_BITWISE-REQ-005 | `remove_<column>!` uses `& ~?` which is not portable across DB adapters (MySQL/SQLite). | **Fixed:** Uses database-portable safe SQL subtraction arithmetic `(COALESCE(col, 0) - (COALESCE(col, 0) & ?))` instead of `& ~?`. |
|
|
15
|
+
| BUG-A-001 | AR_BITWISE-REQ-001 | `RelationExtension#where` only guards Hash queries; Arel queries bypass guard unnoticed. | **Mitigated:** Documented Arel-based bypass query behavior in YARD `@note` documentation. |
|
|
16
|
+
| BUG-SCOPE-001 | AR_BITWISE-REQ-004 | `with_<column>` scope returns all rows when mask = 0 (explicit request). | **Fixed:** Updated scopes to handle zero mask by querying `where("#{quoted_col} = 0")`. |
|
|
17
|
+
| BUG-RAW-CACHE-001 | AR_BITWISE-REQ-007 | Getter cache `@_bitwise_raw_values` isn’t cleared after external raw‑SQL updates, causing stale raw values. | **Fixed:** Added `clear_bitwise_raw_values_cache!` helper method and overrode `reload` to automatically invalidate the cached values. |
|
|
18
|
+
| BUG-CALLBACK-002 | AR_BITWISE-REQ-007 | Callback flag stored per subclass causes duplicate callbacks in inheritance hierarchies. | **Fixed:** Utilizes class-level ancestor checks to ensure callbacks are registered only once per inheritance tree. |
|
|
19
|
+
| BUG-TYPE-002 | AR_BITWISE-REQ-001 | `Type#deserialize` can recurse infinitely when casting an Array containing an Integer‑like object. | **Fixed:** Implemented recursion depth counter to raise an error if deserialization depth exceeds safe limits. |
|
|
20
|
+
| BUG-DOC-001 | AR_BITWISE-REQ-007 | `add_<singular>!`/`remove_<singular>!` methods lack YARD `@note` about non‑atomic implementation. | **Fixed:** Added YARD `@note` documentation warning developers of the non-atomic nature of dynamic single instance methods. |
|
|
21
|
+
| BUG-CONN-001 | AR_BITWISE-REQ-005 | `quoted_col` is evaluated eagerly at class macro time (`bitwise` call) via `connection.quote_column_name(column_name)`. If the model later uses a different DB adapter, the cached quoted column name may be incorrect. | **Fixed:** Evaluates database quoted column name lazily at query execution time instead of eagerly during class loading. |
|
|
22
|
+
| BUG-TAPIOCA-003 | AR_BITWISE-REQ-007 | The Tapioca DSL compiler (`Tapioca::Dsl::Compilers::ActiveRecordBitwise#decorate`) does not generate RBI signatures for class-level atomic methods, scopes, or the `bitwise_schema` class method. | **Fixed:** Generates RBI signatures for class-level atomic methods, scopes, and the `bitwise_schema` class method. |
|
|
23
|
+
| BUG-TAPIOCA-004 | AR_BITWISE-REQ-007 | Instance-level atomic methods accept variadic `*values` at runtime, but the Tapioca compiler declares them with a single positional parameter. | **Fixed:** Declares rest parameters (splat arguments) for RBI signatures of instance-level atomic methods. |
|
|
24
|
+
| BUG-REQ-001 | AR_BITWISE-REQ-002 | `REQUIREMENTS.md` entry AR_BITWISE-REQ-002 states "Ruby 2.0+" but the gemspec is `'>= 3.2.0'` and README states "Ruby 3.2+". | **Fixed:** Updated `REQUIREMENTS.md` to consistently state "Ruby 3.2+". |
|
|
25
|
+
| BUG-SETTER-002 | AR_BITWISE-REQ-007 | The boolean setter builds `current_values` via duplication, dropping `@_bitwise_raw_value` and causing forgotten-bit loss on save. | **Fixed:** Propagates `@_bitwise_raw_value` correctly inside boolean setter array copies. |
|
|
26
|
+
| BUG-PERF-001 | AR_BITWISE-REQ-007 | The `_validate_bitwise_column_type_and_bounds` method runs on every record initialization callback, causing waste overhead. | **Fixed:** Optimized validation callback using a class-level flag to exit early if validation has already run. |
|
|
27
|
+
| BUG-CI-003 | AR_BITWISE-REQ-002 | `release.yml` parses unquoted floating-point `4.0` which causes setup failures. | **Fixed:** Quoted `ruby-version: "4.0"` and upgraded release action to `@v2`. |
|
|
28
|
+
| BUG-META-003 | AR_BITWISE-REQ-002 | gemspec sets source_code_uri and homepage_uri to the same value, causing metadata duplication. | **Fixed:** Removed duplicated `homepage_uri` metadata field from gemspec. |
|
|
29
|
+
| BUG-ENV-001 | AR_BITWISE-REQ-002 | `.gitignore` is missing environment manager specific exclusions like `mise.toml`. | **Fixed:** Added `mise.toml` and `.mise.toml` to `.gitignore`. |
|
|
30
|
+
| BUG-DEP-003 | AR_BITWISE-REQ-002 | `sorbet-runtime` dependency in gemspec uses a `~> 0.5` format instead of `~> x.0`. | **Fixed:** Converted constraint to standard `~> x.0` formatting. |
|
|
31
|
+
| BUG-DOC-002 | AR_BITWISE-REQ-002 | Missing `.env.example` file and missing initialization/testing commands in `README.md`. | **Fixed:** Created `.env.example` and documented bootstrap and test running commands in `README.md`. |
|
|
32
|
+
| BUG-DOC-003 | AR_BITWISE-REQ-002 | `.yardopts` asset copying fails when `doc/assets/` directory is missing. | **Fixed:** Removed empty asset directory copy from `.yardopts` to prevent YARD build failures. |
|
|
33
|
+
| BUG-RELEASE-002 | AR_BITWISE-REQ-002 | Release workflow uses static Action env expressions instead of dynamic shell variable names. | **Fixed:** Replaced Action expressions with dynamic shell variable substitutions. |
|
|
34
|
+
| BUG-CI-004 | AR_BITWISE-REQ-002 | Sorbet CLI `--typed strong` forces all files to strong, causing false positives with `T.untyped` Active Record calls. | **Fixed:** Removed overriding CLI parameter to honor file-level typed headers correctly. |
|
|
35
|
+
| BUG-TAPIOCA-005 | AR_BITWISE-REQ-007 | Tapioca DSL compiler generates return type of getter as `T::Array[Symbol]` which excludes unmapped string values. | **Fixed:** Declares the compiler return type as `T::Array[T.any(Symbol, String)]` to match exactly. |
|
|
36
|
+
| BUG-TEST-001 | AR_BITWISE-REQ-002 | RSpec configurations configure empty typecheck handlers, silencing all runtime type signature violations. | **Fixed:** Removed type checking silencers in `spec_helper.rb` to run all validation checks. |
|
|
37
|
+
| BUG-SQL-001 | AR_BITWISE-REQ-005 | SQL Injection via unsanitized `column_name` interpolation in raw SQL strings within `add_/remove_` class methods and scope definitions. | **Fixed:** All scope and atomic method SQL now uses `connection.quote_column_name(column_name)` for column references. |
|
|
38
|
+
| BUG-SQL-002 | AR_BITWISE-REQ-005 | SQL Injection via unsanitized `mask` integer interpolation in `add_/remove_` class-level atomic methods. | **Fixed:** Mask values are now passed via bind parameters (`?` placeholders) in `update_all` array syntax. |
|
|
39
|
+
| BUG-SQL-003 | AR_BITWISE-REQ-005 | SQL Injection via unsanitized `column_name` interpolation in `update_all` raw SQL strings within class-level `add_/remove_` methods. | **Fixed:** Column names quoted via `connection.quote_column_name`. |
|
|
40
|
+
| BUG-AREL-001 | AR_BITWISE-REQ-005 | Instance-level `add_/remove_` methods use `Arel.sql(column_name.to_s)` inside `pluck`. | **Fixed:** Replaced with `pluck(column_name.to_sym)` which is safe by default. |
|
|
41
|
+
| BUG-PREPEND-001 | AR_BITWISE-REQ-007 | `RelationExtension` and `WhereChainExtension` prepended twice. | **Fixed:** Removed unconditional double-prepend; kept only `ActiveSupport.on_load(:active_record)` block. |
|
|
42
|
+
| BUG-EXTEND-001 | AR_BITWISE-REQ-007 | `ActiveRecord::Bitwise::Model` extended onto `ActiveRecord::Base` twice. | **Fixed:** Same as BUG-PREPEND-001. |
|
|
43
|
+
| BUG-CALLBACK-001 | AR_BITWISE-REQ-007 | `after_initialize` and `after_save` callbacks registered redundantly for each `bitwise` column. | **Fixed:** Callback registration guarded with `@_bitwise_callbacks_registered` class-level flag. |
|
|
44
|
+
| BUG-DESER-001 | AR_BITWISE-REQ-001 | `Type#deserialize` with `nil` value recursively called `deserialize(default)` with an Array, triggering complex round-trip. | **Fixed:** Nil-default path now computes the bitmask from default values directly and deserializes the integer. |
|
|
45
|
+
| BUG-DESER-002 | AR_BITWISE-REQ-001 | In `Type#deserialize`, the `value.is_a?(Array)` branch had a subtle data flow inconsistency with raw value handling. | **Fixed:** Resolved as part of BUG-DESER-001 refactor. |
|
|
46
|
+
| BUG-SYM-001 | AR_BITWISE-REQ-007 | `add_/remove_` class-level atomic methods called `v.to_s.to_sym` bypassing Symbol DoS protection. | **Fixed:** Now uses string-based lookup against `mapping_strings` before any symbolization. |
|
|
47
|
+
| BUG-RELEASE-001 | AR_BITWISE-REQ-002 | Release workflow `sed` pattern `T.let('.*', String)` didn't match actual `VERSION = '...'` in version.rb. | **Fixed:** `sed` command now matches `VERSION = '.*'` pattern. |
|
|
48
|
+
| BUG-CI-001 | AR_BITWISE-REQ-002 | CI matrix included Ruby `"2.7"` below gemspec minimum `'>= 3.2.0'`. | **Fixed:** Removed Ruby 2.7 from CI matrix. |
|
|
49
|
+
| BUG-CI-002 | AR_BITWISE-REQ-002 | README stated "Ruby 2.0+" but gemspec requires `'>= 3.2.0'`. | **Fixed:** README updated to "Ruby 3.2+" and "Rails 7.0+". |
|
|
50
|
+
| BUG-DEP-001 | AR_BITWISE-REQ-002 | `activerecord` dependency used open-ended `'>= 5.0'`. | **Reviewed:** Confirmed `'>= 5.0'` is the correct technical minimum; no change needed. |
|
|
51
|
+
| BUG-DEP-002 | AR_BITWISE-REQ-002 | Multiple dev dependencies lacked version constraints. | **Fixed:** All dev dependencies now have `~> x.y` constraints. |
|
|
52
|
+
| BUG-META-001 | AR_BITWISE-REQ-002 | `allowed_push_host` set to TODO placeholder. | **Fixed:** Set to `'https://rubygems.org'`. |
|
|
53
|
+
| BUG-META-002 | AR_BITWISE-REQ-002 | Missing `homepage_uri` and `changelog_uri` metadata. | **Fixed:** Both metadata keys added. |
|
|
54
|
+
| BUG-FILE-001 | AR_BITWISE-REQ-002 | `LICENSE.txt` missing from repository. | **Fixed:** Created MIT `LICENSE.txt`. |
|
|
55
|
+
| BUG-FILE-002 | AR_BITWISE-REQ-002 | `CHANGELOG.md` missing. | **Fixed:** Created with v0.1.0 entry. |
|
|
56
|
+
| BUG-INFRA-001 | AR_BITWISE-REQ-002 | `.editorconfig` missing. | **Fixed:** Created with standard rules. |
|
|
57
|
+
| BUG-INFRA-002 | AR_BITWISE-REQ-002 | No pre-commit Git hooks configured. | **Fixed:** Created `.githooks/pre-commit` running rspec, rubocop, and sorbet. |
|
|
58
|
+
| BUG-TYPE-001 | AR_BITWISE-REQ-001 | `Type#cast` did not deduplicate `:admin` and `"admin"` as the same value. | **Fixed:** Changed `.uniq` to `.uniq(&:to_s)` for semantic deduplication. |
|
|
59
|
+
| BUG-VALID-001 | AR_BITWISE-REQ-006 | `BitwiseValidator` did not strip nil/empty-string before validation. | **Fixed:** Added `val.nil? \|\| val == ''` rejection before mapping check. |
|
|
60
|
+
| BUG-SCOPE-002 | AR_BITWISE-REQ-004 | Scope invalid-value handling was inconsistent across scope types. | **Fixed:** Added YARD `@note` documentation clarifying behavioral differences. |
|
|
61
|
+
| BUG-THREAD-002 | AR_BITWISE-REQ-005 | Instance-level atomic methods route through RelationExtension adding overhead. | **Mitigated:** Documented via YARD `@note`. The overhead is minimal and correctness is preserved. |
|
|
62
|
+
| BUG-CERT-001 | AR_BITWISE-REQ-002 | Gemspec `cert_path` references `certs/simplecov-ai-public_cert.pem` but the actual file is `certs/activerecord-bitwise-public_cert.pem`. | **Fixed:** Corrected to `certs/activerecord-bitwise-public_cert.pem`. |
|
|
63
|
+
| BUG-TAPIOCA-001 | AR_BITWISE-REQ-007 | Tapioca DSL compiler missing RBI signatures for atomic methods. | **Fixed:** Added `add_<singular>!`, `add_<column>!`, `remove_<singular>!`, `remove_<column>!` to `decorate`. |
|
|
64
|
+
| BUG-TAPIOCA-002 | AR_BITWISE-REQ-007 | Tapioca compiler declared `method_name!` return type as `T::Boolean` instead of `TrueClass`. | **Fixed:** Changed return type to `TrueClass`. |
|
|
65
|
+
| BUG-FILES-001 | AR_BITWISE-REQ-002 | `spec.files` glob excluded top-level docs from gem package. | **Fixed:** Added `README.md`, `LICENSE.txt`, `CHANGELOG.md`, `BUGS.md`, `REQUIREMENTS.md` to files list. |
|
|
66
|
+
| BUG-GITIGNORE-001 | AR_BITWISE-REQ-002 | `.rspec_status` not in `.gitignore`. | **Fixed:** Added to `.gitignore`. |
|
|
67
|
+
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-05-21
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Initial release of the activerecord-bitwise gem.
|
|
15
|
+
- Bitmask arithmetic and operations for storing multiple boolean/enum values in a single integer column.
|
|
16
|
+
- Prefix and suffix options for generated helper methods.
|
|
17
|
+
- Database scopes: `with_`, `with_any_`, `with_exact_`, `without_`.
|
|
18
|
+
- High concurrency SQL atomic methods (`add_!`, `remove_!`) at class and instance levels.
|
|
19
|
+
- Graceful validation with `validates :attribute, bitwise: true`.
|
|
20
|
+
- 17 safety and architectural guarantees.
|
|
21
|
+
- Tapioca DSL compiler for Sorbet static analysis.
|
|
22
|
+
- SimpleCov AI integration for coverage reporting.
|
|
23
|
+
- Real-life usage simulation tests.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VitaliiLazebnyi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# ActiveRecord::Bitwise
|
|
2
|
+
|
|
3
|
+
ActiveRecord::Bitwise is a Ruby on Rails gem providing the ability to store multiple boolean or enum-like states inside a single integer database column. While a standard Rails `enum` saves a single value as an integer/string, `activerecord-bitwise` maps an array of symbolic values to individual bits of a single integer utilizing bitmask arithmetic.
|
|
4
|
+
|
|
5
|
+
This is exceptionally useful for `roles`, `permissions`, `preferences`, or `features` mappings where a record may have zero or multiple states concurrently, without requiring junction tables (has_and_belongs_to_many) or unstructured JSON/array column types.
|
|
6
|
+
|
|
7
|
+
It supports **Ruby 3.2+** and **Ruby on Rails 5.0+**.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your application's Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'activerecord-bitwise'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
And then execute:
|
|
18
|
+
```bash
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install it yourself as:
|
|
23
|
+
```bash
|
|
24
|
+
gem install activerecord-bitwise
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Database Migration
|
|
28
|
+
|
|
29
|
+
The target column in your database must be an `integer`. We highly recommend setting a `default: 0` and `null: false` constraint on the column to avoid database null-state issues.
|
|
30
|
+
|
|
31
|
+
> **Capacity Note (Strict Sign-Bit Limits):** Standard database integers are mathematically **signed**. The gem strictly enforces this ceiling and deliberately refuses to support `unsigned` configurations, universally sacrificing exactly **one bit** of physical headroom across all column limits. Attempting to manually force the final bit throws a fatal database `Integer Out Of Range` crash. *(Why not bypass this with `unsigned`? PostgreSQL natively lacks unsigned schema integers, so forcing it breaks adapter cross-compatibility. Furthermore, allocating the outermost bit triggers "Two's Complement," converting the integer into a negative value, which violently corrupts bitwise (`&`) scope evaluation queries in dynamically-typed adapters like SQLite).*
|
|
32
|
+
> * `limit: 1` (`tinyint`) stores up to **7 flags** (1 byte)
|
|
33
|
+
> * `limit: 2` (`smallint`) stores up to **15 flags** (2 bytes)
|
|
34
|
+
> * `limit: 4` (`integer`) stores up to **31 flags** (4 bytes) - *Rails default*
|
|
35
|
+
> * `limit: 8` (`bigint`) stores up to **63 flags** (8 bytes)
|
|
36
|
+
|
|
37
|
+
To add `activerecord-bitwise` settings to a `User` model with a column named `roles`:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
rails generate migration AddRolesToUsers roles:integer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In the generated migration file, ensure you set the default constraint:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
# (Replace [6.1] with your current Rails version)
|
|
47
|
+
class AddRolesToUsers < ActiveRecord::Migration[6.1]
|
|
48
|
+
def change
|
|
49
|
+
add_column :users, :roles, :integer, default: 0, null: false
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then run the migration:
|
|
55
|
+
```bash
|
|
56
|
+
rails db:migrate
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Model Configuration
|
|
60
|
+
|
|
61
|
+
In your `ActiveRecord` model, simply define your bitwise column (the gem automatically injects into `ActiveRecord::Base`).
|
|
62
|
+
|
|
63
|
+
You can declare settings using a **Hash** (highly recommended to prevent data corruption) or an **Array** (where index defines the bit offset).
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
class User < ApplicationRecord
|
|
67
|
+
# RECOMMENDED: Explicit Hash mapping. The integer values correspond to the bit shift position (e.g. 1<<0).
|
|
68
|
+
# Leave a placeholder (e.g. `_deprecated_role: 1`) to safeguard legacy database states.
|
|
69
|
+
bitwise :roles, { admin: 0, _deprecated_role_1: 1, author: 2, subscriber: 3 }
|
|
70
|
+
|
|
71
|
+
# ADVANCED: Global Fallbacks (Auto-Initialization)
|
|
72
|
+
# You can enforce new records to boot precisely aligned to business logic natively bypassing `#after_initialize`.
|
|
73
|
+
bitwise :permissions, { read: 0, manage: 1 }, default: [:read]
|
|
74
|
+
|
|
75
|
+
# WARNING: Array mapping is supported, but never remove or re-order logic, only append to the end.
|
|
76
|
+
bitwise :legacy_roles, %i[admin moderator author subscriber]
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Prefix and Suffix Options
|
|
81
|
+
|
|
82
|
+
Just like standard Rails enums, you can use the `prefix` and `suffix` options to avoid method name collisions if you have multiple bitwise columns using the same names.
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class User < ApplicationRecord
|
|
86
|
+
bitwise :roles, { admin: 0, author: 1 }, suffix: true
|
|
87
|
+
bitwise :permissions, { admin: 0, author: 1 }, prefix: :can
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
user = User.new
|
|
91
|
+
user.admin_role? # => uses roles column
|
|
92
|
+
user.can_admin? # => uses permissions column
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Usage and API
|
|
96
|
+
|
|
97
|
+
ActiveRecord::Bitwise generates dynamic getter, setter, and scope helpers tailored exactly to your config definitions.
|
|
98
|
+
|
|
99
|
+
### Active Record Operations
|
|
100
|
+
|
|
101
|
+
You can set and retrieve the entire collection using an array of symbols/strings.
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
user = User.new
|
|
105
|
+
|
|
106
|
+
# Set roles using an array
|
|
107
|
+
user.roles = %i[admin author]
|
|
108
|
+
user.roles # => [:admin, :author]
|
|
109
|
+
|
|
110
|
+
# Or strings (it converts under the hood)
|
|
111
|
+
user.roles = ['subscriber']
|
|
112
|
+
user.roles # => [:subscriber]
|
|
113
|
+
|
|
114
|
+
# To clear out all values
|
|
115
|
+
user.roles = []
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Form Helpers & Strong Parameters
|
|
119
|
+
|
|
120
|
+
When dealing with standard Rails form submissions (e.g. `collection_check_boxes`), Rails often submits empty strings `""` for unchecked states. `bitwise` gracefully handles and strips out `""` and `nil` values automatically, so you don't need to manually sanitize your strong parameters:
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# The empty string is automatically ignored
|
|
124
|
+
user.roles = ['', 'admin', 'author']
|
|
125
|
+
user.roles # => [:admin, :author]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Dirty Tracking (`_changed?`)
|
|
129
|
+
|
|
130
|
+
Because it integrates seamlessly with `ActiveModel::Dirty`, you can check for mutations on your virtual array attributes just like standard columns:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
user = User.find(1)
|
|
134
|
+
user.roles = [:admin]
|
|
135
|
+
|
|
136
|
+
user.roles_changed? # => true
|
|
137
|
+
user.roles_was # => []
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Boolean Setters and Getters
|
|
141
|
+
|
|
142
|
+
Individual accessor methods are dynamically generated allowing direct querying and mutation of single attributes.
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
user = User.new
|
|
146
|
+
|
|
147
|
+
# Question methods
|
|
148
|
+
user.admin? # => false
|
|
149
|
+
user.author? # => false
|
|
150
|
+
|
|
151
|
+
# Boolean Setters
|
|
152
|
+
user.admin = true
|
|
153
|
+
user.author = true
|
|
154
|
+
|
|
155
|
+
# Bang Methods (Sets to true and instantly saves to the database)
|
|
156
|
+
user.admin!
|
|
157
|
+
|
|
158
|
+
user.roles # => [:admin, :author]
|
|
159
|
+
user.roles_before_type_cast # => 5 (1 + 4)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### High Concurrency (SQL Atomic Methods)
|
|
163
|
+
|
|
164
|
+
Loading records, modifying arrays, and saving (`#save`) is vulnerable to race conditions in high-throughput applications. To bypass Ruby's memory layer entirely, `bitwise` offers atomic raw-SQL bit manipulation algorithms. These execute directly against the DB layer bypassing Dirty Trackers entirely:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
# Adds the admin role natively via: UPDATE users SET roles = roles | 1 WHERE id = 1
|
|
168
|
+
User.add_roles!(:admin, records: user.id)
|
|
169
|
+
|
|
170
|
+
# Removes the author role via: UPDATE users SET roles = roles & ~2 WHERE id = 1
|
|
171
|
+
user.remove_role!(:author)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Scopes (Querying the Database)
|
|
175
|
+
|
|
176
|
+
ActiveRecord::Bitwise heavily leverages raw bitmask SQL calculations to extract data efficiently without loading objects into memory. It creates scopes to filter your records using `#with_[attribute]` and `#without_[attribute]`.
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# Find all users that have the :admin role
|
|
180
|
+
# (they may also be authors or subscribers)
|
|
181
|
+
User.with_roles(:admin)
|
|
182
|
+
|
|
183
|
+
# Find all users that have BOTH :admin and :author roles
|
|
184
|
+
User.with_roles(:admin, :author)
|
|
185
|
+
|
|
186
|
+
# Find all users that have EITHER :admin OR :author roles
|
|
187
|
+
User.with_any_roles(:admin, :author)
|
|
188
|
+
|
|
189
|
+
# Find users who are ONLY admins (and nothing else)
|
|
190
|
+
User.with_exact_roles(:admin)
|
|
191
|
+
|
|
192
|
+
# Find all users that do NOT have the :moderator role
|
|
193
|
+
User.without_roles(:moderator)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Advanced Information
|
|
197
|
+
|
|
198
|
+
### Concurrency (Optimistic Locking Fallback)
|
|
199
|
+
|
|
200
|
+
If you strictly must mutate states entirely in Ruby memory via active arrays without using the native Atomic SQL methods (described above), we highly advise utilizing standard [Rails Optimistic Locking](https://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html) by adding an integer `lock_version` column to your tables to organically prevent simultaneous process overwrites.
|
|
201
|
+
|
|
202
|
+
### Database Indexing & Full Table Scans
|
|
203
|
+
|
|
204
|
+
Standard B-Tree indexes cannot index bitwise hardware calculations like `WHERE (roles & 1) > 0`. If you expect your table to grow to millions of rows, querying scopes against bits will trigger full sequential scans, degrading DB performance. For query-heavy systems natively on PostgreSQL, apply a functional index or a GIN index onto the bitwise column.
|
|
205
|
+
|
|
206
|
+
### Memory Optimization
|
|
207
|
+
|
|
208
|
+
ActiveRecord::Bitwise guarantees low memory footprint. Storing an array of settings as a single DB integer prevents query inflation and utilizes standard SQL bitwise operators (such as `&` and `|`), delivering performance magnitudes faster than using generalized `json` or `text` based serialization.
|
|
209
|
+
|
|
210
|
+
### Graceful Validation (Safe Assignment)
|
|
211
|
+
|
|
212
|
+
Instead of raising a fatal `500 Server Error` (like standard enums) if a malicious user submits an invalid string payload, the `bitwise` engine holds invalid assignments in memory so you can easily catch them using standard Rails validations.
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class User < ApplicationRecord
|
|
216
|
+
bitwise :roles, { admin: 0, author: 1 }
|
|
217
|
+
validates :roles, bitwise: true
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
user.roles = %i[admin hacker]
|
|
221
|
+
user.valid? # => false
|
|
222
|
+
user.errors[:roles] # => ["contains invalid values: hacker"]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
> **The `validate: false` DB Defense:** If a developer forcibly invokes `user.save(validate: false)` or a background worker invokes `.update_column` while an invalid uncoercible payload (`"hacker"`) is held, the underlying Typecaster permanently **drops** the invalid string during the database serialization phase to prevent catastrophic `ActiveRecord::SerializationFailure`. It will continuously and safely persist only the valid subsets.
|
|
226
|
+
## Known Limitations & Mitigation Strategies
|
|
227
|
+
|
|
228
|
+
While highly defensive, this architecture introduces inherent physical and systemic limitations. You must design around the following constraints to prevent data corruption or catastrophic failure.
|
|
229
|
+
|
|
230
|
+
### 1. The 63-Bit Hard Wall Limiter
|
|
231
|
+
**Problem:** The `bigint` signed column physically caps at 63 concurrent flags. Reaching 64 flags will organically trigger database `Integer Out Of Range` crashes.
|
|
232
|
+
**Developer Mitigation:** Only use this gem for bounded logic scopes (e.g., core user permissions, strict system states). Do not use this for dynamic tags or user-generated groupings. Once you project exceeding 40-50 flags, immediately architect a migration to `JSONB` or standard `has_and_belongs_to_many` junction tables.
|
|
233
|
+
**Gem-Level Mitigation:** **None.** This is a fixed SQL standard arithmetic ceiling that Ruby logic cannot physically circumvent using native native numeric representations.
|
|
234
|
+
|
|
235
|
+
### 2. "Ghost Bit" Refactoring Collisions ("Sleeper Cell" Data Corruption)
|
|
236
|
+
**Problem:** The core defense mechanism that protects "Forgotten Bits" (to prevent destructive saves in multi-node environments) becomes a liability if developers re-use integers. If you delete `{ author: 1 }` and add `{ editor: 1 }`, the gem will seamlessly and silently grant all legacy authors the new `editor` status on their next save.
|
|
237
|
+
**Developer Mitigation:** **Never delete or re-index keys.** You must strictly treat mappings as append-only ledgers. Always leave deprecated keys as intact placeholders: `bitwise :roles, { _deprecated_author: 1, editor: 2 }`.
|
|
238
|
+
**Gem-Level Mitigation:** **None.** The gem intentionally honors unknown ghost bits to prevent multi-node data destruction. It has no way to logically distinguish an architectural key rename from a rolling production deployment delta.
|
|
239
|
+
|
|
240
|
+
### 3. Array RAM Exhaustion (Payload DoS Thread Lock)
|
|
241
|
+
**Problem:** To prevent DB bloat, the gem runs `.compact.map(&:to_sym).uniq` on assignment. If a malicious actor bypasses UI limits and submits an HTTP array containing 2,000,000 randomized strings, this array enumeration will severely spike server RAM, invoking an OOMKill that takes down the entire Ruby node.
|
|
242
|
+
**Developer Mitigation:** Enforce strict parameter length validations at the Controller boundary before model assignment. Do not rely exclusively on Model validations to catch excessive array lengths.
|
|
243
|
+
**Gem-Level Mitigation:** **Active.** The internal mapping engine imposes an instant, O(1) `.size` intercept during raw array assignments, setting a hard ceiling at `100` elements. *(Why limit to 100 instead of exactly 63? A gracefully padded buffer of ~30 spaces absorbs standard Rails empty-strings ["", "admin"], unfiltered array duplicates, and permits innocent frontend typo-strings to be securely processed and displayed by ActiveModel Error outputs, rather than punishing legitimate users with fatal HTTP 500 crashes).* If an array surpasses 100, the gem instantly raises `ArgumentError` entirely neutralizing memory inflation.
|
|
244
|
+
|
|
245
|
+
### 4. Privilege Escalation via Mass Assignment ("God Mode")
|
|
246
|
+
**Problem:** Because a single `roles: []` parameter maps to multiple isolated boolean concepts, unconditionally allowing `params.permit(roles: [])` exposes the application to privilege escalation if an attacker manually injects `"super_admin"` into their profile update form payload.
|
|
247
|
+
**Developer Mitigation:** Never allow mass-assignment of bitwise arrays on generic/public endpoints without strict filtering. Utilize dedicated endpoints for permission mutations, or strictly filter the array explicitly in the controller: `params.permit(roles: permitted_role_keys)`.
|
|
248
|
+
**Gem-Level Mitigation:** **None.** Parameter sanitization is inherently an `ActionController` domain boundary. The ActiveRecord model has no awareness of the HTTP context, session scope, or current user privileges to determine if a requested role assignment is authorized.
|
|
249
|
+
|
|
250
|
+
### 5. Background Worker Cache-Drops (Sidekiq/ActiveJob)
|
|
251
|
+
**Problem:** The "Forgotten Bits" protection heavily relies on memory instance variables (`@_bitwise_raw_value`). Relying on automated YAML/JSON background job serializers strips unpersisted instance variables across the network boundary. Saving an altered model inside a worker without this cache will permanently obliterate all unmapped legacy database bits.
|
|
252
|
+
**Developer Mitigation:** Never pass dirty, instantiated ActiveRecord objects into background workers. Always pass primitive IDs (`user_id`) and execute a fresh `User.find(user_id)` inside the worker execution context to safely pull the database representation into memory prior to mutation.
|
|
253
|
+
**Gem-Level Mitigation:** **None.** Background serializers like Sidekiq intentionally flatten state to avoid Redis buffer overflows. The gem cannot natively transfer its RAM payload across separated processes.
|
|
254
|
+
|
|
255
|
+
### 6. MySQL / SQLite Full Table Scans
|
|
256
|
+
**Problem:** Bitwise scopes (e.g., `User.with_roles(:admin)`) execute raw hardware binary queries (`WHERE (roles & 1) > 0`). Standard B-Tree algorithms cannot index binary math. Without specialized indexing capabilities, millions of rows will trigger massive sequential full-table scans organically degrading database query performance to zero.
|
|
257
|
+
**Developer Mitigation:** Only deploy on **PostgreSQL** leveraging advanced GIN/Functional bitwise indexes. If you are strictly hardware-locked into MySQL (5.7+), mathematically bind "Generated Virtual Columns" (`admin_flag AS (roles & 1)`) and attach B-Tree indexes directly to all virtual boolean columns.
|
|
258
|
+
**Gem-Level Mitigation:** **None.** Indexing strategies are database adapter-specific configurations and must be applied natively via active migrations.
|
|
259
|
+
|
|
260
|
+
### 7. The "Read-Modify-Write" Mutex Lock (Race Condition Data Loss)
|
|
261
|
+
**Problem:** If two separate web requests process overlapping arrays and call `#save` at the exact same millisecond, the database write will obey the "Last Write Wins" rule. One of the user's intended role additions will literally be overridden and destroyed by the other concurrent request without warning.
|
|
262
|
+
**Developer Mitigation:** You must use standard Rails Optimistic Locking by adding an integer `lock_version` column to your tables, or strictly use the gem's native atomic raw-SQL update methods (`User.add_roles!`) instead of mutating Ruby arrays natively.
|
|
263
|
+
**Gem-Level Mitigation:** **None.** The `#save` method belongs to ActiveRecord. The gem cannot natively enforce database row-level locking on generic `#save` calls without severely degrading application throughput.
|
|
264
|
+
|
|
265
|
+
### 8. The "STI Type Hijack" (Cross-Model Leak)
|
|
266
|
+
**Problem:** In Single Table Inheritance (STI), if `Admin` and `Customer` models map to the same `roles` integer column but define completely different enum options, changing a record's `type` attribute will maliciously shift its database binary map. A `Customer` opting into `wants_newsletter: 0` will suddenly evaluation as `has_super_admin: 0` if their type changes and the new model shares the bit integer.
|
|
267
|
+
**Developer Mitigation:** Never share the same bitwise column name across polymorphic or STI models unless the internal mapping configurations are 100% physically identical and inherited from the core abstract class.
|
|
268
|
+
**Gem-Level Mitigation:** **Passive.** The gem strongly isolates its class-attribute caches so models don't pollute each other in Ruby memory, but it cannot physically defend against raw DB column data overlaps.
|
|
269
|
+
|
|
270
|
+
### 9. Garbage Collection Thrashing (Memory Allocation Complexity)
|
|
271
|
+
**Problem:** To strictly maintain Rails' `Dirty Tracking` functionality safely, the gem generates operations functionally and returns explicitly `frozen` Arrays upon accessing virtual getters (e.g. `user.roles`). If an application iterates over an immense dataset in an API JSON-serializer loop like `User.all.map(&:roles)`, the engine constructs a brand new frozen Array populated with newly allocated Symbols *for every individual record*. For 10,000 users, it instantly generates 10,000 Arrays and hundreds of thousands of isolated Object allocations, triggering a massive $O(N \\times V)$ memory spike.
|
|
272
|
+
**Developer Mitigation:** Strictly avoid triggering bitwise Array abstractions inside massive `O(N)` loop enumerations. For massive API JSON payloads, leverage the underlying native hardware integer (`user.roles_before_type_cast`) instead of the virtual Ruby array to prevent your Ruby Garbage Collector (GC) from violently halting the main Puma thread to sweep objects.
|
|
273
|
+
**Gem-Level Mitigation:** **None.** Continuous Ruby object allocation is the unavoidable architectural tradeoff required to mimic mapped complex ActiveModel attributes safely without permanently destroying the `ActiveModel::Dirty` tracker engine instance-wide.
|
|
274
|
+
|
|
275
|
+
### 10. "Black Box" Database Debugging (Obfuscated Raw Data)
|
|
276
|
+
**Problem:** Looking directly at the database column shows a seemingly random integer (e.g., `13`). Database administrators and support agents cannot intuitively know what `13` means without reverse-engineering the bitwise math (`8 + 4 + 1`).
|
|
277
|
+
**Developer Mitigation:** Document the integer mappings externally or provide internal administrative dashboards that safely decode the values into human-readable strings.
|
|
278
|
+
**Gem-Level Mitigation:** **None.** This obfuscation is the fundamental nature of bitmask architectures.
|
|
279
|
+
|
|
280
|
+
### 11. Business Intelligence (BI) & Analytics Friction
|
|
281
|
+
**Problem:** External BI tools (Metabase, Tableau) connected to read-replicas do not understand Ruby. Data analysts cannot use simple `LIKE` or equality queries and must resort to complex, adapter-specific bitwise SQL (`WHERE (roles & 4) > 0`), creating significant friction for non-engineering teams.
|
|
282
|
+
**Developer Mitigation:** Use the gem's `.bitwise_schema` export feature to sync definitions to your data warehouse (e.g., Snowflake) and construct decoded SQL views or materialized derived tables for the analytics team to query against.
|
|
283
|
+
**Gem-Level Mitigation:** **None.** The gem operates strictly within the Ruby/ActiveRecord boundary.
|
|
284
|
+
|
|
285
|
+
### 12. Migration & Refactoring Hell (Immutable Contract)
|
|
286
|
+
**Problem:** Over time, business logic changes. If you must forcibly remove a role across all users or split one role into two, writing the zero-downtime raw SQL data migration is incredibly dangerous, highly prone to errors, and difficult to reverse safely.
|
|
287
|
+
**Developer Mitigation:** Strictly treat the mapping as an append-only ledger. If a role must be retired, leave it as a `_deprecated_` placeholder. If logic must be split, add new bits and handle the legacy inference at the Ruby method level.
|
|
288
|
+
**Gem-Level Mitigation:** **None.** Complex state data migrations must be handled manually by the developer.
|
|
289
|
+
|
|
290
|
+
### 13. Framework Lock-in (Portability Loss)
|
|
291
|
+
**Problem:** Transitioning parts of your stack away from Ruby to a Go, Node.js, or Elixir microservice means the new service cannot seamlessly read the `bitwise` column natively. You will be forced to manually port the exact bitwise integer decoding logic into the new language.
|
|
292
|
+
**Developer Mitigation:** Expose the decoded states exclusively via your internal JSON API / GraphQL layer rather than allowing external microservices to connect directly to the underlying SQL table.
|
|
293
|
+
**Gem-Level Mitigation:** **None.** The raw database schema is optimized for Ruby evaluation.
|
|
294
|
+
|
|
295
|
+
### 14. The `<<` Array Push Trap (Developer Ergonomics)
|
|
296
|
+
**Problem:** Because the gem returns frozen arrays to protect `ActiveModel::Dirty` tracking, developers trying to use the standard Ruby array append operator (`user.roles << :admin`) will violently crash the application in production with a `FrozenError`.
|
|
297
|
+
**Developer Mitigation:** Developers must retrain muscle memory to strictly use array reassignment (`user.roles += [:admin]`) or utilize the atomic bang methods (`user.admin!`).
|
|
298
|
+
**Gem-Level Mitigation:** **Active.** The arrays are explicitly frozen so they fail fast at runtime, preventing silent data-loss bugs that would occur if `<<` was allowed but not tracked by Rails.
|
|
299
|
+
|
|
300
|
+
### 15. The `validate: false` Data Destruction
|
|
301
|
+
**Problem:** Standard Rails behavior assumes `user.save(validate: false)` forcefully writes exact payload states to the database. However, this gem actively intercepts invalid uncoercible payloads during serialization and permanently silently drops them to prevent generic `SerializationFailure` crashes.
|
|
302
|
+
**Developer Mitigation:** Never use `validate: false` when processing external input schemas if you expect exactly 1:1 persistence. Always run `#valid?` to capture the validation errors explicitly.
|
|
303
|
+
**Gem-Level Mitigation:** **Passive.** The Typecaster intentionally shreds invalid strings to prioritize database boot-safety over unvalidated payload integrity.
|
|
304
|
+
|
|
305
|
+
### 16. Brittle Database Agnosticism
|
|
306
|
+
**Problem:** The gem severely degrades the seamless database-agnostic philosophy of Rails. SQLite strictly coerces outputs to Strings, Postgres requires functional GIN indexes, and MySQL demands Generated Virtual Columns to avoid performance death. Moving from local SQLite to production Postgres behaves radically differently at the hardware query planner level.
|
|
307
|
+
**Developer Mitigation:** Your CI must flawlessly mirror your production database environment natively. Do not rely on SQLite for testing if you plan to deploy on Postgres/MySQL in production.
|
|
308
|
+
**Gem-Level Mitigation:** **Active/Passive.** The gem provides global `#to_i` wrappers for SQLite, but cannot automatically configure adapter-specific indexing.
|
|
309
|
+
|
|
310
|
+
## Development
|
|
311
|
+
|
|
312
|
+
### Bootstrapping the Project
|
|
313
|
+
|
|
314
|
+
1. Install dependencies:
|
|
315
|
+
```bash
|
|
316
|
+
bundle install
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Running the Test Suite
|
|
320
|
+
|
|
321
|
+
Execute the RSpec tests:
|
|
322
|
+
```bash
|
|
323
|
+
bundle exec rspec
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Static Analysis and Type Checking
|
|
327
|
+
|
|
328
|
+
Run the Sorbet static type-checker:
|
|
329
|
+
```bash
|
|
330
|
+
bundle exec srb tc
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Run RuboCop to verify style guidelines:
|
|
334
|
+
```bash
|
|
335
|
+
bundle exec rubocop
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Generating Documentation
|
|
339
|
+
|
|
340
|
+
Build the YARD documentation:
|
|
341
|
+
```bash
|
|
342
|
+
bundle exec yard doc
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/REQUIREMENTS.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Active Specifications
|
|
2
|
+
|
|
3
|
+
| ID | Description |
|
|
4
|
+
|---|---|
|
|
5
|
+
| AR_BITWISE-REQ-001 | Feature must allow saving multiple symbols to integer via bitwise masking logic. |
|
|
6
|
+
| AR_BITWISE-REQ-002 | System must be Ruby 3.2+ and Rails 5.0+ compatible. |
|
|
7
|
+
| AR_BITWISE-REQ-003 | Model Configuration must support Prefix and Suffix options for generated helper methods. |
|
|
8
|
+
| AR_BITWISE-REQ-004 | Database Scopes must include `with_`, `with_any_`, `with_exact_`, and `without_` prefixes with fuzzer immunization and zero-bounds. |
|
|
9
|
+
| AR_BITWISE-REQ-005 | High Concurrency SQL atomic methods must be provided at class and instance levels. |
|
|
10
|
+
| AR_BITWISE-REQ-006 | Graceful Validation with safe assignment and custom `validates ..., bitwise: true` must be supported. |
|
|
11
|
+
| AR_BITWISE-REQ-007 | Gem must provide 17 detailed Safety Guarantees (forgotten bits, STI isolation, fuzzer immunization, nil db coalescence, symbol DoS prevention, frozen arrays, column type check, where clause poisoning defense, boot-deadlock trap, lifecycle bricking prevention, clone bleeding prevention, zero-state scope bounds, multi-tenant ETL schema, SQLite string coercion, update_all, over-shift execution halt). |
|
|
12
|
+
| AR_BITWISE-REQ-008 | System must install and configure `simplecov-ai` to generate a concise AST-mapped Markdown digest at `coverage/ai_report.md` alongside the standard SimpleCov HTML reports. |
|
|
13
|
+
| AR_BITWISE-REQ-009 | Gem must have comprehensive real-life usage simulation tests (high-concurrency race condition defense, ETL/analytics data pipeline integration, dynamic multi-tenant form validation and processing). |
|
|
14
|
+
|
|
15
|
+
## [ARCHIVED]
|
|
16
|
+
_No archived requirements yet._
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEOTCCAqGgAwIBAgIUBONmsFo7fxLGkUHsKe65onH+5ogwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwLDEqMCgGA1UEAwwhdml0YWxpaS5sYXplYm55aS5naXRodWJAZ21haWwuY29t
|
|
4
|
+
MB4XDTI2MDQxNTEzNTMyOVoXDTM2MDQxMjEzNTMyOVowLDEqMCgGA1UEAwwhdml0
|
|
5
|
+
YWxpaS5sYXplYm55aS5naXRodWJAZ21haWwuY29tMIIBojANBgkqhkiG9w0BAQEF
|
|
6
|
+
AAOCAY8AMIIBigKCAYEA5zdezJE+Zrsk9j53/IxBfRoaqvLcPvrcfl+EaEwWhIkV
|
|
7
|
+
0+08GtgS9N7VpB8cgaH2rkLJPjHIetsN/g5GMkDRsbJNXMrPVhxe1e1lI/r6j0Tm
|
|
8
|
+
JD0PaU4r8VzitxkqY9BBmSI8GjDjAfrT1u5jSXH1iAtKUoq5F116uYrxbgiDpvqa
|
|
9
|
+
kUQYcTf+6cZaPlF4KKhULnhKqs8u/NxyH4vPZyxEfg/gA4bODvcjW1A6d59BTiLV
|
|
10
|
+
yrJPebwU+F+URb8aoQ4AGvPKFiG1Y1fxRHuPrOpyymFnBnjwgMyQkNHtzTeEriV9
|
|
11
|
+
z1BUb10Pb/pjLBCrOvnStTPmcm1GE8HL2psYvlLvBlYqq3gzpQPBBKE3Jefa7ilC
|
|
12
|
+
cYsBYOGpynpA9uu9cXKa4jtpPDGQ7Qrpnk9gHy/0xfbgLdAkRCoZJeR7wDL/1xmm
|
|
13
|
+
nXwcUOLSOBj1Y4P9M+uQSQUZFTAaLbwyaBfE1gvVjwbTv3+rNP1ck1hACt+numGG
|
|
14
|
+
m7R6MF+Hmh8pNnDBYpBNAgMBAAGjUzBRMB0GA1UdDgQWBBRbuaz1EhdG6T4KIeWr
|
|
15
|
+
ac8LULxO9zAfBgNVHSMEGDAWgBRbuaz1EhdG6T4KIeWrac8LULxO9zAPBgNVHRMB
|
|
16
|
+
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBgQBgfGTDIMxlm6o8o7dzCR0HosRm
|
|
17
|
+
DSeUrx46EG1knTEqO05CooEHW98hrHa1/EwzkPaH1KhjjserQb6VtczMnySlfySu
|
|
18
|
+
HbKWAIaqzlpf8zaE5tCiAKgFKr77b2XB7xKt25p/Vf/Kn/RLm3+sYQ2izzzMimei
|
|
19
|
+
tBHo29cLV9bB/5HHFDwjrtdC5a0HJHiir0w4MCSDDGtnsKird4RKD2xESpoVjiNg
|
|
20
|
+
L9nEGk25YDeIfKn8UtxduMv53T86CiBSsDcEb6oVjNiMOA0HFucwFKX+Vy5u0/qx
|
|
21
|
+
ZRoLbZiCkTTGyNkBh4o6RCCTn37Lj98FBxYMbAHLNhEcKnAGxB7XP/CYsV4+QHOy
|
|
22
|
+
h0PctylhIvm24QeKgIWJUWamFPfqdvlP660T4umxl2wMqvNpWmGMmGTMCraoKwxl
|
|
23
|
+
zpp6uA15MXgTU7CxGivRgUKM64TqBMZKkOJcCtPkruSobxiR8cROrBNTqEbrmedM
|
|
24
|
+
26EUEoxwDzfSzHU2SKz5pMR+8DClMUKB1rctg68=
|
|
25
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
require 'active_model'
|
|
6
|
+
|
|
7
|
+
# Custom validator supporting validates :attribute, bitwise: true
|
|
8
|
+
class BitwiseValidator < ActiveModel::EachValidator
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { override.params(record: ActiveRecord::Base, attribute: Symbol, value: T.untyped).void }
|
|
12
|
+
def validate_each(record, attribute, value)
|
|
13
|
+
return if value.nil?
|
|
14
|
+
|
|
15
|
+
definition = T.unsafe(record.class).bitwise_definitions[attribute.to_sym]
|
|
16
|
+
return unless definition
|
|
17
|
+
|
|
18
|
+
mapping = definition[:mapping]
|
|
19
|
+
mapping_strings = mapping.keys.map(&:to_s)
|
|
20
|
+
invalid_values = Kernel.Array(value).reject { |val| val.nil? || val == '' || mapping_strings.include?(val.to_s) }
|
|
21
|
+
|
|
22
|
+
return unless invalid_values.any?
|
|
23
|
+
|
|
24
|
+
T.unsafe(record).errors.add(attribute, "contains invalid values: #{invalid_values.join(', ')}")
|
|
25
|
+
end
|
|
26
|
+
end
|