enumerable-refactor
Enumerable Refactor
Overview
This skill identifies and refactors the most common Ruby anti-pattern: initializing an empty variable and populating it with .each. Almost every time you see this pattern, a purpose-built Enumerable method exists that is more concise, expressive, and often more performant.
The Core Anti-Pattern
# ANY time you see this shape, it can almost certainly be refactored:
result = [] # or {}, 0, "", Set.new, false, nil
something.each do |item|
# ... build up result ...
end
result
When to Use This Skill
Apply when:
- Reviewing or refactoring Ruby code that uses
.eachto build return values - User asks to make Ruby code more idiomatic
- User mentions "enumerable", "refactoring", or "each loop"
- You spot the empty-variable-then-each pattern during code review
Do NOT apply when:
- The
.eachloop is purely for side effects (sending emails, writing to DB, printing) - The loop has complex control flow (
retry, exception handling) that genuinely resists functional expression - The imperative version is genuinely clearer for the specific case
Workflow
1. Find the Anti-Patterns
Search the codebase for the telltale signs:
# Search for empty array initialization followed by .each
Grep for: "= \[\]\s*$" in *.rb files
Grep for: "= {}\s*$" in *.rb files
Grep for: "= 0\s*$" in *.rb files
Grep for: "= \"\"\s*$" in *.rb files
Grep for: "= Set.new" in *.rb files
Grep for: "= false\s*$" in *.rb files
Then check if those initializations are followed by .each within a few lines.
Load the reference for the full pattern catalog:
Read references/patterns.md
2. Classify Each Instance
For each anti-pattern found, identify what the .each loop is doing:
| Loop behavior | Replace with |
|---|---|
| Transforming each element into a new array | map |
| Transforming + flattening nested arrays | flat_map |
| Transforming + removing nils | filter_map |
| Keeping elements that match a condition | select / filter |
| Removing elements that match a condition | reject |
| Building a hash from a collection | to_h { |x| [k, v] } |
| Building a hash with conditional/complex logic | each_with_object({}) |
| Grouping elements by a key | group_by |
| Splitting into two groups (true/false) | partition |
| Summing numbers | sum |
| Counting matches | count |
| Building a frequency hash | tally |
| Finding the first match | find / detect |
| Finding min/max by attribute | min_by / max_by |
| Setting a boolean flag | any? / all? / none? |
| Joining strings | join or map { ... }.join |
| Accumulating a single immutable value | reduce / inject |
3. Refactor
For each instance, apply the replacement. Show before/after. Verify behavior is preserved.
Key rules:
- Use
each_with_objectfor mutable accumulators (Hash, Array, Set), NOTinject - Use
inject/reduceonly for immutable accumulation (numbers, frozen strings) - Prefer
filter_mapover.select.mapor.map.compact(single pass) - Prefer
tallyoverHash.new(0)+.eachfor frequency counting - Prefer
to_h { |x| [k, v] }overeach_with_object({})for simple key-value mappings - Prefer
partitionover two separateselect/rejectcalls - Prefer
sort_byoversortwith a comparison block
4. Check for Chaining Opportunities
After individual refactors, look for method chains that can be simplified:
# Two passes -> one pass
users.select(&:active?).map(&:email)
# becomes
users.filter_map { |u| u.email if u.active? }
# Unnecessary intermediate array
departments.map(&:employees).flatten
# becomes
departments.flat_map(&:employees)
# Two separate iterations for min and max
lowest = temps.min
highest = temps.max
# becomes
lowest, highest = temps.minmax
5. Verify
- Ensure the refactored code produces the same result
- Run tests if available
- Check for subtle differences (e.g.,
filter_mapremovesfalsevalues, not justnil)
Important Caveats
-
filter_mapdropsfalsetoo: If the transformation can legitimately returnfalse, use.map.compactinstead. -
injecthash bug: Never useinjectto build a hash —Hash#[]=returns the value, not the hash. Useeach_with_objectinstead. -
Lazy evaluation: For very large collections where you only need the first N results, suggest
.lazybefore the chain. -
Rails extensions: In Rails codebases, also suggest
index_by,index_with,pluck,excluding, andsolewhere appropriate. -
Side effects are OK with
.each: Not every.eachis an anti-pattern. If the loop body performs side effects and doesn't build a return value,.eachis the right choice.
More from nateberkopec/dotfiles
sandi-metz-rules
This skill should be used when users request code review, refactoring, or code quality improvements for Ruby codebases. Apply Sandi Metz's four rules for writing maintainable object-oriented code - classes under 100 lines, methods under 5 lines, no more than 4 parameters, and controllers instantiate only one object. Use when users mention "Sandi Metz", "code quality", "refactoring", or when reviewing Ruby code for maintainability.
15nanobanana
Generate, edit, and restore images using Gemini CLI with the Nano Banana extension. Use when users request image generation, edits, icons, patterns, diagrams, or visual storytelling.
15argument-validator
This skill should be used when users want to validate or critique an argument by extracting premises, surfacing hidden assumptions, checking logical validity, optionally formalizing in Lean, and researching premise support.
15webapp-testing
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
14env-to-fnox
This skill should be used when users want to migrate from .env files to fnox with 1Password (or another secret provider). It covers installing fnox, creating 1Password items, configuring fnox.toml, and integrating with mise. Use when users mention ".env migration", "fnox setup", "1password secrets", or want to improve their secret management workflow.
14deep-research
This skill should be used when users request comprehensive, in-depth research on a topic that requires detailed analysis similar to an academic journal or whitepaper. The skill conducts multi-phase research using web search and content analysis, employing high parallelism with multiple subagents, and produces a detailed markdown report with citations.
14