eager_eye 1.2.6 → 1.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +38 -7
- data/lib/eager_eye/analyzer.rb +9 -2
- data/lib/eager_eye/configuration.rb +3 -1
- data/lib/eager_eye/version.rb +1 -1
- data/lib/eager_eye.rb +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd5177a12d64b54cc6cd426440f79018da9ccc7dd931db70c491c9154bb385a1
|
|
4
|
+
data.tar.gz: dec16deb3b55ab821225d5d0e06a6fa308c41fd472dad405bf125c7e5839f2e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4336421ff71269732f594163dc3dbe29ca70d1b10c19dd333d972ea63bff9da36a4e83a041263fa6f61d758c8b0551912e075e00ce8f05978ab59c7db698af41
|
|
7
|
+
data.tar.gz: 51de528f0dca6b3279c0cb1fe6f21e5c35592dda81220419c209d8d43faf35b41375cbfffd33a463546f5e81e4807f8c48268f618d8383c82a570f81da823efa
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.7] - 2026-03-10
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **New Detector: `ScopeChainNPlusOne`** - Detects scope calls on associations inside iterations
|
|
15
|
+
- Catches `post.comments.recent`, `post.comments.approved.count` patterns
|
|
16
|
+
- Parses model files for `scope :name, -> { ... }` declarations
|
|
17
|
+
- Flags known scope names called on association chains inside loops
|
|
18
|
+
- Each scope call executes a new query per iteration — suggests preloading or joined queries
|
|
19
|
+
- **New Parser: `ScopeParser`** - Collects scope definitions from model files for cross-file detection
|
|
20
|
+
|
|
10
21
|
## [1.2.6] - 2026-02-25
|
|
11
22
|
|
|
12
23
|
### Changed
|
data/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml"><img src="https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml/badge.svg" alt="CI"></a>
|
|
13
|
-
<a href="https://rubygems.org/gems/eager_eye"><img src="https://img.shields.io/badge/gem-v1.2.
|
|
13
|
+
<a href="https://rubygems.org/gems/eager_eye"><img src="https://img.shields.io/badge/gem-v1.2.7-red.svg" alt="Gem Version"></a>
|
|
14
14
|
<a href="https://github.com/hamzagedikkaya/eager_eye"><img src="https://img.shields.io/badge/coverage-95%25-brightgreen.svg" alt="Coverage"></a>
|
|
15
15
|
<a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg" alt="Ruby"></a>
|
|
16
16
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
|
|
43
43
|
## Features
|
|
44
44
|
|
|
45
|
-
✨ **Detects
|
|
45
|
+
✨ **Detects 10 types of N+1 problems:**
|
|
46
46
|
- Loop associations (queries in iterations)
|
|
47
47
|
- Serializer nesting issues
|
|
48
48
|
- Missing counter caches
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
- Pluck to array misuse
|
|
53
53
|
- Delegation N+1s (hidden via `delegate :method, to: :association`)
|
|
54
54
|
- Decorator N+1s (Draper, SimpleDelegator, Presenter, ViewObject)
|
|
55
|
+
- Scope chain N+1s (named scopes on associations in loops)
|
|
55
56
|
|
|
56
57
|
🔧 **Developer-friendly:**
|
|
57
58
|
- Inline suppression (like RuboCop)
|
|
@@ -398,6 +399,33 @@ Supports the following object references inside decorators:
|
|
|
398
399
|
- `__getobj__` — SimpleDelegator standard
|
|
399
400
|
- `source`, `model` — alternative Draper aliases
|
|
400
401
|
|
|
402
|
+
### 10. Scope Chain N+1
|
|
403
|
+
|
|
404
|
+
Detects named scope calls on associations inside iterations. Unlike explicit query methods (`.where`, `.find_by`) caught by `CustomMethodQuery`, named scopes (`.recent`, `.active`, `.published`) are invisible query triggers.
|
|
405
|
+
|
|
406
|
+
```ruby
|
|
407
|
+
# Model
|
|
408
|
+
class Comment < ApplicationRecord
|
|
409
|
+
scope :recent, -> { where("created_at > ?", 1.week.ago) }
|
|
410
|
+
scope :approved, -> { where(approved: true) }
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# Bad - scope call per iteration
|
|
414
|
+
posts.each do |post|
|
|
415
|
+
post.comments.recent # Query for each post!
|
|
416
|
+
post.comments.approved.count # Query for each post!
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Good - preload and filter in Ruby
|
|
420
|
+
posts.includes(:comments).each do |post|
|
|
421
|
+
post.comments.select { |c| c.created_at > 1.week.ago }
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
EagerEye detects these by:
|
|
426
|
+
1. Scanning model files for `scope :name, -> { ... }` declarations
|
|
427
|
+
2. Flagging known scope names called on association chains inside iteration blocks
|
|
428
|
+
|
|
401
429
|
## Inline Suppression
|
|
402
430
|
|
|
403
431
|
Suppress false positives using inline comments (RuboCop-style):
|
|
@@ -442,6 +470,7 @@ Both CamelCase and snake_case formats are accepted:
|
|
|
442
470
|
| Pluck to Array | `PluckToArray` | `pluck_to_array` |
|
|
443
471
|
| Delegation N+1 | `DelegationNPlusOne` | `delegation_n_plus_one` |
|
|
444
472
|
| Decorator N+1 | `DecoratorNPlusOne` | `decorator_n_plus_one` |
|
|
473
|
+
| Scope Chain N+1 | `ScopeChainNPlusOne` | `scope_chain_n_plus_one` |
|
|
445
474
|
| All Detectors | `all` | `all` |
|
|
446
475
|
|
|
447
476
|
## Auto-fix (Experimental)
|
|
@@ -546,18 +575,20 @@ enabled_detectors:
|
|
|
546
575
|
- pluck_to_array
|
|
547
576
|
- delegation_n_plus_one
|
|
548
577
|
- decorator_n_plus_one
|
|
578
|
+
- scope_chain_n_plus_one
|
|
549
579
|
|
|
550
580
|
# Severity levels per detector (error, warning, info)
|
|
551
581
|
severity_levels:
|
|
552
|
-
loop_association: error
|
|
582
|
+
loop_association: error # Definite N+1
|
|
553
583
|
serializer_nesting: warning
|
|
554
584
|
custom_method_query: warning
|
|
555
585
|
count_in_iteration: warning
|
|
556
586
|
callback_query: warning
|
|
557
|
-
pluck_to_array: warning
|
|
558
|
-
delegation_n_plus_one: warning
|
|
559
|
-
decorator_n_plus_one: warning
|
|
560
|
-
|
|
587
|
+
pluck_to_array: warning # Optimization
|
|
588
|
+
delegation_n_plus_one: warning # Hidden delegation N+1
|
|
589
|
+
decorator_n_plus_one: warning # Decorator/Presenter N+1
|
|
590
|
+
scope_chain_n_plus_one: warning # Scope chain on association
|
|
591
|
+
missing_counter_cache: info # Suggestion
|
|
561
592
|
|
|
562
593
|
# Minimum severity to report (default: info)
|
|
563
594
|
min_severity: warning
|
data/lib/eager_eye/analyzer.rb
CHANGED
|
@@ -13,16 +13,18 @@ module EagerEye
|
|
|
13
13
|
callback_query: Detectors::CallbackQuery,
|
|
14
14
|
pluck_to_array: Detectors::PluckToArray,
|
|
15
15
|
delegation_n_plus_one: Detectors::DelegationNPlusOne,
|
|
16
|
-
decorator_n_plus_one: Detectors::DecoratorNPlusOne
|
|
16
|
+
decorator_n_plus_one: Detectors::DecoratorNPlusOne,
|
|
17
|
+
scope_chain_n_plus_one: Detectors::ScopeChainNPlusOne
|
|
17
18
|
}.freeze
|
|
18
19
|
|
|
19
|
-
attr_reader :paths, :issues, :association_preloads, :delegation_maps
|
|
20
|
+
attr_reader :paths, :issues, :association_preloads, :delegation_maps, :scope_maps
|
|
20
21
|
|
|
21
22
|
def initialize(paths: nil)
|
|
22
23
|
@paths = Array(paths || EagerEye.configuration.app_path)
|
|
23
24
|
@issues = []
|
|
24
25
|
@association_preloads = {}
|
|
25
26
|
@delegation_maps = {}
|
|
27
|
+
@scope_maps = {}
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def run
|
|
@@ -48,6 +50,10 @@ module EagerEye
|
|
|
48
50
|
deleg_parser = DelegationParser.new
|
|
49
51
|
deleg_parser.parse_model(ast, model_name)
|
|
50
52
|
@delegation_maps.merge!(deleg_parser.delegation_maps)
|
|
53
|
+
|
|
54
|
+
scope_parser = ScopeParser.new
|
|
55
|
+
scope_parser.parse_model(ast, model_name)
|
|
56
|
+
@scope_maps.merge!(scope_parser.scope_maps)
|
|
51
57
|
rescue Errno::ENOENT, Errno::EACCES
|
|
52
58
|
next
|
|
53
59
|
end
|
|
@@ -108,6 +114,7 @@ module EagerEye
|
|
|
108
114
|
args = [ast, file_path]
|
|
109
115
|
args << @association_preloads if detector.is_a?(Detectors::LoopAssociation)
|
|
110
116
|
args << @delegation_maps if detector.is_a?(Detectors::DelegationNPlusOne)
|
|
117
|
+
args << @scope_maps if detector.is_a?(Detectors::ScopeChainNPlusOne)
|
|
111
118
|
args
|
|
112
119
|
end
|
|
113
120
|
|
|
@@ -9,6 +9,7 @@ module EagerEye
|
|
|
9
9
|
loop_association serializer_nesting missing_counter_cache
|
|
10
10
|
custom_method_query count_in_iteration callback_query
|
|
11
11
|
pluck_to_array delegation_n_plus_one decorator_n_plus_one
|
|
12
|
+
scope_chain_n_plus_one
|
|
12
13
|
].freeze
|
|
13
14
|
|
|
14
15
|
DEFAULT_SEVERITY_LEVELS = {
|
|
@@ -20,7 +21,8 @@ module EagerEye
|
|
|
20
21
|
callback_query: :warning,
|
|
21
22
|
pluck_to_array: :warning,
|
|
22
23
|
delegation_n_plus_one: :warning,
|
|
23
|
-
decorator_n_plus_one: :warning
|
|
24
|
+
decorator_n_plus_one: :warning,
|
|
25
|
+
scope_chain_n_plus_one: :warning
|
|
24
26
|
}.freeze
|
|
25
27
|
|
|
26
28
|
VALID_SEVERITIES = %i[info warning error].freeze
|
data/lib/eager_eye/version.rb
CHANGED
data/lib/eager_eye.rb
CHANGED
|
@@ -6,6 +6,7 @@ require_relative "eager_eye/configuration"
|
|
|
6
6
|
require_relative "eager_eye/issue"
|
|
7
7
|
require_relative "eager_eye/association_parser"
|
|
8
8
|
require_relative "eager_eye/delegation_parser"
|
|
9
|
+
require_relative "eager_eye/scope_parser"
|
|
9
10
|
require_relative "eager_eye/detectors/base"
|
|
10
11
|
require_relative "eager_eye/detectors/loop_association"
|
|
11
12
|
require_relative "eager_eye/detectors/serializer_nesting"
|
|
@@ -16,6 +17,7 @@ require_relative "eager_eye/detectors/callback_query"
|
|
|
16
17
|
require_relative "eager_eye/detectors/pluck_to_array"
|
|
17
18
|
require_relative "eager_eye/detectors/delegation_n_plus_one"
|
|
18
19
|
require_relative "eager_eye/detectors/decorator_n_plus_one"
|
|
20
|
+
require_relative "eager_eye/detectors/scope_chain_n_plus_one"
|
|
19
21
|
require_relative "eager_eye/comment_parser"
|
|
20
22
|
require_relative "eager_eye/analyzer"
|
|
21
23
|
require_relative "eager_eye/fixers/base"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eager_eye
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamzagedikkaya
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ast
|