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
 
 
              
 
    
Top comments (1)
I love this!