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