inquiry_attrs 1.0.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
- data/AGENTS.md +245 -0
- data/CHANGELOG.md +11 -0
- data/CLAUDE.md +112 -0
- data/LICENSE +21 -0
- data/README.md +186 -0
- data/lib/inquiry_attrs/concern.rb +77 -0
- data/lib/inquiry_attrs/installer.rb +59 -0
- data/lib/inquiry_attrs/nil_inquiry.rb +40 -0
- data/lib/inquiry_attrs/railtie.rb +12 -0
- data/lib/inquiry_attrs/symbol_inquiry.rb +55 -0
- data/lib/inquiry_attrs/version.rb +5 -0
- data/lib/inquiry_attrs.rb +50 -0
- data/lib/tasks/inquiry_attrs.rake +27 -0
- data/llms/overview.md +129 -0
- data/llms/usage.md +204 -0
- metadata +105 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 615f0028c78433e7cd13171ce70724af8e5c535c9bc3ddac9127b6121cec6cc2
|
|
4
|
+
data.tar.gz: 95d371d0ee8d484e6f80b353a62dc49c92d19df63eeb08c35abaccccacb1738f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f1327f64dc6cc2aec05aa5ae6b97619ba1bfa95cfe394633c44f7e7ca67a8d2fc4da5cb9cd58c114b234dde4eef3e5db973a35ed19e9678c23befd188b4d4b5b
|
|
7
|
+
data.tar.gz: a4ce3e81aab2b778aff6abf4c8532e5ed74765f457b49efc9a90134bf803d94f4396e5a3706875b73fa32159634e816779f4ad973b365f0ea105bc7ecd4bb8e5
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# AGENTS.md — inquiry_attrs
|
|
2
|
+
|
|
3
|
+
This file is the primary context document for AI agents and LLMs working on
|
|
4
|
+
this gem. Read it fully before making any changes.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What this gem does
|
|
9
|
+
|
|
10
|
+
`inquiry_attrs` adds predicate-style inquiry methods to Rails model attributes.
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Without inquiry_attrs
|
|
14
|
+
user.status == 'active'
|
|
15
|
+
|
|
16
|
+
# With inquiry_attrs
|
|
17
|
+
user.status.active? # => true
|
|
18
|
+
user.status.inactive? # => false
|
|
19
|
+
user.status.nil? # => true when blank — never raises NoMethodError
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
It is a **Rails-only** gem (depends on `activesupport >= 7`, `activerecord >= 7`,
|
|
23
|
+
`railties >= 7`). All Rails APIs (`blank?`, `ActiveSupport::Concern`,
|
|
24
|
+
`ActiveSupport.on_load`, `String#inquiry`) are available and should be preferred
|
|
25
|
+
over reinventing them.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## File map
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
lib/
|
|
33
|
+
inquiry_attrs.rb # Entry point — requires everything, loads Railtie
|
|
34
|
+
inquiry_attrs/
|
|
35
|
+
version.rb # VERSION = '1.0.0'
|
|
36
|
+
nil_inquiry.rb # NilInquiry::INSTANCE — frozen singleton for blank values
|
|
37
|
+
symbol_inquiry.rb # SymbolInquiry < SimpleDelegator — wraps Symbol attrs
|
|
38
|
+
concern.rb # Concern — adds .inquirer class macro
|
|
39
|
+
installer.rb # Installer — file-system logic for the rake tasks
|
|
40
|
+
railtie.rb # Railtie — wires rake tasks into the host Rails app
|
|
41
|
+
tasks/
|
|
42
|
+
inquiry_attrs.rake # Shell rake tasks (install / uninstall)
|
|
43
|
+
|
|
44
|
+
test/
|
|
45
|
+
test_helper.rb # Minitest setup + SQLite in-memory AR connection
|
|
46
|
+
inquiry_attrs/
|
|
47
|
+
nil_inquiry_test.rb # Unit tests for NilInquiry
|
|
48
|
+
symbol_inquiry_test.rb # Unit tests for SymbolInquiry
|
|
49
|
+
concern_test.rb # Integration tests: AR, StoreModel, plain Ruby, Symbol
|
|
50
|
+
install_task_test.rb # Unit tests for Installer (no Rake machinery needed)
|
|
51
|
+
|
|
52
|
+
llms/
|
|
53
|
+
overview.md # Architecture deep-dive for LLMs
|
|
54
|
+
usage.md # Common patterns and recipes
|
|
55
|
+
|
|
56
|
+
AGENTS.md # This file
|
|
57
|
+
README.md
|
|
58
|
+
CHANGELOG.md
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## How to run tests
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Full suite (preferred)
|
|
67
|
+
bundle exec rake
|
|
68
|
+
|
|
69
|
+
# Single file
|
|
70
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb
|
|
71
|
+
|
|
72
|
+
# Single test by name
|
|
73
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb \
|
|
74
|
+
--name test_matching_predicate_returns_true
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Tests use **Minitest** (`minitest-reporters` with SpecReporter). There is no
|
|
78
|
+
Rails application — ActiveRecord is wired directly to an in-memory SQLite
|
|
79
|
+
database in `test/test_helper.rb`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Architecture — the three return types
|
|
84
|
+
|
|
85
|
+
`.inquirer :attr` overrides the original attribute reader and returns one of:
|
|
86
|
+
|
|
87
|
+
| Raw value | Return type | Key behaviour |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `nil` or any `blank?` value | `NilInquiry::INSTANCE` | `nil?` true, all predicates false |
|
|
90
|
+
| `Symbol` | `SymbolInquiry.new(raw)` | predicate matches symbol name |
|
|
91
|
+
| Everything else | `raw.to_s.inquiry` → `ActiveSupport::StringInquirer` | standard Rails inquiry |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Architecture — the `instance_method` capture pattern
|
|
96
|
+
|
|
97
|
+
This is the **most important** design decision in `concern.rb`:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
def inquirer(*attrs)
|
|
101
|
+
attrs.each do |attr|
|
|
102
|
+
original = method_defined?(attr) ? instance_method(attr) : nil
|
|
103
|
+
|
|
104
|
+
# Remove the method from this class's own table before redefining it to
|
|
105
|
+
# silence Ruby's "method redefined" warning. The original is already
|
|
106
|
+
# safely captured in `original` above.
|
|
107
|
+
remove_method(attr) if instance_methods(false).include?(attr)
|
|
108
|
+
|
|
109
|
+
define_method(attr) do
|
|
110
|
+
raw = original ? original.bind_call(self) : super()
|
|
111
|
+
# … return type logic
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Why capture + remove + redefine:** `inquirer :status` must replace the original
|
|
118
|
+
reader. Both AR and StoreModel define their readers via `define_method`; calling
|
|
119
|
+
`define_method` again on the same method triggers Ruby's "method redefined"
|
|
120
|
+
warning. Calling `remove_method` first clears it from the class's own method
|
|
121
|
+
table so the subsequent `define_method` is seen as a fresh definition.
|
|
122
|
+
`remove_method` is safe here because it only removes the method from *this*
|
|
123
|
+
class, not from superclasses, and the original is already preserved in `original`.
|
|
124
|
+
|
|
125
|
+
**Consequence:** `inquirer` must always be called **after** the attribute reader
|
|
126
|
+
is defined. Violating this order produces a `NoMethodError` in plain Ruby classes
|
|
127
|
+
(AR and StoreModel define readers early enough for this not to matter).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Architecture — Railtie / rake task / Installer split
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
Rails app inquiry_attrs gem
|
|
135
|
+
───────────────── ──────────────────────────────────────────────────
|
|
136
|
+
Gemfile ──requires──▶ lib/inquiry_attrs.rb
|
|
137
|
+
└── lib/inquiry_attrs/railtie.rb (only if Rails::Railtie defined)
|
|
138
|
+
└── rake_tasks { load 'lib/tasks/inquiry_attrs.rake' }
|
|
139
|
+
|
|
140
|
+
$ rails inquiry_attrs:install
|
|
141
|
+
──▶ task :install
|
|
142
|
+
└── InquiryAttrs::Installer.install!(Rails.root)
|
|
143
|
+
└── writes config/initializers/inquiry_attrs.rb
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Why Installer is a separate class:** The rake task calls `Rails.root`, which is
|
|
147
|
+
unavailable in tests without a full Rails boot. By pushing all logic into
|
|
148
|
+
`Installer.install!(root)`, tests can pass any `Pathname` as the root — no
|
|
149
|
+
stubbing, no Rake DSL needed.
|
|
150
|
+
|
|
151
|
+
**The generated initializer** contains:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
ActiveSupport.on_load(:active_record) do
|
|
155
|
+
include InquiryAttrs::Concern
|
|
156
|
+
end
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
This is the **only** place where `on_load` is used. The gem itself does not
|
|
160
|
+
auto-include anything on load — that would be implicit and hard to audit.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Key classes — quick reference
|
|
165
|
+
|
|
166
|
+
### `NilInquiry` (`lib/inquiry_attrs/nil_inquiry.rb`)
|
|
167
|
+
|
|
168
|
+
- Frozen singleton: `NilInquiry::INSTANCE`
|
|
169
|
+
- `nil?` → `true`; `blank?` → `true`; `present?` → `false`
|
|
170
|
+
- Any `?`-method → `false` via `method_missing`
|
|
171
|
+
- `== nil`, `== ""`, `== INSTANCE` → `true`
|
|
172
|
+
- Implements `to_s` / `to_str` / `inspect`
|
|
173
|
+
|
|
174
|
+
### `SymbolInquiry` (`lib/inquiry_attrs/symbol_inquiry.rb`)
|
|
175
|
+
|
|
176
|
+
- Subclasses `SimpleDelegator`, wraps a `Symbol`
|
|
177
|
+
- Raises `ArgumentError` for non-Symbol; unwraps nested `SymbolInquiry`
|
|
178
|
+
- `?`-method returns `true` iff `sym.to_s == method_name.delete_suffix('?')`
|
|
179
|
+
- `==` accepts `Symbol`, `String`, or `SymbolInquiry`
|
|
180
|
+
- `is_a?(Symbol)` and `kind_of?(Symbol)` → `true`
|
|
181
|
+
- `nil?` → `false`; `blank?` → `false`; `present?` → `true`
|
|
182
|
+
|
|
183
|
+
### `Concern` (`lib/inquiry_attrs/concern.rb`)
|
|
184
|
+
|
|
185
|
+
- `extend ActiveSupport::Concern`
|
|
186
|
+
- Single class method: `inquirer(*attrs)` — see `instance_method` capture pattern above
|
|
187
|
+
- Uses `blank?` (Rails) instead of `nil? || == ""`
|
|
188
|
+
|
|
189
|
+
### `Installer` (`lib/inquiry_attrs/installer.rb`)
|
|
190
|
+
|
|
191
|
+
- `Installer::INITIALIZER_PATH` — `Pathname` relative path to the initializer
|
|
192
|
+
- `Installer::INITIALIZER_CONTENT` — the exact string written to disk
|
|
193
|
+
- `Installer.install!(rails_root)` → `:created` or `:skipped`
|
|
194
|
+
- `Installer.uninstall!(rails_root)` → `:removed` or `:skipped`
|
|
195
|
+
- Accepts `Pathname` or `String` as `rails_root`
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Adding a new feature — checklist
|
|
200
|
+
|
|
201
|
+
1. **Write the test first** in the appropriate `test/inquiry_attrs/*_test.rb` file.
|
|
202
|
+
2. Implement in `lib/inquiry_attrs/`.
|
|
203
|
+
3. If the feature touches `Installer` (file operations), test via `InstallerTest`
|
|
204
|
+
passing a `Dir.mktmpdir` path — never stub `Rails.root`.
|
|
205
|
+
4. If the feature is a new public API, document it in `llms/overview.md` and
|
|
206
|
+
`llms/usage.md` and update `README.md`.
|
|
207
|
+
5. Run the full test suite and confirm 0 failures.
|
|
208
|
+
6. Update `CHANGELOG.md`.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Things to never do
|
|
213
|
+
|
|
214
|
+
| Don't | Why |
|
|
215
|
+
|---|---|
|
|
216
|
+
| Add `on_load` back to `lib/inquiry_attrs.rb` | Makes the gem implicitly modify every AR model; hard to audit |
|
|
217
|
+
| Broaden the `rescue` in `concern.rb` | Swallows real errors in attribute readers |
|
|
218
|
+
| Call `inquirer` before `attr_accessor` in plain Ruby | `instance_method` capture returns `nil`; reader will be `nil` |
|
|
219
|
+
| Stub `Rails.root` in tests | Use `Installer.install!(tmpdir)` instead |
|
|
220
|
+
| Add a dependency on anything outside ActiveSupport/ActiveRecord/Railties | This is a Rails gem; Rails is already the dependency |
|
|
221
|
+
| Introduce allocation inside the hot path (e.g. `String.new.extend(...)`) | `NilInquiry::INSTANCE` is a frozen singleton for a reason |
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Dependencies
|
|
226
|
+
|
|
227
|
+
| Gem | Version | Why |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| `activesupport` | `>= 7.0` | `Concern`, `blank?`, `String#inquiry`, `on_load` |
|
|
230
|
+
| `activerecord` | `>= 7.0` | AR integration (attr readers, `on_load` hook) |
|
|
231
|
+
| `railties` | `>= 7.0` | `Rails::Railtie` for exposing rake tasks |
|
|
232
|
+
| `sqlite3` | dev only | In-memory DB for AR tests |
|
|
233
|
+
| `store_model` | dev only | Integration tests for StoreModel |
|
|
234
|
+
| `minitest` + `minitest-reporters` | dev only | Test framework |
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Test conventions
|
|
239
|
+
|
|
240
|
+
- Use `Minitest::Test` (not `ActiveSupport::TestCase`)
|
|
241
|
+
- AR schema is created in `setup` with `force: true` and dropped in `teardown`
|
|
242
|
+
- `InstallerTest` uses `Dir.mktmpdir` — always clean up in `teardown`
|
|
243
|
+
- `assert` / `refute` preferred over `assert_equal true/false`
|
|
244
|
+
- Group related tests with comment banners: `# ── install! ── #`
|
|
245
|
+
- Test method names describe the exact behaviour: `test_install_skips_when_initializer_already_exists`
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0] — 2026-02-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `InquiryAttrs::Concern` with `.inquirer(*attrs)` class macro
|
|
7
|
+
- `InquiryAttrs::SymbolInquiry` — predicate wrapper for Symbol attributes
|
|
8
|
+
- `InquiryAttrs::NilInquiry::INSTANCE` — frozen singleton for blank/nil attributes
|
|
9
|
+
- Auto-include into `ActiveRecord::Base` via `ActiveSupport.on_load(:active_record)`
|
|
10
|
+
- Support for StoreModel and plain Ruby classes via explicit `include`
|
|
11
|
+
- LLM context files in `llms/`
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# CLAUDE.md — inquiry_attrs
|
|
2
|
+
|
|
3
|
+
## Start here
|
|
4
|
+
|
|
5
|
+
Before writing any code, read these files in order:
|
|
6
|
+
|
|
7
|
+
1. **@AGENTS.md** — architecture, design decisions, guardrails, test conventions
|
|
8
|
+
2. **@llms/overview.md** — class responsibilities and internal design notes
|
|
9
|
+
3. **@llms/usage.md** — common patterns and recipes
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Project context
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
|---|---|
|
|
17
|
+
| **Gem name** | `inquiry_attrs` |
|
|
18
|
+
| **Type** | Rails gem (Ruby only, no frontend) |
|
|
19
|
+
| **Ruby** | ≥ 3.0 |
|
|
20
|
+
| **Rails deps** | `activesupport`, `activerecord`, `railties` — all ≥ 7.0 |
|
|
21
|
+
| **Test framework** | Minitest (`minitest-reporters`, SpecReporter) |
|
|
22
|
+
| **Database** | SQLite in-memory (tests only) |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Running tests
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Full suite (preferred)
|
|
30
|
+
bundle exec rake
|
|
31
|
+
|
|
32
|
+
# Full suite (explicit)
|
|
33
|
+
bundle exec rake test
|
|
34
|
+
|
|
35
|
+
# Single file
|
|
36
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb
|
|
37
|
+
|
|
38
|
+
# Single test by name
|
|
39
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb \
|
|
40
|
+
--name test_matching_predicate_returns_true
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Key files — one-line each
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
lib/inquiry_attrs/nil_inquiry.rb # NilInquiry::INSTANCE — frozen singleton for blank values
|
|
49
|
+
lib/inquiry_attrs/symbol_inquiry.rb # SymbolInquiry < SimpleDelegator — wraps Symbol attrs
|
|
50
|
+
lib/inquiry_attrs/concern.rb # .inquirer macro — instance_method capture pattern
|
|
51
|
+
lib/inquiry_attrs/installer.rb # Installer.install!/uninstall! — file-system logic
|
|
52
|
+
lib/inquiry_attrs/railtie.rb # loads rake tasks into the host Rails app
|
|
53
|
+
lib/tasks/inquiry_attrs.rake # rails inquiry_attrs:install / :uninstall
|
|
54
|
+
test/test_helper.rb # Minitest setup + SQLite + on_load simulation
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Code style
|
|
60
|
+
|
|
61
|
+
Follow **@~/.agent-os/standards/code-style.md** and **@~/.agent-os/standards/best-practices.md**.
|
|
62
|
+
|
|
63
|
+
Key rules that apply to this gem:
|
|
64
|
+
|
|
65
|
+
- 2-space indentation, no tabs
|
|
66
|
+
- Single quotes for strings; double quotes only for interpolation
|
|
67
|
+
- `snake_case` methods/variables, `PascalCase` classes, `UPPER_SNAKE_CASE` constants
|
|
68
|
+
- Comment the *why*, not the *what*; never remove existing comments unless removing the associated code
|
|
69
|
+
- Keep it simple — fewest lines possible, no over-engineering
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Workflow
|
|
74
|
+
|
|
75
|
+
### Fixing a bug or adding a feature
|
|
76
|
+
|
|
77
|
+
1. Read **@AGENTS.md** → section "Adding a new feature — checklist"
|
|
78
|
+
2. Write the failing test first
|
|
79
|
+
3. Implement the fix
|
|
80
|
+
4. Run the full suite — must be 0 failures before committing
|
|
81
|
+
|
|
82
|
+
### Testing the rake task
|
|
83
|
+
|
|
84
|
+
The rake tasks are thin shells over `InquiryAttrs::Installer`. Test `Installer`
|
|
85
|
+
directly — pass a `Dir.mktmpdir` path as `rails_root`. Never stub `Rails.root`.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
def test_something
|
|
89
|
+
Dir.mktmpdir do |tmpdir|
|
|
90
|
+
root = Pathname.new(tmpdir)
|
|
91
|
+
FileUtils.mkdir_p(root.join('config', 'initializers'))
|
|
92
|
+
result = InquiryAttrs::Installer.install!(root)
|
|
93
|
+
assert_equal :created, result
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Building the gem
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
gem build inquiry_attrs.gemspec
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Hard rules (from @AGENTS.md)
|
|
107
|
+
|
|
108
|
+
- **Never** add `ActiveSupport.on_load` back to `lib/inquiry_attrs.rb` — the
|
|
109
|
+
on_load lives only in the generated initializer
|
|
110
|
+
- **Never** call `inquirer` before `attr_accessor` in plain Ruby classes
|
|
111
|
+
- **Never** broaden the `rescue` in `concern.rb`
|
|
112
|
+
- **Never** stub `Rails.root` in tests — use `Installer.install!(tmpdir)` instead
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# inquiry_attrs
|
|
2
|
+
|
|
3
|
+
Predicate-style inquiry methods for Rails model attributes.
|
|
4
|
+
|
|
5
|
+
Instead of comparing strings:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
user.status == 'active'
|
|
9
|
+
user.role == 'admin'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Write expressive predicates:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
user.status.active?
|
|
16
|
+
user.role.admin?
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Nil/blank attributes safely return `false` for every predicate — no more
|
|
20
|
+
`NoMethodError` on nil.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# Gemfile
|
|
28
|
+
gem 'inquiry_attrs'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
### ActiveRecord — zero configuration
|
|
36
|
+
|
|
37
|
+
`inquiry_attrs` auto-includes itself into every `ActiveRecord::Base` subclass
|
|
38
|
+
via `ActiveSupport.on_load(:active_record)`. Just call `.inquirer` in any model:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
class User < ApplicationRecord
|
|
42
|
+
inquirer :status, :kind
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
user = User.new(status: "active", kind: "admin")
|
|
46
|
+
user.status.active? # => true
|
|
47
|
+
user.status.inactive? # => false
|
|
48
|
+
user.kind.admin? # => true
|
|
49
|
+
user.kind.user? # => false
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Nil / blank attributes
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
user = User.new(status: nil)
|
|
56
|
+
|
|
57
|
+
user.status.nil? # => true
|
|
58
|
+
user.status.active? # => false ← no NoMethodError!
|
|
59
|
+
user.status == nil # => true
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### String comparison and String methods still work
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
user.status # => "active"
|
|
66
|
+
user.status == 'active' # => true
|
|
67
|
+
user.status.include?('act') # => true
|
|
68
|
+
user.status.upcase # => "ACTIVE"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## StoreModel
|
|
74
|
+
|
|
75
|
+
Include `InquiryAttrs::Concern` explicitly for non-AR classes:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
class ShippingAddress
|
|
79
|
+
include StoreModel::Model
|
|
80
|
+
include InquiryAttrs::Concern
|
|
81
|
+
|
|
82
|
+
attribute :kind, :string # "shipping", "billing", "return"
|
|
83
|
+
inquirer :kind
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
address = ShippingAddress.new(kind: "billing")
|
|
87
|
+
address.kind.billing? # => true
|
|
88
|
+
address.kind.shipping? # => false
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Plain Ruby
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
class Subscription
|
|
97
|
+
include InquiryAttrs::Concern
|
|
98
|
+
|
|
99
|
+
attr_accessor :plan, :state
|
|
100
|
+
|
|
101
|
+
def initialize(plan:, state: nil)
|
|
102
|
+
@plan = plan
|
|
103
|
+
@state = state
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Call inquirer AFTER attr_accessor — the original reader must exist first.
|
|
107
|
+
inquirer :plan, :state
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sub = Subscription.new(plan: 'enterprise')
|
|
111
|
+
sub.plan.enterprise? # => true
|
|
112
|
+
sub.state.nil? # => true
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## How it works
|
|
118
|
+
|
|
119
|
+
`.inquirer :attr` wraps the original attribute reader and returns one of three
|
|
120
|
+
objects based on the raw value:
|
|
121
|
+
|
|
122
|
+
| Raw value | Return type | Key behaviour |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `nil` or any blank value | `InquiryAttrs::NilInquiry::INSTANCE` | `nil?` → `true`, all predicates → `false` |
|
|
125
|
+
| `Symbol` | `InquiryAttrs::SymbolInquiry` | `:active.active?` → `true` |
|
|
126
|
+
| Any other string | `ActiveSupport::StringInquirer` | Standard Rails inquiry |
|
|
127
|
+
|
|
128
|
+
### `InquiryAttrs::NilInquiry`
|
|
129
|
+
|
|
130
|
+
A frozen singleton returned for blank attributes. Every `?`-method returns
|
|
131
|
+
`false`; behaves like `nil` in comparisons and `blank?` checks.
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
ni = InquiryAttrs::NilInquiry::INSTANCE
|
|
135
|
+
ni.nil? # => true
|
|
136
|
+
ni.active? # => false
|
|
137
|
+
ni == nil # => true
|
|
138
|
+
ni.blank? # => true
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### `InquiryAttrs::SymbolInquiry`
|
|
142
|
+
|
|
143
|
+
Wraps a Symbol with predicate methods; compares equal to both the symbol and
|
|
144
|
+
its string equivalent.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
si = InquiryAttrs::SymbolInquiry.new(:active)
|
|
148
|
+
si.active? # => true
|
|
149
|
+
si == :active # => true
|
|
150
|
+
si == 'active' # => true
|
|
151
|
+
si.is_a?(Symbol) # => true
|
|
152
|
+
si.to_s # => "active"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## API
|
|
158
|
+
|
|
159
|
+
### `InquiryAttrs::Concern`
|
|
160
|
+
|
|
161
|
+
Auto-included into `ActiveRecord::Base`. Include manually in other classes.
|
|
162
|
+
|
|
163
|
+
### `.inquirer(*attribute_names)`
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
inquirer :status # single attribute
|
|
167
|
+
inquirer :status, :role, :plan # multiple attributes
|
|
168
|
+
inquirer :status, only: :show # passes options to before_action style macros (AR scoped)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
bundle install
|
|
177
|
+
bundle exec ruby -Ilib -Itest test/inquiry_attrs/nil_inquiry_test.rb \
|
|
178
|
+
test/inquiry_attrs/symbol_inquiry_test.rb \
|
|
179
|
+
test/inquiry_attrs/concern_test.rb
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InquiryAttrs
|
|
4
|
+
# +ActiveSupport::Concern+ that adds the +.inquirer+ macro to any class.
|
|
5
|
+
#
|
|
6
|
+
# For ActiveRecord models this is automatically included via
|
|
7
|
+
# +ActiveSupport.on_load(:active_record)+ — no explicit include needed:
|
|
8
|
+
#
|
|
9
|
+
# class User < ApplicationRecord
|
|
10
|
+
# inquirer :status, :role
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# For StoreModel, plain Ruby, or any other class, include it manually:
|
|
14
|
+
#
|
|
15
|
+
# class Address
|
|
16
|
+
# include StoreModel::Model
|
|
17
|
+
# include InquiryAttrs::Concern
|
|
18
|
+
#
|
|
19
|
+
# attribute :kind, :string
|
|
20
|
+
# inquirer :kind
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
module Concern
|
|
24
|
+
extend ActiveSupport::Concern
|
|
25
|
+
|
|
26
|
+
class_methods do
|
|
27
|
+
# Wraps the named attribute readers with inquiry behaviour.
|
|
28
|
+
#
|
|
29
|
+
# Returns one of three objects depending on the raw value:
|
|
30
|
+
#
|
|
31
|
+
# * +InquiryAttrs::NilInquiry::INSTANCE+ — when the value is +blank?+
|
|
32
|
+
# * +InquiryAttrs::SymbolInquiry+ — when the value is a +Symbol+
|
|
33
|
+
# * +ActiveSupport::StringInquirer+ — for any other present value
|
|
34
|
+
# (identical to calling +value.inquiry+ on a string)
|
|
35
|
+
#
|
|
36
|
+
# @param attrs [Array<Symbol>] attribute reader names to wrap
|
|
37
|
+
def inquirer(*attrs)
|
|
38
|
+
attrs.each do |attr|
|
|
39
|
+
# Capture the original reader before we overwrite it.
|
|
40
|
+
# This covers attr_accessor, StoreModel, and AR attribute methods
|
|
41
|
+
# that are already defined by the time inquirer is called.
|
|
42
|
+
original = method_defined?(attr) ? instance_method(attr) : nil
|
|
43
|
+
|
|
44
|
+
# Remove the method from this class's own table (not from superclasses)
|
|
45
|
+
# so the define_method below is seen as a fresh definition rather than
|
|
46
|
+
# a redefinition — silencing Ruby's "method redefined" warning.
|
|
47
|
+
remove_method(attr) if instance_methods(false).include?(attr)
|
|
48
|
+
|
|
49
|
+
define_method(attr) do
|
|
50
|
+
raw = if original
|
|
51
|
+
original.bind_call(self)
|
|
52
|
+
else
|
|
53
|
+
# Lazy AR attribute methods or Dry::Struct hash access.
|
|
54
|
+
begin
|
|
55
|
+
super()
|
|
56
|
+
rescue NoMethodError
|
|
57
|
+
begin
|
|
58
|
+
self[attr]
|
|
59
|
+
rescue NoMethodError, TypeError
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if raw.blank?
|
|
66
|
+
NilInquiry::INSTANCE
|
|
67
|
+
elsif raw.is_a?(Symbol)
|
|
68
|
+
SymbolInquiry.new(raw)
|
|
69
|
+
else
|
|
70
|
+
raw.to_s.inquiry
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InquiryAttrs
|
|
4
|
+
# Manages the lifecycle of the inquiry_attrs initializer inside a Rails app.
|
|
5
|
+
#
|
|
6
|
+
# This class holds all file-system logic so it can be unit-tested without
|
|
7
|
+
# requiring a full Rails boot or Rake DSL. The rake tasks are thin shells
|
|
8
|
+
# that delegate here.
|
|
9
|
+
#
|
|
10
|
+
# @example From a Rake task (or Rails generator):
|
|
11
|
+
# InquiryAttrs::Installer.install!(Rails.root)
|
|
12
|
+
# InquiryAttrs::Installer.uninstall!(Rails.root)
|
|
13
|
+
#
|
|
14
|
+
class Installer
|
|
15
|
+
INITIALIZER_PATH = Pathname.new('config/initializers/inquiry_attrs.rb')
|
|
16
|
+
|
|
17
|
+
INITIALIZER_CONTENT = <<~RUBY
|
|
18
|
+
# frozen_string_literal: true
|
|
19
|
+
|
|
20
|
+
# Auto-include InquiryAttrs::Concern into every ActiveRecord model so that
|
|
21
|
+
# the .inquirer macro is available without an explicit include in each class.
|
|
22
|
+
#
|
|
23
|
+
# Generated by: rails inquiry_attrs:install
|
|
24
|
+
#
|
|
25
|
+
# To opt out of auto-include, remove this file and add
|
|
26
|
+
# include InquiryAttrs::Concern
|
|
27
|
+
# to whichever models need it.
|
|
28
|
+
ActiveSupport.on_load(:active_record) do
|
|
29
|
+
include InquiryAttrs::Concern
|
|
30
|
+
end
|
|
31
|
+
RUBY
|
|
32
|
+
|
|
33
|
+
# Write the initializer to +rails_root/config/initializers/inquiry_attrs.rb+.
|
|
34
|
+
#
|
|
35
|
+
# @param rails_root [Pathname, String] the root directory of the Rails app
|
|
36
|
+
# @return [:created, :skipped]
|
|
37
|
+
def self.install!(rails_root)
|
|
38
|
+
destination = Pathname.new(rails_root).join(INITIALIZER_PATH)
|
|
39
|
+
|
|
40
|
+
return :skipped if destination.exist?
|
|
41
|
+
|
|
42
|
+
destination.write(INITIALIZER_CONTENT)
|
|
43
|
+
:created
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Remove the initializer if it exists.
|
|
47
|
+
#
|
|
48
|
+
# @param rails_root [Pathname, String]
|
|
49
|
+
# @return [:removed, :skipped]
|
|
50
|
+
def self.uninstall!(rails_root)
|
|
51
|
+
destination = Pathname.new(rails_root).join(INITIALIZER_PATH)
|
|
52
|
+
|
|
53
|
+
return :skipped unless destination.exist?
|
|
54
|
+
|
|
55
|
+
destination.delete
|
|
56
|
+
:removed
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InquiryAttrs
|
|
4
|
+
# Returned by an inquired attribute when its raw value is blank.
|
|
5
|
+
#
|
|
6
|
+
# Every +?+ predicate returns +false+, so callers never receive a
|
|
7
|
+
# +NoMethodError+ and nil comparisons work naturally.
|
|
8
|
+
#
|
|
9
|
+
# user.status # => InquiryAttrs::NilInquiry::INSTANCE (when nil/blank)
|
|
10
|
+
# user.status.nil? # => true
|
|
11
|
+
# user.status.active? # => false
|
|
12
|
+
# user.status == nil # => true
|
|
13
|
+
#
|
|
14
|
+
class NilInquiry
|
|
15
|
+
INSTANCE = new.freeze
|
|
16
|
+
|
|
17
|
+
# Any +?+ method returns false — no NoMethodError on blank attributes.
|
|
18
|
+
def method_missing(method_name, *_args, &_block)
|
|
19
|
+
return false if method_name.to_s.end_with?('?')
|
|
20
|
+
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
25
|
+
method_name.to_s.end_with?('?') || super
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def nil? = true
|
|
29
|
+
def blank? = true
|
|
30
|
+
def empty? = true
|
|
31
|
+
def present? = false
|
|
32
|
+
def to_s = ''
|
|
33
|
+
def to_str = ''
|
|
34
|
+
def inspect = 'nil'
|
|
35
|
+
|
|
36
|
+
def ==(other)
|
|
37
|
+
other.nil? || other == '' || other.equal?(INSTANCE)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InquiryAttrs
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
railtie_name :inquiry_attrs
|
|
6
|
+
|
|
7
|
+
# Expose `rails inquiry_attrs:install` to the host application.
|
|
8
|
+
rake_tasks do
|
|
9
|
+
load File.expand_path('../../tasks/inquiry_attrs.rake', __dir__)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InquiryAttrs
|
|
4
|
+
# Wraps a Symbol to provide the same predicate interface as
|
|
5
|
+
# +ActiveSupport::StringInquirer+.
|
|
6
|
+
#
|
|
7
|
+
# inquiry = SymbolInquiry.new(:active)
|
|
8
|
+
# inquiry.active? # => true
|
|
9
|
+
# inquiry.inactive? # => false
|
|
10
|
+
# inquiry == :active # => true
|
|
11
|
+
# inquiry == 'active' # => true
|
|
12
|
+
# inquiry.is_a?(Symbol) # => true
|
|
13
|
+
#
|
|
14
|
+
class SymbolInquiry < SimpleDelegator
|
|
15
|
+
# @param sym [Symbol]
|
|
16
|
+
# @raise [ArgumentError] if the argument is not a Symbol
|
|
17
|
+
def initialize(sym)
|
|
18
|
+
raise ArgumentError, "Must be a Symbol, got #{sym.class}" unless sym.is_a?(Symbol)
|
|
19
|
+
|
|
20
|
+
super(sym.is_a?(SymbolInquiry) ? sym.__getobj__ : sym)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def method_missing(method_name, *_args, &_block)
|
|
24
|
+
if method_name.to_s.end_with?('?')
|
|
25
|
+
__getobj__.to_s == method_name.to_s.delete_suffix('?')
|
|
26
|
+
else
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
32
|
+
method_name.to_s.end_with?('?') || super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Compares against Symbol, String, or another SymbolInquiry.
|
|
36
|
+
def ==(other)
|
|
37
|
+
case other
|
|
38
|
+
when SymbolInquiry then __getobj__ == other.__getobj__
|
|
39
|
+
when Symbol then __getobj__ == other
|
|
40
|
+
when String then __getobj__.to_s == other
|
|
41
|
+
else false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def is_a?(klass) = klass == Symbol || super
|
|
46
|
+
alias kind_of? is_a?
|
|
47
|
+
|
|
48
|
+
def nil? = false
|
|
49
|
+
def blank? = false
|
|
50
|
+
def present? = true
|
|
51
|
+
def to_s = __getobj__.to_s
|
|
52
|
+
def to_sym = __getobj__
|
|
53
|
+
def inspect = ":#{__getobj__}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'active_support/concern'
|
|
5
|
+
require 'active_support/core_ext/object/blank'
|
|
6
|
+
require 'active_support/core_ext/string/inquiry'
|
|
7
|
+
|
|
8
|
+
require_relative 'inquiry_attrs/version'
|
|
9
|
+
require_relative 'inquiry_attrs/nil_inquiry'
|
|
10
|
+
require_relative 'inquiry_attrs/symbol_inquiry'
|
|
11
|
+
require_relative 'inquiry_attrs/concern'
|
|
12
|
+
require_relative 'inquiry_attrs/installer'
|
|
13
|
+
|
|
14
|
+
# InquiryAttrs adds predicate-style inquiry methods to Rails model attributes.
|
|
15
|
+
#
|
|
16
|
+
# Instead of comparing strings:
|
|
17
|
+
#
|
|
18
|
+
# user.status == 'active'
|
|
19
|
+
#
|
|
20
|
+
# Write expressive predicates:
|
|
21
|
+
#
|
|
22
|
+
# user.status.active?
|
|
23
|
+
#
|
|
24
|
+
# Nil/blank values safely return +false+ for all predicates — no more
|
|
25
|
+
# +NoMethodError+ on nil.
|
|
26
|
+
#
|
|
27
|
+
# Run the install task to wire the gem into your Rails app:
|
|
28
|
+
#
|
|
29
|
+
# rails inquiry_attrs:install
|
|
30
|
+
#
|
|
31
|
+
# That creates +config/initializers/inquiry_attrs.rb+ which calls
|
|
32
|
+
# +ActiveSupport.on_load(:active_record)+ so that every ActiveRecord model
|
|
33
|
+
# gets the +.inquirer+ macro with no explicit include.
|
|
34
|
+
#
|
|
35
|
+
# For StoreModel or plain Ruby classes, include the concern manually:
|
|
36
|
+
#
|
|
37
|
+
# class ShippingAddress
|
|
38
|
+
# include StoreModel::Model
|
|
39
|
+
# include InquiryAttrs::Concern
|
|
40
|
+
#
|
|
41
|
+
# attribute :kind, :string
|
|
42
|
+
# inquirer :kind
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
module InquiryAttrs
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Register the Railtie when running inside a Rails application.
|
|
49
|
+
# The Railtie exposes the `rails inquiry_attrs:install` rake task.
|
|
50
|
+
require 'inquiry_attrs/railtie' if defined?(Rails::Railtie)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'inquiry_attrs/installer'
|
|
4
|
+
|
|
5
|
+
namespace :inquiry_attrs do
|
|
6
|
+
INITIALIZER_RELATIVE = InquiryAttrs::Installer::INITIALIZER_PATH.to_s
|
|
7
|
+
|
|
8
|
+
desc 'Install an initializer that auto-includes InquiryAttrs::Concern into ActiveRecord'
|
|
9
|
+
task :install do
|
|
10
|
+
result = InquiryAttrs::Installer.install!(Rails.root)
|
|
11
|
+
|
|
12
|
+
case result
|
|
13
|
+
when :created then printf " %-10s %s\n", 'create', INITIALIZER_RELATIVE
|
|
14
|
+
when :skipped then printf " %-10s %s\n", 'skip', "#{INITIALIZER_RELATIVE} already exists"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc 'Remove the inquiry_attrs initializer'
|
|
19
|
+
task :uninstall do
|
|
20
|
+
result = InquiryAttrs::Installer.uninstall!(Rails.root)
|
|
21
|
+
|
|
22
|
+
case result
|
|
23
|
+
when :removed then printf " %-10s %s\n", 'remove', INITIALIZER_RELATIVE
|
|
24
|
+
when :skipped then printf " %-10s %s\n", 'skip', "#{INITIALIZER_RELATIVE} not found"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/llms/overview.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# inquiry_attrs — LLM Context Overview
|
|
2
|
+
|
|
3
|
+
> Load this file before modifying or extending the gem.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
`inquiry_attrs` adds predicate-style inquiry methods to Rails model attributes.
|
|
8
|
+
It is a Rails-only gem (depends on ActiveSupport ≥ 7.0 and ActiveRecord ≥ 7.0).
|
|
9
|
+
|
|
10
|
+
## File map
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
lib/
|
|
14
|
+
inquiry_attrs.rb # Entry point — requires everything, fires on_load hook
|
|
15
|
+
inquiry_attrs/
|
|
16
|
+
version.rb # VERSION constant
|
|
17
|
+
nil_inquiry.rb # NilInquiry::INSTANCE — returned for blank values
|
|
18
|
+
symbol_inquiry.rb # SymbolInquiry — wraps Symbol attributes
|
|
19
|
+
concern.rb # Concern — provides .inquirer class macro
|
|
20
|
+
test/
|
|
21
|
+
test_helper.rb # Minitest setup, SQLite in-memory AR connection
|
|
22
|
+
inquiry_attrs/
|
|
23
|
+
nil_inquiry_test.rb
|
|
24
|
+
symbol_inquiry_test.rb
|
|
25
|
+
concern_test.rb # Integration: AR, StoreModel, plain Ruby, Symbol attrs
|
|
26
|
+
llms/
|
|
27
|
+
overview.md # This file
|
|
28
|
+
usage.md # Common patterns and recipes
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Public surface
|
|
32
|
+
|
|
33
|
+
### Auto-include for ActiveRecord
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
# lib/inquiry_attrs.rb
|
|
37
|
+
ActiveSupport.on_load(:active_record) do
|
|
38
|
+
include InquiryAttrs::Concern
|
|
39
|
+
end
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
This fires once when ActiveRecord is first loaded, so every
|
|
43
|
+
`ApplicationRecord` subclass automatically has access to `.inquirer` — no
|
|
44
|
+
explicit `include` needed in AR models.
|
|
45
|
+
|
|
46
|
+
### `InquiryAttrs::Concern` (opt-in for non-AR classes)
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
include InquiryAttrs::Concern # adds .inquirer class method
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `.inquirer(*attrs)` — the only public class-level API
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
inquirer :status # wraps :status reader
|
|
56
|
+
inquirer :status, :role, :plan # wraps multiple readers at once
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Return type decision table
|
|
60
|
+
|
|
61
|
+
| Raw value (from original reader) | Return type |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `nil`, `""`, or any `blank?` value | `InquiryAttrs::NilInquiry::INSTANCE` |
|
|
64
|
+
| `Symbol` | `InquiryAttrs::SymbolInquiry.new(raw)` |
|
|
65
|
+
| Any other string | `raw.to_s.inquiry` → `ActiveSupport::StringInquirer` |
|
|
66
|
+
|
|
67
|
+
## Class responsibilities
|
|
68
|
+
|
|
69
|
+
### `InquiryAttrs::NilInquiry`
|
|
70
|
+
|
|
71
|
+
- Frozen singleton (`INSTANCE`)
|
|
72
|
+
- `nil?` → `true`; `blank?` → `true`; `present?` → `false`
|
|
73
|
+
- Every `?`-method → `false` via `method_missing`
|
|
74
|
+
- `== nil`, `== ""`, `== INSTANCE` → `true`
|
|
75
|
+
- Implements `to_s`, `to_str`, `inspect`
|
|
76
|
+
|
|
77
|
+
### `InquiryAttrs::SymbolInquiry < SimpleDelegator`
|
|
78
|
+
|
|
79
|
+
- Wraps a `Symbol`; raises `ArgumentError` for non-symbols
|
|
80
|
+
- Unwraps nested `SymbolInquiry` on init
|
|
81
|
+
- Any `?`-method returns `true` iff `sym.to_s == method_name.delete_suffix('?')`
|
|
82
|
+
- `==` accepts `Symbol`, `String`, or `SymbolInquiry`
|
|
83
|
+
- `is_a?(Symbol)` → `true` (and `kind_of?`)
|
|
84
|
+
- `nil?` → `false`; `blank?` → `false`; `present?` → `true`
|
|
85
|
+
|
|
86
|
+
### `InquiryAttrs::Concern`
|
|
87
|
+
|
|
88
|
+
The `inquirer` class method:
|
|
89
|
+
|
|
90
|
+
1. Captures the **original** reader with `instance_method(attr)` before
|
|
91
|
+
redefining — this is the critical design choice that makes plain Ruby
|
|
92
|
+
`attr_accessor` and StoreModel work correctly.
|
|
93
|
+
2. Defines a new method via `define_method` that calls `original.bind_call(self)`
|
|
94
|
+
to read the raw value.
|
|
95
|
+
3. Falls back to `super()` → `self[attr]` → `nil` when no original exists
|
|
96
|
+
(e.g., lazy AR attribute definitions or Dry::Struct).
|
|
97
|
+
4. Applies the return type decision table above.
|
|
98
|
+
|
|
99
|
+
## Why `instance_method` capture instead of `super()`?
|
|
100
|
+
|
|
101
|
+
When `inquirer :status` is called after `attr_accessor :status`, the new
|
|
102
|
+
`define_method` *replaces* the attr_accessor reader. There is then no superclass
|
|
103
|
+
method to call via `super()`, which raises `NoMethodError`. Capturing
|
|
104
|
+
`instance_method(:status)` before the redefinition avoids this entirely.
|
|
105
|
+
|
|
106
|
+
For AR models, `ActiveModel::Attributes` pre-defines readers before `inquirer`
|
|
107
|
+
is typically called, so `instance_method` finds them too.
|
|
108
|
+
|
|
109
|
+
## Why `blank?` instead of `nil? || == ""`?
|
|
110
|
+
|
|
111
|
+
Since this is a Rails-only gem, `blank?` (from ActiveSupport) is always
|
|
112
|
+
available. It handles `nil`, `""`, whitespace-only strings, and any object
|
|
113
|
+
that defines `blank?`, all in one call.
|
|
114
|
+
|
|
115
|
+
## Rails conventions used
|
|
116
|
+
|
|
117
|
+
| Convention | Where used |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `ActiveSupport::Concern` | `InquiryAttrs::Concern` |
|
|
120
|
+
| `ActiveSupport.on_load(:active_record)` | `lib/inquiry_attrs.rb` — auto-include in AR |
|
|
121
|
+
| `ActiveSupport::StringInquirer` via `String#inquiry` | `concern.rb` — wraps string values |
|
|
122
|
+
| `Object#blank?` | `concern.rb` — nil/blank detection |
|
|
123
|
+
|
|
124
|
+
## Test setup
|
|
125
|
+
|
|
126
|
+
- **Framework:** Minitest with `minitest-reporters` (SpecReporter)
|
|
127
|
+
- **Database:** SQLite in-memory (`adapter: 'sqlite3', database: ':memory:'`)
|
|
128
|
+
- **AR schema:** defined inline in `setup` with `force: true`, dropped in `teardown`
|
|
129
|
+
- **Run command:** `bundle exec ruby -Ilib -Itest test/inquiry_attrs/*.rb`
|
data/llms/usage.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# inquiry_attrs — Usage Patterns
|
|
2
|
+
|
|
3
|
+
> Common patterns and recipes for LLMs helping users work with this gem.
|
|
4
|
+
|
|
5
|
+
## 1. ActiveRecord — no setup needed
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
# Just call inquirer in any AR model.
|
|
9
|
+
# No include required — auto-added via ActiveSupport.on_load(:active_record).
|
|
10
|
+
|
|
11
|
+
class User < ApplicationRecord
|
|
12
|
+
inquirer :status, :role, :plan
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
user = User.new(status: 'active', role: 'admin', plan: 'pro')
|
|
16
|
+
|
|
17
|
+
user.status.active? # => true
|
|
18
|
+
user.status.inactive? # => false
|
|
19
|
+
user.role.admin? # => true
|
|
20
|
+
user.plan.pro? # => true
|
|
21
|
+
user.plan.free? # => false
|
|
22
|
+
|
|
23
|
+
# String methods still work
|
|
24
|
+
user.status == 'active' # => true
|
|
25
|
+
user.status.include?('act') # => true
|
|
26
|
+
user.status.upcase # => "ACTIVE"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 2. Nil / blank safety
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
user = User.new(status: nil)
|
|
33
|
+
|
|
34
|
+
user.status.nil? # => true
|
|
35
|
+
user.status.blank? # => true
|
|
36
|
+
user.status.active? # => false ← no NoMethodError!
|
|
37
|
+
user.status == nil # => true
|
|
38
|
+
user.status.to_s # => ""
|
|
39
|
+
|
|
40
|
+
# Guard pattern
|
|
41
|
+
if user.status.nil?
|
|
42
|
+
# handle blank
|
|
43
|
+
elsif user.status.active?
|
|
44
|
+
# handle active
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 3. Replace case/when string comparisons
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Before
|
|
52
|
+
case user.status
|
|
53
|
+
when 'active' then grant_access(user)
|
|
54
|
+
when 'suspended' then deny_access(user)
|
|
55
|
+
when nil, '' then redirect_to login_path
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# After
|
|
59
|
+
grant_access(user) if user.status.active?
|
|
60
|
+
deny_access(user) if user.status.suspended?
|
|
61
|
+
redirect_to login_path if user.status.nil?
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 4. Scopes and conditions
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
class User < ApplicationRecord
|
|
68
|
+
inquirer :status
|
|
69
|
+
|
|
70
|
+
scope :active, -> { where(status: 'active') }
|
|
71
|
+
scope :suspended, -> { where(status: 'suspended') }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# In views or controllers
|
|
75
|
+
User.all.select { |u| u.status.active? }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## 5. StoreModel
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
class ShippingAddress
|
|
82
|
+
include StoreModel::Model
|
|
83
|
+
include InquiryAttrs::Concern # required for non-AR classes
|
|
84
|
+
|
|
85
|
+
attribute :kind, :string
|
|
86
|
+
inquirer :kind
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class Order < ApplicationRecord
|
|
90
|
+
attribute :shipping_address, ShippingAddress.to_type
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
order.shipping_address.kind.billing? # => true/false
|
|
94
|
+
order.shipping_address.kind.nil? # => true when blank
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 6. Plain Ruby class
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Subscription
|
|
101
|
+
include InquiryAttrs::Concern
|
|
102
|
+
|
|
103
|
+
attr_accessor :plan, :state
|
|
104
|
+
|
|
105
|
+
def initialize(plan:, state: nil)
|
|
106
|
+
@plan = plan
|
|
107
|
+
@state = state
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# IMPORTANT: call inquirer AFTER attr_accessor
|
|
111
|
+
inquirer :plan, :state
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
sub = Subscription.new(plan: 'enterprise')
|
|
115
|
+
sub.plan.enterprise? # => true
|
|
116
|
+
sub.state.nil? # => true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## 7. Symbol attributes (e.g., Dry::Struct, enums stored as symbols)
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# If a reader returns a Symbol, you get SymbolInquiry
|
|
123
|
+
si = InquiryAttrs::SymbolInquiry.new(:active)
|
|
124
|
+
si.active? # => true
|
|
125
|
+
si.inactive? # => false
|
|
126
|
+
si == :active # => true
|
|
127
|
+
si == 'active' # => true
|
|
128
|
+
si.is_a?(Symbol) # => true
|
|
129
|
+
si.to_s # => "active"
|
|
130
|
+
si.to_sym # => :active
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## 8. Testing models with inquirer
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
# Minitest
|
|
137
|
+
class UserTest < ActiveSupport::TestCase
|
|
138
|
+
test 'status predicate' do
|
|
139
|
+
user = User.new(status: 'active')
|
|
140
|
+
assert user.status.active?
|
|
141
|
+
refute user.status.inactive?
|
|
142
|
+
assert_equal 'active', user.status
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
test 'nil status is safe' do
|
|
146
|
+
user = User.new(status: nil)
|
|
147
|
+
assert user.status.nil?
|
|
148
|
+
refute user.status.active?
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 9. Detecting NilInquiry in code
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
# Use nil? or == nil — do NOT use is_a?(NilClass)
|
|
157
|
+
user.status.nil? # => true ✅
|
|
158
|
+
user.status == nil # => true ✅
|
|
159
|
+
user.status.is_a?(NilClass) # => false ❌ (it's NilInquiry, not NilClass)
|
|
160
|
+
|
|
161
|
+
# Or compare to the instance directly
|
|
162
|
+
user.status.equal?(InquiryAttrs::NilInquiry::INSTANCE) # => true
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## 10. Common mistakes
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# ❌ Calling inquirer before attr_accessor in plain Ruby
|
|
169
|
+
class Broken
|
|
170
|
+
include InquiryAttrs::Concern
|
|
171
|
+
inquirer :status # no original reader to capture yet!
|
|
172
|
+
attr_accessor :status
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# ✅ Always after
|
|
176
|
+
class Fixed
|
|
177
|
+
include InquiryAttrs::Concern
|
|
178
|
+
attr_accessor :status
|
|
179
|
+
inquirer :status
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# ❌ Forgetting include for non-AR classes
|
|
183
|
+
class MyStoreModel
|
|
184
|
+
include StoreModel::Model
|
|
185
|
+
# include InquiryAttrs::Concern ← missing!
|
|
186
|
+
inquirer :status # => NoMethodError
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# ✅ Explicit include for StoreModel / plain Ruby
|
|
190
|
+
class MyStoreModel
|
|
191
|
+
include StoreModel::Model
|
|
192
|
+
include InquiryAttrs::Concern # ← required
|
|
193
|
+
inquirer :status
|
|
194
|
+
end
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## 11. Difference from `String#inquiry` (ActiveSupport)
|
|
198
|
+
|
|
199
|
+
| Feature | `"active".inquiry` | `inquirer :status` |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| Nil/blank safety | Raises `NoMethodError` | Returns `NilInquiry::INSTANCE` |
|
|
202
|
+
| Symbol support | Manual conversion | Automatic `SymbolInquiry` |
|
|
203
|
+
| Model integration | Manual wrapping | Declarative macro |
|
|
204
|
+
| AR auto-include | No | Yes, via `on_load` |
|
metadata
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: inquiry_attrs
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pawel Niemczyk
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: activerecord
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: railties
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '7.0'
|
|
54
|
+
description: |
|
|
55
|
+
InquiryAttrs wraps ActiveRecord/ActiveModel (and StoreModel/Dry::Struct) attributes with
|
|
56
|
+
predicate-style inquiry methods. Write user.status.active? instead of
|
|
57
|
+
user.status == "active". Blank/nil values safely return false for every
|
|
58
|
+
predicate — no more NoMethodError on nil. Run `rails inquiry_attrs:install`
|
|
59
|
+
to generate an initializer that auto-includes the concern into every
|
|
60
|
+
ActiveRecord model.
|
|
61
|
+
email: []
|
|
62
|
+
executables: []
|
|
63
|
+
extensions: []
|
|
64
|
+
extra_rdoc_files: []
|
|
65
|
+
files:
|
|
66
|
+
- AGENTS.md
|
|
67
|
+
- CHANGELOG.md
|
|
68
|
+
- CLAUDE.md
|
|
69
|
+
- LICENSE
|
|
70
|
+
- README.md
|
|
71
|
+
- lib/inquiry_attrs.rb
|
|
72
|
+
- lib/inquiry_attrs/concern.rb
|
|
73
|
+
- lib/inquiry_attrs/installer.rb
|
|
74
|
+
- lib/inquiry_attrs/nil_inquiry.rb
|
|
75
|
+
- lib/inquiry_attrs/railtie.rb
|
|
76
|
+
- lib/inquiry_attrs/symbol_inquiry.rb
|
|
77
|
+
- lib/inquiry_attrs/version.rb
|
|
78
|
+
- lib/tasks/inquiry_attrs.rake
|
|
79
|
+
- llms/overview.md
|
|
80
|
+
- llms/usage.md
|
|
81
|
+
homepage: https://github.com/your-org/inquiry_attrs
|
|
82
|
+
licenses:
|
|
83
|
+
- MIT
|
|
84
|
+
metadata:
|
|
85
|
+
homepage_uri: https://github.com/your-org/inquiry_attrs
|
|
86
|
+
source_code_uri: https://github.com/your-org/inquiry_attrs
|
|
87
|
+
changelog_uri: https://github.com/your-org/inquiry_attrs/blob/main/CHANGELOG.md
|
|
88
|
+
rdoc_options: []
|
|
89
|
+
require_paths:
|
|
90
|
+
- lib
|
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.0'
|
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
requirements: []
|
|
102
|
+
rubygems_version: 3.6.9
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: Predicate-style inquiry methods for Rails model attributes
|
|
105
|
+
test_files: []
|