DEV Community

Cover image for Cattri: The Ruby DSL for Clean, Scalable Class and Instance Attributes
Nathan Lucas
Nathan Lucas

Posted on

1 1 1 1

Cattri: The Ruby DSL for Clean, Scalable Class and Instance Attributes

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

Or add it in your Gemfile

gem "cattri"
Enter fullscreen mode Exit fullscreen mode

Explore more or contribute: https://github.com/bnlucas/cattri

Dynatrace image

Observability should elevate – not hinder – the developer experience.

Is your troubleshooting toolset diminishing code output? With Dynatrace, developers stay in flow while debugging – reducing downtime and getting back to building faster.

Explore Observability for Developers

Top comments (1)

Collapse
 
galtzo profile image
Peter H. Boling

I love this!

👋 Kindness is contagious

Delve into a trove of insights in this thoughtful post, celebrated by the welcoming DEV Community. Programmers of every stripe are invited to share their viewpoints and enrich our collective expertise.

A simple “thank you” can brighten someone’s day—drop yours in the comments below!

On DEV, exchanging knowledge lightens our path and forges deeper connections. Found this valuable? A quick note of gratitude to the author can make all the difference.

Get Started