inquiry_attrs
Predicate-style inquiry methods for Rails model attributes.
Instead of comparing strings:
user.status == 'active'
user.role == 'admin'Write expressive predicates:
user.status.active?
user.role.admin?Nil/blank attributes safely return false for every predicate — no more
NoMethodError on nil.
Installation
# Gemfile
gem 'inquiry_attrs'Quick start
ActiveRecord — zero configuration
Use rake task to install or uninstall the initializer:
rails inquiry_attrs:install # Install an initializer that auto-includes InquiryAttrs::Concern into ActiveRecord
rails inquiry_attrs:uninstall # Remove the inquiry_attrs initializerinquiry_attrs auto-includes itself into every ActiveRecord::Base subclass
via ActiveSupport.on_load(:active_record). Just call .inquirer in any model:
class User < ApplicationRecord
inquirer :status, :kind
end
user = User.new(status: "active", kind: "admin")
user.status.active? # => true
user.status.inactive? # => false
user.kind.admin? # => true
user.kind.user? # => falseNil / blank attributes
user = User.new(status: nil)
user.status.nil? # => true
user.status.active? # => false ← no NoMethodError!
user.status == nil # => trueString comparison and String methods still work
user.status # => "active"
user.status == 'active' # => true
user.status.include?('act') # => true
user.status.upcase # => "ACTIVE"StoreModel
Include InquiryAttrs::Concern explicitly for non-AR classes:
class ShippingAddress
include StoreModel::Model
include InquiryAttrs::Concern
attribute :kind, :string # "shipping", "billing", "return"
inquirer :kind
end
address = ShippingAddress.new(kind: "billing")
address.kind.billing? # => true
address.kind.shipping? # => falsePlain Ruby
class Subscription
include InquiryAttrs::Concern
attr_accessor :plan, :state
def initialize(plan:, state: nil)
@plan = plan
@state = state
end
# Call inquirer AFTER attr_accessor — the original reader must exist first.
inquirer :plan, :state
end
sub = Subscription.new(plan: 'enterprise')
sub.plan.enterprise? # => true
sub.state.nil? # => true⚠️ Reserved predicate names
Some predicate names are already defined as real methods on the objects
inquiry_attrs returns. Calling them does not test whether the attribute
value equals that word — the existing method is called instead and
method_missing is never reached.
| Value / predicate | Already defined by | What it actually tests |
|---|---|---|
"nil" / .nil?
|
Ruby Object#nil?
|
Whether the object is nil — always false for present strings, always true for blank values |
"blank" / .blank?
|
ActiveSupport Object#blank?
|
Whether the value is blank (nil, "", whitespace) — not whether it equals "blank"
|
"present" / .present?
|
ActiveSupport Object#present?
|
Opposite of blank? — not whether it equals "present"
|
"empty" / .empty?
|
Ruby String#empty?
|
Whether the string is "" — not whether it equals "empty"
|
"frozen" / .frozen?
|
Ruby Object#frozen?
|
Whether the object is frozen — not whether it equals "frozen"
|
Example of the problem
class Order < ApplicationRecord
inquirer :state
end
# ❌ Misleading — .blank? tests blankness, not state == "blank"
order = Order.new(state: 'blank')
order.state.blank? # => false ("blank" is a non-empty string, so not blank)
# ❌ Misleading — .present? tests non-blankness, not state == "present"
order = Order.new(state: 'present')
order.state.present? # => true (any non-blank string is present)
# ❌ Misleading — .nil? tests object identity, not state == "nil"
order = Order.new(state: 'nil')
order.state.nil? # => false (it is a StringInquirer, not nil)Rule of thumb: if your domain uses values such as nil, blank, present,
empty, or frozen, use direct string comparison instead of a predicate:
order.state == 'blank' # ✅ reliable
order.state == 'present' # ✅ reliable
order.state == 'nil' # ✅ reliableHow it works
.inquirer :attr wraps the original attribute reader and returns one of three
objects based on the raw value:
| Raw value | Return type | Key behaviour |
|---|---|---|
nil or any blank value |
InquiryAttrs::NilInquiry::INSTANCE |
nil? → true, all predicates → false
|
Symbol |
InquiryAttrs::SymbolInquiry |
:active.active? → true
|
| Any other string | ActiveSupport::StringInquirer |
Standard Rails inquiry |
Note: if an attribute value shares a name with a built-in Ruby/Rails predicate (
"nil","blank","present","empty","frozen") the real method will be called — not a string-equality check. See ⚠️ Reserved predicate names for details.
InquiryAttrs::NilInquiry
A frozen singleton returned for blank attributes. Every ?-method returns
false; behaves like nil in comparisons, blank? checks, and type
introspection.
ni = InquiryAttrs::NilInquiry::INSTANCE
ni.nil? # => true
ni.active? # => false
ni == nil # => true
ni.blank? # => true
ni.is_a?(NilClass) # => true
ni.kind_of?(NilClass) # => true
ni.instance_of?(NilClass) # => trueInquiryAttrs::SymbolInquiry
Wraps a Symbol with predicate methods; compares equal to both the symbol and
its string equivalent; reports itself as a Symbol in all type-check methods.
si = InquiryAttrs::SymbolInquiry.new(:active)
si.active? # => true
si == :active # => true
si == 'active' # => true
si.is_a?(Symbol) # => true
si.kind_of?(Symbol) # => true
si.instance_of?(Symbol) # => true
si.to_s # => "active"API
InquiryAttrs::Concern
Auto-included into ActiveRecord::Base. Include manually in other classes.
.inquirer(*attribute_names)
inquirer :status # single attribute
inquirer :status, :role, :plan # multiple attributesClaude Code Plugin
inquiry_attrs ships with a Claude Code skill that teaches Claude how to install
and use the gem in any Rails, StoreModel, or plain Ruby project.
Install the skill
Download inquiry-attrs.skill from the releases page and import it into Claude Code:
claude skill install inquiry-attrs.skillOr copy the claude-plugin/ directory into your project:
cp -r /path/to/inquiry_attrs/claude-plugin /your/project/.claude-plugins/inquiry-attrsWhat the skill teaches Claude
Once installed, the skill activates automatically when you ask things like:
- "Add inquiry_attrs to this project"
- "Add
.active?/.admin?style methods to my model" - "Replace
user.status == 'active'with predicates" - "Make attributes nil-safe with inquiry methods"
- "What are the reserved predicate names in inquiry_attrs?"
Claude will know how to add the gem, run the installer, use inquirer correctly
in AR models, StoreModel, and plain Ruby, and avoid the reserved predicate name
gotcha.
Development
bundle install
# Full suite (preferred)
bundle exec rake
# Single file
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb
# Single test by name
bundle exec ruby -Ilib -Itest test/inquiry_attrs/concern_test.rb \
--name test_matching_predicate_returns_trueLicense
MIT