Keela 🐕
Like the famous CSI dog who found what others missed, Keela sniffs out unused code in your Ruby codebase.
Why Remove Unused Code?
Dead code isn't harmless — it's actively costly:
- Cognitive overhead: Developers read and try to understand code that doesn't matter, slowing down onboarding and feature work
- CI minutes: Tests for unused methods still run, burning compute time on every pipeline
- False confidence: Test coverage metrics include dead code, masking gaps in the code that actually runs
- Refactoring friction: Unused code creates dependencies that make refactoring harder ("wait, is this called somewhere?")
- Security surface: More code means more potential vulnerabilities, even in paths users never hit
Most codebases accumulate dead code gradually — a feature flag that's always on, a method replaced but never deleted, a scope that lost its last caller. Keela helps you find it and clean it up.
Installation
gem install keela
Or add to your Gemfile:
gem 'keela', group: :development
Quick Start
# First time: generate a baseline of current unused code
keela --update-baseline
# This creates .keela_baseline.yml in your project root
# From now on, just run:
keela
# Keela will fail if:
# - NEW unused code is detected (someone added dead code)
# - Previously unused code was REMOVED (time to update the baseline!)
How It Works
Keela operates in two modes:
Baseline Mode (Default)
If a .keela_baseline.yml file exists, Keela compares the current scan against it:
| Scenario | Result |
|---|---|
| No changes from baseline | ✅ Pass (silent, exit 0) |
| NEW unused code detected | ❌ Fail (shows new items) |
| Code REMOVED from baseline | ❌ Fail (prompts to update baseline) |
This lets you gradually pay down tech debt while preventing new dead code from sneaking in.
Report Mode
If no baseline exists (or you use --report), Keela shows all unused code:
keela --report
Command Line Options
# Scan for all unused code (methods, scopes, constants, delegations, attributes)
keela
# Scan for specific types
keela --type methods
keela --type scopes
# Combine multiple types
keela --type methods,scopes,constants
# Force report mode (ignore baseline)
keela --report
# Update the baseline file
keela --update-baseline
# Use a custom baseline path
keela --baseline config/unused_baseline.yml
# Specify excluded items file
keela --excluded config/keela_excluded.yml
# Custom file extensions
keela --extensions rb,rake,haml
# Exclude files matching patterns
keela --exclude 'vendor/**/*' --exclude 'tmp/**/*'
# Include additional directories (adds to defaults)
keela --include 'engines/**/*.rb' --include 'custom/**/*.rb'
# Use a custom config file
keela --config path/to/keela.yml
# Output as JSON (for CI integrations)
keela --format json
# Show version
keela --version
Detection Strategies
Keela detects several types of unused code:
| Strategy | Detects | Example |
|---|---|---|
| methods | Unused method definitions | def unused_method |
| scopes | Unused ActiveRecord scopes | scope :unused_scope, -> { } |
| constants | Unused constants | UNUSED_CONSTANT = 'value' |
| delegations | Unused delegate declarations | delegate :unused, to: :target |
| attributes | Unused attr_* declarations | attr_accessor :unused_attr |
| i18n_keys | Unused translation keys | en.users.unused_key in locale YAML |
Run all strategies (default) or target specific ones with --type.
Note: The i18n_keys strategy is not included in --type all because it requires scanning YAML locale files. Run it explicitly with --type i18n_keys.
I18n Keys (Beta)
The i18n_keys strategy is beta and may produce false positives. It cannot detect:
- Lazy lookup -
t('.title')in views resolves based on the view path - Dynamic keys -
t("users.#{action}.title")with interpolated segments - Model translations -
User.human_attribute_name(:email)andUser.model_name.human - Pluralization siblings - If
one:is used,other:may appear unused
Review results carefully and use the exclusion file for known false positives.
Configuration File
Create a keela.yml or .keela.yml in your project root:
# keela.yml
extensions:
- rb
- haml
- erb
exclude_patterns:
- "vendor/**/*"
- "tmp/**/*"
include_patterns:
- "engines/**/*.%<ext>s"
excluded_path: ".keela_excluded.yml"
baseline_path: ".keela_baseline.yml"
Keela automatically loads keela.yml or .keela.yml from the current directory. Use --config to specify a different path.
Customizing Which Files to Scan
There are two approaches:
1. Tweak the defaults with --include and --exclude (or include_patterns/exclude_patterns in config):
# Add engines/ to the default app/, lib/, config/ directories
keela --include 'engines/**/*.rb'
# Exclude vendor files from scanning
keela --exclude 'vendor/**/*'
2. Full control with directory_patterns - replaces the defaults entirely:
# keela.yml - scan ONLY these directories
directory_patterns:
- "src/**/*.%<ext>s"
- "custom/**/*.%<ext>s"
Use directory_patterns when you need complete control. Use --include/--exclude when you just want to tweak the defaults.
Note: Mixing both approaches raises a ConfigurationError. Choose one or the other.
Available options:
| Key | Description | Default |
|---|---|---|
directory_patterns |
Glob patterns for files to scan (replaces defaults) | app/, lib/, config/ |
extensions |
File extensions to scan | rb, haml, erb |
include_patterns |
Additional patterns to scan (added to defaults) | [] |
exclude_patterns |
Patterns for files to exclude | [] |
excluded_path |
Path to YAML file of excluded items | nil |
baseline_path |
Path to baseline YAML file | .keela_baseline.yml |
required_directory |
Directory that must exist for scanning to proceed | nil |
CI Integration
Keela is designed for CI pipelines. Add it to catch dead code before it merges:
# .gitlab-ci.yml
unused_code:
script:
- bundle exec keela
rules:
- if: $CI_MERGE_REQUEST_IID
# .github/workflows/ci.yml
- name: Check for unused code
run: bundle exec keela
The workflow:
- Initial setup: Run
keela --update-baselineand commit.keela_baseline.yml - CI runs:
keelacompares against baseline, fails on new dead code - After cleanup: Run
keela --update-baselineto update the baseline
JSON Output
Use --format json for machine-readable output:
keela --format json --report
{
"strategies": ["methods", "scopes"],
"unused": {
"methods": {
"app/models/user.rb": ["unused_method", "old_helper"]
},
"scopes": {
"app/models/post.rb": ["inactive"]
}
},
"summary": {
"total": 3,
"by_strategy": {
"methods": 2,
"scopes": 1
}
}
}
This is useful for integrating with other tools, generating reports, or processing results programmatically.
Exclusion File
Some code appears unused but is actually called dynamically. Exclude it:
# .keela_excluded.yml
app/models/user.rb:
- legacy_method: "Called via metaprogramming"
- callback_method: "Used as ActiveRecord callback"
app/helpers/application_helper.rb:
- helper_method: "Called from views dynamically"
Then run with:
keela --excluded .keela_excluded.yml
Ruby API
require 'keela'
# Configure Keela
Keela.configure do |config|
config.extensions = %w[rb haml erb]
config.directory_patterns = %w[
app/**/*.%<ext>s
lib/**/*.%<ext>s
]
config.include_patterns = %w[engines/**/*.%<ext>s]
config.exclude_patterns = %w[vendor/**/* tmp/**/*]
config.excluded_path = '.keela_excluded.yml'
config.baseline_path = '.keela_baseline.yml'
end
# Run a scan
strategy = Keela::Strategies::Methods.new
scanner = Keela::Scanner.new(strategy: strategy)
success = scanner.run
# Access results
scanner.unused_collection # Hash of file => [unused_names]
scanner.new_unused # Items not in baseline
scanner.removed # Items in baseline but no longer unused
Custom Strategies
Detect other patterns by creating your own strategy:
class CallbackStrategy < Keela::Strategy
def name
"callbacks"
end
def definition_file_pattern
%r{app/models}
end
def extract_definition(line)
# Match: before_save :do_something
line =~ /(?:before|after|around)_\w+\s+:(\w+)/ ? Regexp.last_match(1) : nil
end
def usage_regex(name)
/def #{Regexp.quote(name)}\b/
end
def skip_comments?
true
end
end
scanner = Keela::Scanner.new(strategy: CallbackStrategy.new)
scanner.run(force_report: true)
About the Name
Keela was a famous English Springer Spaniel known as the "CSI dog." She could detect microscopic traces of blood that other forensic methods missed, and worked on many high-profile cases. Like her namesake, this gem finds the unused code that other tools miss.
License
MIT License. See LICENSE.txt.