| name | simplifying-ruby-code |
| description | Identify over-engineering in Ruby - prefer simple data structures (Hash, Struct, Data) and pure functions over unnecessary classes |
Simplifying Ruby Code
Core Principle
Prefer simple data structures (Hash, Array, Struct, Data) and pure functions over unnecessary classes and abstractions.
MANDATORY: Identify whether code is a decision (pure logic) or effect (I/O). Keep them separate. See writing-code skill.
When to Use
- Command objects with single
call method and no state
- Value objects that just wrap data without behavior
- Service classes that could be module functions
- Custom classes for simple data (coordinates, ranges, tuples)
- Deep inheritance where composition would work
- Missing Ruby protocols (
to_h, to_a, each)
- Tests require extensive mocking (indicates mixed concerns)
Over-Engineering Patterns
Command Objects → Module Functions
class UserCreator
def initialize(params); @params = params; end
def call; User.create(@params); end
end
User.create(params)
Keep command object when: Has state, multi-step algorithm, needs queuing.
Value Objects → Struct/Data/Hash
class Point
attr_reader :x, :y
def initialize(x, y); @x, @y = x, y; end
def ==(other); x == other.x && y == other.y; end
end
Point = Data.define(:x, :y)
Point = Struct.new(:x, :y, keyword_init: true)
point = {x: 10, y: 20}
Utility Classes → Modules
class DateFormatter
def self.format_for_display(date); date.strftime("%B %d, %Y"); end
end
module DateFormatter
module_function
def format_for_display(date); date.strftime("%B %d, %Y"); end
end
Deep Inheritance → Composition
class Animal; end
class Mammal < Animal; end
class Dog < Mammal; end
module WarmBlooded
def warm_blooded?; true; end
end
class Dog
include WarmBlooded
end
Data Structure Selection
| Use | When |
|---|
| Hash | Temporary data, varying keys, JSON interface |
| Struct | Fixed attributes, need methods, mutable OK |
| Data | Fixed attributes, immutable (Ruby 3.2+) |
| Custom Class | Complex validation, rich behavior, domain concepts |
Ruby Protocols
Implement for interoperability with standard library:
class Collection
include Enumerable
def each(&block); @items.each(&block); end
def to_a; @items.dup; end
def to_h; @items.to_h; end
def to_json(*args); @items.to_json(*args); end
end
Key protocols: to_h, to_a, to_json, to_s, each, <=>, hash/eql?
Refactoring Steps
- Identify decisions vs effects - Mark pure logic vs I/O
- Extract pure functions - Create module functions with data parameters
- Test pure functions - No mocks needed
- Simplify data structures - Replace classes with Struct/Data/Hash
- Remove unnecessary layers - Inline wrappers that add no value
Detection Checklist
Key Takeaways
- Hash/Struct/Data over custom classes for simple data
- Module functions over command objects unless state needed
- Composition over inheritance for behavior sharing
- Implement Ruby protocols for interoperability
- OOP for domain models, functional for calculations - Use both appropriately