inquiry_attrs 1.0.1 → 1.0.2
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 +4 -4
- data/AGENTS.md +26 -2
- data/CHANGELOG.md +23 -1
- data/CLAUDE.md +25 -0
- data/README.md +78 -15
- data/lib/inquiry_attrs/nil_inquiry.rb +6 -0
- data/lib/inquiry_attrs/symbol_inquiry.rb +2 -1
- data/lib/inquiry_attrs/version.rb +1 -1
- data/llms/overview.md +29 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eaef756f9fda05436e3f930b7d0252712fea66dc7818b23b1238b2c098290c77
|
|
4
|
+
data.tar.gz: 61d886c923036fd403a0e2c780a1b45bb03de0f3f44f136a0a9cb44f48ebf7f5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38bb6a3e26e701fbcc4e8dbe874f329ff74b245b67e4552566883b5a7520d2feef0d0c170ce5a639fe730792aba01d208a237914e1d23f746be98d8adb302719
|
|
7
|
+
data.tar.gz: 241b76ab67af3f051a12d40113cfaac361294e7b88a254ec12ff93d2b1f8be60309b2fa22c6e69b289d5115ce8dcd0444eb872d8bc964468beb8fe5cd8826e34
|
data/AGENTS.md
CHANGED
|
@@ -92,6 +92,26 @@ database in `test/test_helper.rb`.
|
|
|
92
92
|
|
|
93
93
|
---
|
|
94
94
|
|
|
95
|
+
## Architecture — reserved predicate names
|
|
96
|
+
|
|
97
|
+
Some predicate names are **already defined as real methods** on the returned
|
|
98
|
+
objects. `method_missing` is never reached for them, so calling them does **not**
|
|
99
|
+
test whether the attribute value equals that word.
|
|
100
|
+
|
|
101
|
+
| Predicate | Defined on | What it actually does |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `nil?` | Ruby `Object` | `false` for any present value; `true` for `NilInquiry` |
|
|
104
|
+
| `blank?` | ActiveSupport | `true` when value is blank — not when it equals `"blank"` |
|
|
105
|
+
| `present?` | ActiveSupport | Opposite of `blank?` — not when value equals `"present"` |
|
|
106
|
+
| `empty?` | Ruby `String` | `true` only for `""` — not when value equals `"empty"` |
|
|
107
|
+
| `frozen?` | Ruby `Object` | Reflects freeze state of the object |
|
|
108
|
+
|
|
109
|
+
**When generating or reviewing code:** if an attribute's domain values include
|
|
110
|
+
`nil`, `blank`, `present`, `empty`, or `frozen`, flag this and suggest direct
|
|
111
|
+
string comparison (`== 'blank'`) rather than a predicate.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
95
115
|
## Architecture — the `instance_method` capture pattern
|
|
96
116
|
|
|
97
117
|
This is the **most important** design decision in `concern.rb`:
|
|
@@ -166,9 +186,11 @@ auto-include anything on load — that would be implicit and hard to audit.
|
|
|
166
186
|
### `NilInquiry` (`lib/inquiry_attrs/nil_inquiry.rb`)
|
|
167
187
|
|
|
168
188
|
- Frozen singleton: `NilInquiry::INSTANCE`
|
|
169
|
-
- `nil?` → `true`; `blank?` → `true`; `present?` → `false`
|
|
189
|
+
- `nil?` → `true`; `blank?` → `true`; `empty?` → `true`; `present?` → `false`
|
|
170
190
|
- Any `?`-method → `false` via `method_missing`
|
|
171
191
|
- `== nil`, `== ""`, `== INSTANCE` → `true`
|
|
192
|
+
- `is_a?(NilClass)`, `kind_of?(NilClass)`, `instance_of?(NilClass)` → `true`
|
|
193
|
+
(`NilClass` cannot be subclassed; methods are overridden explicitly)
|
|
172
194
|
- Implements `to_s` / `to_str` / `inspect`
|
|
173
195
|
|
|
174
196
|
### `SymbolInquiry` (`lib/inquiry_attrs/symbol_inquiry.rb`)
|
|
@@ -177,7 +199,8 @@ auto-include anything on load — that would be implicit and hard to audit.
|
|
|
177
199
|
- Raises `ArgumentError` for non-Symbol; unwraps nested `SymbolInquiry`
|
|
178
200
|
- `?`-method returns `true` iff `sym.to_s == method_name.delete_suffix('?')`
|
|
179
201
|
- `==` accepts `Symbol`, `String`, or `SymbolInquiry`
|
|
180
|
-
- `is_a?(Symbol)
|
|
202
|
+
- `is_a?(Symbol)`, `kind_of?(Symbol)`, `instance_of?(Symbol)` → `true`
|
|
203
|
+
(`Symbol` cannot be subclassed; methods are overridden explicitly)
|
|
181
204
|
- `nil?` → `false`; `blank?` → `false`; `present?` → `true`
|
|
182
205
|
|
|
183
206
|
### `Concern` (`lib/inquiry_attrs/concern.rb`)
|
|
@@ -219,6 +242,7 @@ auto-include anything on load — that would be implicit and hard to audit.
|
|
|
219
242
|
| Stub `Rails.root` in tests | Use `Installer.install!(tmpdir)` instead |
|
|
220
243
|
| Add a dependency on anything outside ActiveSupport/ActiveRecord/Railties | This is a Rails gem; Rails is already the dependency |
|
|
221
244
|
| Introduce allocation inside the hot path (e.g. `String.new.extend(...)`) | `NilInquiry::INSTANCE` is a frozen singleton for a reason |
|
|
245
|
+
| Suggest `.blank?` / `.nil?` / `.present?` / `.empty?` / `.frozen?` as inquiry predicates for those exact values | These are real methods, not inquiry predicates — they test object state, not string equality. Use `== 'blank'` etc. instead |
|
|
222
246
|
|
|
223
247
|
---
|
|
224
248
|
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,27 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.0.2] — 2026-02-27
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`NilInquiry#is_a?` / `#kind_of?` / `#instance_of?`** — all three type-check
|
|
19
|
+
methods now return `true` when called with `NilClass` as the argument.
|
|
20
|
+
`NilClass` cannot be subclassed in Ruby, so the methods are overridden
|
|
21
|
+
explicitly (the same technique already used by `SymbolInquiry` for `Symbol`).
|
|
22
|
+
`is_a?(InquiryAttrs::NilInquiry)` continues to return `true`.
|
|
23
|
+
|
|
24
|
+
- **`SymbolInquiry#kind_of?` / `#instance_of?`** — aliased to the existing
|
|
25
|
+
`is_a?` override so all three type-check methods consistently return `true`
|
|
26
|
+
for `Symbol` and the `SymbolInquiry` class itself.
|
|
27
|
+
|
|
28
|
+
- **README — ⚠️ Reserved predicate names** — new section documenting that
|
|
29
|
+
attribute values whose names match built-in Ruby/Rails `?`-methods (`nil`,
|
|
30
|
+
`blank`, `present`, `empty`, `frozen`) will invoke the real method rather than
|
|
31
|
+
testing string equality, and explaining the safe `== 'value'` alternative.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
14
35
|
## [1.0.0] — 2026-02-27
|
|
15
36
|
|
|
16
37
|
### Added
|
|
@@ -80,5 +101,6 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
80
101
|
|
|
81
102
|
---
|
|
82
103
|
|
|
83
|
-
[Unreleased]: https://github.com/pniemczyk/inquiry_attrs/compare/v1.0.
|
|
104
|
+
[Unreleased]: https://github.com/pniemczyk/inquiry_attrs/compare/v1.0.2...HEAD
|
|
105
|
+
[1.0.2]: https://github.com/pniemczyk/inquiry_attrs/compare/v1.0.0...v1.0.2
|
|
84
106
|
[1.0.0]: https://github.com/pniemczyk/inquiry_attrs/releases/tag/v1.0.0
|
data/CLAUDE.md
CHANGED
|
@@ -110,3 +110,28 @@ gem build inquiry_attrs.gemspec
|
|
|
110
110
|
- **Never** call `inquirer` before `attr_accessor` in plain Ruby classes
|
|
111
111
|
- **Never** broaden the `rescue` in `concern.rb`
|
|
112
112
|
- **Never** stub `Rails.root` in tests — use `Installer.install!(tmpdir)` instead
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Reserved predicate names — gotcha
|
|
117
|
+
|
|
118
|
+
Some predicate names are **real methods** on the objects `inquiry_attrs` returns.
|
|
119
|
+
They are **never** handled by `method_missing` and do **not** test string equality.
|
|
120
|
+
|
|
121
|
+
| Predicate | Actual behaviour |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `.nil?` | Always `false` for present values; always `true` for blank (`NilInquiry`) |
|
|
124
|
+
| `.blank?` | Tests blankness (nil / "" / whitespace) — not `value == "blank"` |
|
|
125
|
+
| `.present?` | Opposite of `blank?` — not `value == "present"` |
|
|
126
|
+
| `.empty?` | `true` only for `""` — not `value == "empty"` |
|
|
127
|
+
| `.frozen?` | Reflects the object's freeze state |
|
|
128
|
+
|
|
129
|
+
**When writing or reviewing code:** if a domain value matches one of the names
|
|
130
|
+
above, use direct comparison instead:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
record.state == 'blank' # ✅ correct
|
|
134
|
+
record.state.blank? # ❌ tests blankness, not state == "blank"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
See `README.md` → "⚠️ Reserved predicate names" for the full worked example.
|
data/README.md
CHANGED
|
@@ -121,6 +121,52 @@ sub.state.nil? # => true
|
|
|
121
121
|
|
|
122
122
|
---
|
|
123
123
|
|
|
124
|
+
## ⚠️ Reserved predicate names
|
|
125
|
+
|
|
126
|
+
Some predicate names are **already defined as real methods** on the objects
|
|
127
|
+
`inquiry_attrs` returns. Calling them does **not** test whether the attribute
|
|
128
|
+
value equals that word — the existing method is called instead and
|
|
129
|
+
`method_missing` is never reached.
|
|
130
|
+
|
|
131
|
+
| Value / predicate | Already defined by | What it actually tests |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `"nil"` / `.nil?` | Ruby `Object#nil?` | Whether the object is `nil` — always `false` for present strings, always `true` for blank values |
|
|
134
|
+
| `"blank"` / `.blank?` | ActiveSupport `Object#blank?` | Whether the value is blank (`nil`, `""`, whitespace) — **not** whether it equals `"blank"` |
|
|
135
|
+
| `"present"` / `.present?` | ActiveSupport `Object#present?` | Opposite of `blank?` — **not** whether it equals `"present"` |
|
|
136
|
+
| `"empty"` / `.empty?` | Ruby `String#empty?` | Whether the string is `""` — **not** whether it equals `"empty"` |
|
|
137
|
+
| `"frozen"` / `.frozen?` | Ruby `Object#frozen?` | Whether the object is frozen — **not** whether it equals `"frozen"` |
|
|
138
|
+
|
|
139
|
+
### Example of the problem
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
class Order < ApplicationRecord
|
|
143
|
+
inquirer :state
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# ❌ Misleading — .blank? tests blankness, not state == "blank"
|
|
147
|
+
order = Order.new(state: 'blank')
|
|
148
|
+
order.state.blank? # => false ("blank" is a non-empty string, so not blank)
|
|
149
|
+
|
|
150
|
+
# ❌ Misleading — .present? tests non-blankness, not state == "present"
|
|
151
|
+
order = Order.new(state: 'present')
|
|
152
|
+
order.state.present? # => true (any non-blank string is present)
|
|
153
|
+
|
|
154
|
+
# ❌ Misleading — .nil? tests object identity, not state == "nil"
|
|
155
|
+
order = Order.new(state: 'nil')
|
|
156
|
+
order.state.nil? # => false (it is a StringInquirer, not nil)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Rule of thumb:** if your domain uses values such as `nil`, `blank`, `present`,
|
|
160
|
+
`empty`, or `frozen`, use direct string comparison instead of a predicate:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
order.state == 'blank' # ✅ reliable
|
|
164
|
+
order.state == 'present' # ✅ reliable
|
|
165
|
+
order.state == 'nil' # ✅ reliable
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
124
170
|
## How it works
|
|
125
171
|
|
|
126
172
|
`.inquirer :attr` wraps the original attribute reader and returns one of three
|
|
@@ -132,31 +178,42 @@ objects based on the raw value:
|
|
|
132
178
|
| `Symbol` | `InquiryAttrs::SymbolInquiry` | `:active.active?` → `true` |
|
|
133
179
|
| Any other string | `ActiveSupport::StringInquirer` | Standard Rails inquiry |
|
|
134
180
|
|
|
181
|
+
> **Note:** if an attribute value shares a name with a built-in Ruby/Rails
|
|
182
|
+
> predicate (`"nil"`, `"blank"`, `"present"`, `"empty"`, `"frozen"`) the real
|
|
183
|
+
> method will be called — not a string-equality check. See
|
|
184
|
+
> [⚠️ Reserved predicate names](#️-reserved-predicate-names) for details.
|
|
185
|
+
|
|
135
186
|
### `InquiryAttrs::NilInquiry`
|
|
136
187
|
|
|
137
188
|
A frozen singleton returned for blank attributes. Every `?`-method returns
|
|
138
|
-
`false`; behaves like `nil` in comparisons
|
|
189
|
+
`false`; behaves like `nil` in comparisons, `blank?` checks, and type
|
|
190
|
+
introspection.
|
|
139
191
|
|
|
140
192
|
```ruby
|
|
141
193
|
ni = InquiryAttrs::NilInquiry::INSTANCE
|
|
142
|
-
ni.nil?
|
|
143
|
-
ni.active?
|
|
144
|
-
ni == nil
|
|
145
|
-
ni.blank?
|
|
194
|
+
ni.nil? # => true
|
|
195
|
+
ni.active? # => false
|
|
196
|
+
ni == nil # => true
|
|
197
|
+
ni.blank? # => true
|
|
198
|
+
ni.is_a?(NilClass) # => true
|
|
199
|
+
ni.kind_of?(NilClass) # => true
|
|
200
|
+
ni.instance_of?(NilClass) # => true
|
|
146
201
|
```
|
|
147
202
|
|
|
148
203
|
### `InquiryAttrs::SymbolInquiry`
|
|
149
204
|
|
|
150
205
|
Wraps a Symbol with predicate methods; compares equal to both the symbol and
|
|
151
|
-
its string equivalent.
|
|
206
|
+
its string equivalent; reports itself as a `Symbol` in all type-check methods.
|
|
152
207
|
|
|
153
208
|
```ruby
|
|
154
209
|
si = InquiryAttrs::SymbolInquiry.new(:active)
|
|
155
|
-
si.active?
|
|
156
|
-
si == :active
|
|
157
|
-
si == 'active'
|
|
158
|
-
si.is_a?(Symbol)
|
|
159
|
-
si.
|
|
210
|
+
si.active? # => true
|
|
211
|
+
si == :active # => true
|
|
212
|
+
si == 'active' # => true
|
|
213
|
+
si.is_a?(Symbol) # => true
|
|
214
|
+
si.kind_of?(Symbol) # => true
|
|
215
|
+
si.instance_of?(Symbol) # => true
|
|
216
|
+
si.to_s # => "active"
|
|
160
217
|
```
|
|
161
218
|
|
|
162
219
|
---
|
|
@@ -172,7 +229,6 @@ Auto-included into `ActiveRecord::Base`. Include manually in other classes.
|
|
|
172
229
|
```ruby
|
|
173
230
|
inquirer :status # single attribute
|
|
174
231
|
inquirer :status, :role, :plan # multiple attributes
|
|
175
|
-
inquirer :status, only: :show # passes options to before_action style macros (AR scoped)
|
|
176
232
|
```
|
|
177
233
|
|
|
178
234
|
---
|
|
@@ -181,9 +237,16 @@ inquirer :status, only: :show # passes options to before_action style
|
|
|
181
237
|
|
|
182
238
|
```bash
|
|
183
239
|
bundle install
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
240
|
+
|
|
241
|
+
# Full suite (preferred)
|
|
242
|
+
bundle exec rake
|
|
243
|
+
|
|
244
|
+
# Single file
|
|
245
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb
|
|
246
|
+
|
|
247
|
+
# Single test by name
|
|
248
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb \
|
|
249
|
+
--name test_matching_predicate_returns_true
|
|
187
250
|
```
|
|
188
251
|
|
|
189
252
|
---
|
|
@@ -25,6 +25,12 @@ module InquiryAttrs
|
|
|
25
25
|
method_name.to_s.end_with?('?') || super
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# NilClass cannot be subclassed in Ruby, so we override the type-check
|
|
29
|
+
# methods explicitly — the same technique used by SymbolInquiry for Symbol.
|
|
30
|
+
def is_a?(klass) = klass == NilClass || super
|
|
31
|
+
alias kind_of? is_a?
|
|
32
|
+
alias instance_of? is_a?
|
|
33
|
+
|
|
28
34
|
def nil? = true
|
|
29
35
|
def blank? = true
|
|
30
36
|
def empty? = true
|
data/llms/overview.md
CHANGED
|
@@ -64,14 +64,39 @@ inquirer :status, :role, :plan # wraps multiple readers at once
|
|
|
64
64
|
| `Symbol` | `InquiryAttrs::SymbolInquiry.new(raw)` |
|
|
65
65
|
| Any other string | `raw.to_s.inquiry` → `ActiveSupport::StringInquirer` |
|
|
66
66
|
|
|
67
|
+
## Reserved predicate names
|
|
68
|
+
|
|
69
|
+
Several predicate names are **already defined as real methods** on the returned
|
|
70
|
+
objects. `method_missing` is never reached for them, so they **do not** test
|
|
71
|
+
whether the raw attribute value equals that word — they invoke the existing method.
|
|
72
|
+
|
|
73
|
+
| Predicate | Defined by | What it tests |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| `nil?` | Ruby `Object` | Whether the object is `nil` — always `false` for present values |
|
|
76
|
+
| `blank?` | ActiveSupport | Whether the value is blank (nil / "" / whitespace) |
|
|
77
|
+
| `present?` | ActiveSupport | Opposite of `blank?` |
|
|
78
|
+
| `empty?` | Ruby `String` / `NilInquiry` | Whether the string is `""` |
|
|
79
|
+
| `frozen?` | Ruby `Object` | Whether the object is frozen |
|
|
80
|
+
|
|
81
|
+
**Code generation rule:** when a model's attribute domain includes values whose
|
|
82
|
+
names match the table above (e.g. a `state` column that can hold `"blank"` or
|
|
83
|
+
`"nil"`), use direct string comparison — never a predicate:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
record.state == 'blank' # ✅ tests string equality
|
|
87
|
+
record.state.blank? # ❌ tests blankness, not state == "blank"
|
|
88
|
+
```
|
|
89
|
+
|
|
67
90
|
## Class responsibilities
|
|
68
91
|
|
|
69
92
|
### `InquiryAttrs::NilInquiry`
|
|
70
93
|
|
|
71
94
|
- Frozen singleton (`INSTANCE`)
|
|
72
|
-
- `nil?` → `true`; `blank?` → `true`; `present?` → `false`
|
|
73
|
-
- Every `?`-method → `false` via `method_missing`
|
|
95
|
+
- `nil?` → `true`; `blank?` → `true`; `empty?` → `true`; `present?` → `false`
|
|
96
|
+
- Every `?`-method → `false` via `method_missing` (except the explicit overrides above)
|
|
74
97
|
- `== nil`, `== ""`, `== INSTANCE` → `true`
|
|
98
|
+
- `is_a?(NilClass)`, `kind_of?(NilClass)`, `instance_of?(NilClass)` → `true`
|
|
99
|
+
(`NilClass` cannot be subclassed; overridden explicitly)
|
|
75
100
|
- Implements `to_s`, `to_str`, `inspect`
|
|
76
101
|
|
|
77
102
|
### `InquiryAttrs::SymbolInquiry < SimpleDelegator`
|
|
@@ -80,7 +105,8 @@ inquirer :status, :role, :plan # wraps multiple readers at once
|
|
|
80
105
|
- Unwraps nested `SymbolInquiry` on init
|
|
81
106
|
- Any `?`-method returns `true` iff `sym.to_s == method_name.delete_suffix('?')`
|
|
82
107
|
- `==` accepts `Symbol`, `String`, or `SymbolInquiry`
|
|
83
|
-
- `is_a?(Symbol)` → `true`
|
|
108
|
+
- `is_a?(Symbol)`, `kind_of?(Symbol)`, `instance_of?(Symbol)` → `true`
|
|
109
|
+
(`Symbol` cannot be subclassed; overridden explicitly)
|
|
84
110
|
- `nil?` → `false`; `blank?` → `false`; `present?` → `true`
|
|
85
111
|
|
|
86
112
|
### `InquiryAttrs::Concern`
|