About simplifying-ruby-code
A Ruby code review and refactoring skill focused on simplifying structure while preserving meaningful behavior. It offers patterns for replacing unnecessary classes, separating decisions from I/O, and checking callers before removing code.
Key features
- 01Replace stateless command objects with functions
- 02Choose Hash, Struct, or Data for values
- 03Prefer composition over deep inheritance
- 04Separate pure decisions from I/O
- 05Check callers before removing classes
- 06Guide review and stepwise refactoring
Use cases
- 01Reviewing Ruby code for simplification opportunities
- 02Refactoring Rails service objects and concerns
- 03Planning safe removal of classes
- 04Separating computation from database or file effects
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ bostonaholic/ skills --skill simplifying-ruby-codeAfter installing, type/simplifying-ruby-codein Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
agents/openai.yamlreferences/review-findings.mdscripts/find-callers.shDetails
From the project READMESimplifying Ruby Code
Prefer plain data (Hash, Array, Struct, Data) and pure functions over classes that add no state or behavior. Separate decisions from effects: a decision takes data and returns data; an effect does I/O (database, network, files, time). Keep them in separate methods or modules so decisions test without mocks.
When the user asks for a review, report findings and edit nothing unless they ask. Cite patterns by number ("Pattern 1") or by anti-pattern name.
Patterns
-
Command objects to module functions. A class whose only public method is
call,perform,run, orexecute, holding nothing beyond its constructor arguments, becomes a direct call or a module function (UserCreator.new(params).callbecomesUser.create(params)). Keep the object when it holds state across calls, runs a multi-step algorithm worth naming, or must be serialized, as a background job is. -
Value objects to Data, Struct, or Hash. A class that only stores attributes, with hand-written
initialize, readers, or==and no behavior or validation:Use When Hash Transient data, varying keys, JSON in or out Struct Fixed attributes, mutation acceptable Data Fixed attributes, immutable (check ruby -v>= 3.2)Custom class Validation, invariants, or real domain behavior -
Class-method-only classes to modules. A class never instantiated, defining only
self.methods, becomes a module withmodule_function. -
Deep inheritance to composition. More than 2 levels counting only the project's own classes (framework and stdlib bases such as
ApplicationRecordorStandardErrordo not count), or an abstract base with a single subclass. -
Missing Ruby protocols. Callers unpack a collection-like or value-like class by hand instead of it implementing
eachwithEnumerable,to_h,<=>withComparable, orhashandeql?. -
Mixed decisions and effects. Tests need heavy mocks, or a method computes and writes in one body. Extract the computation into a pure function and leave a thin method for the I/O.
Rails anti-patterns
- Service objects: a single-method, stateless service belongs in a model method or module function (Pattern 1). Keep complex orchestration as a service but separate decisions from effects (Pattern 6). Jobs are legitimate objects because they serialize.
- Models: keep ActiveRecord models as classes; move business calculations into pure functions.
- Concerns: a concern shares behavior across models; flag one used as a dumping ground for a single model.
- Helpers: view formatting only; business rules go in a module.
- Reinvented Rails: custom DSLs or base classes that duplicate scopes,
validations, callbacks, enums, or
ActiveModel.
Plain Ruby anti-patterns
- Template methods: a base class with hook methods where a block would do.
- Builders: for objects that keyword arguments or
Data.definebuild directly. - Wrappers: classes that wrap Array, Hash, Set, or another stdlib type and forward most calls.
- Metaprogramming:
method_missing,define_method, orinstance_evalDSLs where plain methods work. - Dependencies: a gem pulled in for a few lines of stdlib code.
In a gem, every public constant is API that other projects may call.
Before removing or inlining a class
- Run
bash <skill-dir>/scripts/find-callers.sh <Constant> <project>for each class. - Exit 0: account for every
referenceandrequirehit, and decide for eachdynamichit whether the name it builds can be the class; that takes reading the code, so it stays with you. - Exit 1: no caller found. Exit 2: fix the argument. Exit 3: install the named tool. Exit 4: report the grep error.
- A controller is also reached from
config/routes.rbby its snake_case resource name, which the script does not search; read that file. - In a review, drop or downgrade a finding whose callers you cannot account for.
Review findings
Read review findings when reporting a review: the grouping, the fields of each finding, and the closing order.
Refactoring
Get a green test baseline first. Apply one pattern at a time and run the suite after each; on failure, fix or revert that step before continuing. Update tests that mocked the removed layer to call the pure function or the direct API instead.