0.0
The project is in a healthy, maintained state
Scope Hunter is a RuboCop extension that detects ActiveRecord query chains that duplicate existing named scopes and suggests using those scopes instead. It indexes model scopes, canonicalizes relation chains, and flags matches with an autocorrect that replaces the initial query with Model.scope while preserving any trailing methods. This keeps query logic DRY, improves readability, and helps teams discover and reuse well-named scopes.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Development

>= 1.40

Runtime

~> 3.3
>= 1.60
 Project Readme

Scope Hunter

Ruby Version RuboCop License

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 install

Or 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: true

Example

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
end

After 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
  • joins
  • order
  • limit, offset
  • select, distinct
  • group, having
  • references, includes, preload

โš™๏ธ Configuration

Enable/Disable

ScopeHunter/UseExistingScope:
  Enabled: true  # or false to disable

Autocorrect Mode

ScopeHunter/UseExistingScope:
  Enabled: true
  Autocorrect: conservative  # Default: conservative

Suggest Partial Matches

ScopeHunter/UseExistingScope:
  Enabled: true
  SuggestPartialMatches: true  # Default: true

๐ŸŽฏ How It Works

  1. Indexing Phase: Scans your model files and indexes all scope definitions
  2. Detection Phase: For each ActiveRecord query, creates a normalized signature
  3. Matching: Compares query signatures against indexed scopes
  4. Flagging: Reports offenses when matches are found
  5. 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 preserved

Multiple 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/setup

To install dependencies. Then, run:

bundle exec rspec

To run the tests.

To install this gem onto your local machine, run:

bundle exec rake install

To release a new version:

  1. Update the version number in lib/scope_hunter/version.rb
  2. 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

  1. Fork the repository
  2. Clone your fork: git clone https://github.com/YOUR_USERNAME/scope_hunter.git
  3. Install dependencies: bundle install
  4. Run tests: bundle exec rspec
  5. Create a feature branch: git checkout -b my-feature
  6. Make your changes and add tests
  7. Run tests: bundle exec rspec
  8. Commit your changes: git commit -am 'Add feature'
  9. Push to the branch: git push origin my-feature
  10. 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