Scope Hunter
A RuboCop extension that detects duplicate ActiveRecord query chains and suggests replacing them with existing named scopes. Keep your query logic DRY, improve readability, and help your team discover and reuse well-named scopes.
โจ Features
- ๐ Detects duplicate queries - Finds ActiveRecord queries that match existing scopes
- ๐ Autocorrect support - Automatically replaces duplicate queries with scope names
- ๐ฏ Smart matching - Normalizes queries to handle hash key order, different syntaxes
- ๐ฆ Preserves method chains - Keeps trailing methods like
.order(),.limit()intact - ๐ Zero configuration - Works out of the box with sensible defaults
๐ฆ Installation
Add this line to your application's Gemfile:
gem 'scope_hunter'And then execute:
$ bundle installOr install it yourself as:
$ gem install scope_hunter๐ Usage
Basic Setup
Add to your .rubocop.yml:
require:
- scope_hunter
AllCops:
NewCops: enable
ScopeHunter/UseExistingScope:
Enabled: trueExample
Before:
class User < ApplicationRecord
scope :active, -> { where(status: :active) }
scope :published, -> { where(published: true) }
def self.find_active_users
User.where(status: :active) # โ Duplicate!
end
def self.recent_published
User.where(published: true).order(created_at: :desc) # โ Duplicate!
end
endAfter running rubocop -A:
class User < ApplicationRecord
scope :active, -> { where(status: :active) }
scope :published, -> { where(published: true) }
def self.find_active_users
User.active # โ
Uses scope
end
def self.recent_published
User.published.order(created_at: :desc) # โ
Uses scope, preserves .order()
end
end๐ What It Detects
The cop flags ActiveRecord queries that match existing scopes:
- โ
where()clauses matching scope definitions - โ
joins()matching scope definitions - โ
order()matching scope definitions - โ Multiple conditions (normalized for hash key order)
- โ Queries with trailing methods (preserved during autocorrect)
Supported Query Methods
-
where/rewhere joinsorder-
limit,offset -
select,distinct -
group,having -
references,includes,preload
โ๏ธ Configuration
Enable/Disable
ScopeHunter/UseExistingScope:
Enabled: true # or false to disableAutocorrect Mode
ScopeHunter/UseExistingScope:
Enabled: true
Autocorrect: conservative # Default: conservativeSuggest Partial Matches
ScopeHunter/UseExistingScope:
Enabled: true
SuggestPartialMatches: true # Default: true๐ฏ How It Works
-
Indexing Phase: Scans your model files and indexes all
scopedefinitions - Detection Phase: For each ActiveRecord query, creates a normalized signature
- Matching: Compares query signatures against indexed scopes
- Flagging: Reports offenses when matches are found
- Autocorrect: Replaces duplicate queries with scope names, preserving trailing methods
Signature Normalization
The cop normalizes queries to match scopes regardless of:
- Hash key order:
{a: 1, b: 2}โก{b: 2, a: 1} - Hash syntax:
{status: :active}โก{:status => :active} - Query values: Only keys are matched (values are normalized to
?)
๐ Examples
Basic Where Clause
# Detected
scope :active, -> { where(status: :active) }
User.where(status: :active) # โ Flagged
# Autocorrected to
User.active # โ
With Trailing Methods
# Detected
scope :active, -> { where(status: :active) }
User.where(status: :active).order(:name).limit(10) # โ Flagged
# Autocorrected to
User.active.order(:name).limit(10) # โ
Trailing methods preservedMultiple Conditions
# Detected (hash order doesn't matter)
scope :active_published, -> { where(status: :active, published: true) }
User.where(published: true, status: :active) # โ Flagged
# Autocorrected to
User.active_published # โ
Joins
# Detected
scope :with_comments, -> { joins(:comments) }
Post.joins(:comments) # โ Flagged
# Autocorrected to
Post.with_comments # โ
Different Models
# Only matches within the same model
class User < ApplicationRecord
scope :active, -> { where(status: :active) }
end
class Post < ApplicationRecord
Post.where(status: :active) # โ
Not flagged (different model)
end๐งช Running RuboCop
# Check for offenses
bundle exec rubocop
# Check specific files
bundle exec rubocop app/models/
# Autocorrect offenses
bundle exec rubocop -A
# Check only ScopeHunter cop
bundle exec rubocop --only ScopeHunter/UseExistingScope๐ ๏ธ Development
After checking out the repo, run:
bin/setupTo install dependencies. Then, run:
bundle exec rspecTo run the tests.
To install this gem onto your local machine, run:
bundle exec rake installTo release a new version:
- Update the version number in
lib/scope_hunter/version.rb - Run
bundle exec rake release
This will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
๐ค Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/Ajithxolo/scope_hunter.
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Development Setup
- Fork the repository
- Clone your fork:
git clone https://github.com/YOUR_USERNAME/scope_hunter.git - Install dependencies:
bundle install - Run tests:
bundle exec rspec - Create a feature branch:
git checkout -b my-feature - Make your changes and add tests
- Run tests:
bundle exec rspec - Commit your changes:
git commit -am 'Add feature' - Push to the branch:
git push origin my-feature - Submit a pull request
๐ License
The gem is available as open source under the terms of the MIT License.
๐ Acknowledgments
- Built for the Ruby/Rails community
- Inspired by the need for DRY ActiveRecord code
- Thanks to all contributors!
๐ Resources
Made with โค๏ธ for the Ruby community