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.
- 🔍 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
Add this line to your application's Gemfile:
gem 'scope_hunter'And then execute:
$ bundle installOr install it yourself as:
$ gem install scope_hunterAdd to your .rubocop.yml:
require:
- scope_hunter
AllCops:
NewCops: enable
ScopeHunter/UseExistingScope:
Enabled: trueBefore:
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
endThe 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)
where/rewherejoinsorderlimit,offsetselect,distinctgroup,havingreferences,includes,preload
ScopeHunter/UseExistingScope:
Enabled: true # or false to disableScopeHunter/UseExistingScope:
Enabled: true
Autocorrect: conservative # Default: conservativeScopeHunter/UseExistingScope:
Enabled: true
SuggestPartialMatches: true # Default: true- 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
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
?)
# Detected
scope :active, -> { where(status: :active) }
User.where(status: :active) # ❌ Flagged
# Autocorrected to
User.active # ✅# 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 preserved# 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 # ✅# Detected
scope :with_comments, -> { joins(:comments) }
Post.joins(:comments) # ❌ Flagged
# Autocorrected to
Post.with_comments # ✅# 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# 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/UseExistingScopeAfter 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.
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.
- 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
The gem is available as open source under the terms of the MIT License.
- Built for the Ruby/Rails community
- Inspired by the need for DRY ActiveRecord code
- Thanks to all contributors!
Made with ❤️ for the Ruby community