GitHub: https://github.com/bnlucas/cattri
Ruby gives us tools like attr_accessor, and Rails builds on this with class_attribute, cattr_accessor, and more—but if you’ve ever wrestled with their inconsistencies around visibility, subclassing, or defaults, you’re not alone.
Cattri is a minimal-footprint Ruby DSL for defining both class- and instance-level attributes with clarity, safety, and control. Let’s explore what makes it a better alternative.
The Problem with Existing Attribute APIs
Ruby’s attr_* respects visibility but doesn’t support class-level or subclass-safe behavior. Rails’ class_attribute handles subclassing but ignores visibility and requires ActiveSupport. If you’re building a gem and want both, you’re forced to either duplicate logic manually or take on a heavyweight dependency.
| Feature/Behavior | Cattri |
attr_* (Ruby) |
cattr_accessor (Rails) |
class_attribute (Rails) |
|---|---|---|---|---|
| 🔁 Subclass-safe value duplication | ✅ | ❌ | ❌ | ✅ |
| 🔒 Subclass-safe metadata inheritance | ✅ | ❌ | ❌ | ✅ |
| 👁 Respects Ruby visibility (private, etc.) | ✅ | ✅ | ❌ | ❌ |
| 🌀 Lazy defaults (via lambdas) | ✅ | ❌ | ⚠️ (writer-only) | ✅ |
| 🧱 Static defaults | ✅ | ❌ | ⚠️ (manual) | ✅ |
🧼 Coercion support (e.g., ->(val) { !!val }) |
✅ | ❌ | ❌ | ❌ |
| 🧠 Introspection (list defined attrs) | ✅ | ❌ | ❌ | ❌ |
| 🧪 Strict error handling for missing definitions | ✅ | ❌ | ❌ | ❌ |
| 🧩 Unified class + instance attribute API | ✅ | ❌ | ❌ | ⚠️ (instance_reader) |
| ⚖️ No ActiveSupport dependency | ✅ | ✅ | ❌ | ❌ |
Using Ruby’s attr_accessor for Class-Level Attributes
class Base
class << self
attr_accessor :config
end
end
Base.config = { debug: false }
class Sub < Base
self.config[:debug] = true
end
Base.config[:debug] # => true ❌ shared state
What’s required?
- ✅ Manual class << self block
- ❌ Shared state across all subclasses
- ❌ No default support
- ❌ No visibility control
- ❌ No metadata or coercion
- ❌ No per-subclass copy
Using Cattri’s cattr
class Base
include Cattri
cattr :config, default: -> { { debug: false } }
end
class Sub < Base
config[:debug] = true
end
Base.config[:debug] # => false ✅ safe isolation
Using Ruby’s attr_accessor for Instance-Level Attributes
class Base
attr_accessor :config
def initialize
@config = { debug: false }
end
end
class Sub < Base
def initialize
super
@config = @config.dup # required to avoid shared mutation
@config[:debug] = true
end
end
a = Base.new
b = Sub.new
a.config[:debug] # => false
b.config[:debug] # => true
What’s required?
- ✅ Manual initialize boilerplate
- ✅ Explicit default values
- ✅ Defensive dup to avoid shared state
- ❌ No metadata, coercion, or introspection
Using Cattri’s iattr
class Base
include Cattri
iattr :config, default: -> { { debug: false } }
end
class Sub < Base
def initialize
super
config[:debug] = true
end
end
Using Rails’ class_attribute
class Base
class_attribute :config
class_attribute :api_key # public visibility
end
Base.config = { debug: false }
class Sub < Base
self.config[:debug] = true
end
Base.config[:debug] # => false ✅ isolated
Benefits of class_attribute
- ✅ Subclass-safe value isolation (via dup)
- ✅ Optional instance reader/writer
- ❌ No visibility support
- ❌ No default value support (manual setup)
- ❌ No coercion, or introspection
- ⚠️ Requires ActiveSupport
To support defaults:
class Base
class_attribute :config
self.config = { debug: false } # must define manually
end
Using Cattri’s cattr
class Base
include Cattri
cattr :config, default: -> { { debug: false } }
private
cattr :api_key # private visibility
end
class Sub < Base
config[:debug] = true
end
Base.config[:debug] # => false ✅ isolated
| Feature |
class_attribute (Rails) |
cattr (Cattri) |
|---|---|---|
| Subclass-safe value? | ✅ | ✅ |
| Metadata inheritance? | ✅ | ✅ |
| Default values? | ❌ (must set manually) | ✅ (static or lazy) |
| Visibility tracking? | ❌ | ✅ |
| Coercion support? | ❌ | ✅ |
| Introspection? | ❌ | ✅ |
| Requires ActiveSupport? | ✅ | ❌ |
Why Cattri?
Cattri brings modern, minimal, and predictable behavior to attribute definition in Ruby. Whether you’re building a DSL, gem configuration system, or just want clean subclass behavior without reaching for ActiveSupport, Cattri gives you:
- Consistent class and instance attribute semantics
- Subclass-safe isolation without boilerplate
- Visibility-aware definitions that feel native to Ruby
If you’ve ever worked around the limitations of attr_*, cattr_accessor, or class_attribute, Cattri was built to make that pain go away—for good.
Try it, use it in a gem, subclass it, and see how it holds up.
Installation
bundle add cattri
Or add it in your Gemfile
gem "cattri"
Explore more or contribute: https://github.com/bnlucas/cattri
