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