eager_eye 1.2.2 → 1.2.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a432b16aece37497880140ff64a88cd3dabe6ae603846a513d21f71a8b11b5a1
4
- data.tar.gz: 330aac1ef792a5ca24597dcd4128329e20f05959ea45437e244788b94f12026d
3
+ metadata.gz: 2dee00b4a1b227d3fe506af3663b4a00730c312d61f9a1db876c4a59e7cbbd62
4
+ data.tar.gz: 1ce8596e9752c3b2f4fc2fa2d9463fcb9eeca1f50f9031c9ef1acbaef95ba08f
5
5
  SHA512:
6
- metadata.gz: 192aa6bb71494f09091ac64a44de11526db09ec389cf41db80229457d59d52f5e81c9b9ae05cc6892ec11c646078a12520c657940a986c47f6519af6b956526f
7
- data.tar.gz: 3e18421c967675ea0c10ab19ff7f0426d2a2d8e2fcf969a20b6bc0571f0f32a40c8f515b16c464df57c67b447fe0e9c3b3087b22cee37bbda3bee89b1dd3d418
6
+ metadata.gz: 1c453c88107dfa5b3fbbdf1c66a2f2ca5fc2d2df3a99f91e27827f36a720e59d4974e9f880fad2668adf05116a81bcd69b8cdca6d3a0c0e24b8f3332f3c886a1
7
+ data.tar.gz: a1bc480d5b3d1d82eb8eccb7697ff50cab9ba94fbf127a08cd39f3278dfe90a95ebc14df28cf518e26bac86ad2ba59eaacdb5efc783e58caae8187d670b4f071
data/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.4] - 2026-02-21
11
+
12
+ ### Added
13
+
14
+ - **New Detector: `DelegationNPlusOne`** - Detects hidden N+1 queries caused by `delegate :method, to: :association`
15
+ - `delegate :name, :email, to: :user` calls inside loops load the target association on each iteration
16
+ - EagerEye previously could not catch these because `order.name` looks like a plain attribute, not an association access
17
+ - Parses model files for `delegate` declarations and tracks delegated method → association mappings
18
+ - Detects delegated method calls in `each`, `map`, `select`, `flat_map`, `find_each`, and all other iteration methods
19
+ - Respects `includes`, `preload`, and `eager_load` — suppresses warnings when the target association is preloaded
20
+ - Supports both local (same-file) delegate declarations and cross-file detection via model parsing
21
+
22
+ ## [1.2.3] - 2026-02-15
23
+
24
+ ### Added
25
+
26
+ - **Batch Iteration Support** - All detectors now recognize `find_each`, `find_in_batches`, and `in_batches`
27
+ - `LoopAssociation`: Detects association calls inside `find_each` blocks
28
+ - `CallbackQuery`: Detects query iterations inside `find_each` in callbacks
29
+ - `MissingCounterCache`: Detects `.count`/`.size`/`.length` inside `find_each` blocks
30
+ - `CountInIteration`: Detects `.count` inside `find_each` blocks
31
+ - `CustomMethodQuery`: Detects `.where`/`.find_by` etc. inside `find_each` blocks
32
+ - Previously, `User.find_each { |u| u.posts }` was not flagged — now correctly detected as N+1
33
+
10
34
  ## [1.2.2] - 2026-01-31
11
35
 
12
36
  ### Fixed
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.2-red.svg" alt="Gem Version"></a>
13
+ <a href="https://rubygems.org/gems/eager_eye"><img src="https://img.shields.io/badge/gem-v1.2.4-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 7 types of N+1 problems:**
45
+ ✨ **Detects 8 types of N+1 problems:**
46
46
  - Loop associations (queries in iterations)
47
47
  - Serializer nesting issues
48
48
  - Missing counter caches
@@ -50,6 +50,7 @@
50
50
  - Count in iteration patterns
51
51
  - Callback query N+1s
52
52
  - Pluck to array misuse
53
+ - Delegation N+1s (hidden via `delegate :method, to: :association`)
53
54
 
54
55
  🔧 **Developer-friendly:**
55
56
  - Inline suppression (like RuboCop)
@@ -339,6 +340,35 @@ Post.where(user_id: User.active.select(:id))
339
340
  - ⚠️ **Warning** - Scoped `.pluck(:id)` (two queries, memory overhead)
340
341
  - 🔴 **Error** - Unscoped `.all.pluck(:id)` (loads entire table)
341
342
 
343
+ ### 8. Delegation N+1
344
+
345
+ Detects when methods delegated via `delegate :method, to: :association` are called inside loops without preloading the target association. These are invisible to `LoopAssociation` because `order.name` looks like a plain attribute, not an association access.
346
+
347
+ ```ruby
348
+ # Model
349
+ class Order < ApplicationRecord
350
+ belongs_to :user
351
+ delegate :full_name, :email, to: :user
352
+ end
353
+
354
+ # Bad - N+1 (each call hits the database for user)
355
+ orders.each do |order|
356
+ order.full_name # actually: order.user.full_name — loads user for each order!
357
+ order.email # actually: order.user.email — another load!
358
+ end
359
+
360
+ # Good - Eager load the delegated-to association
361
+ orders.includes(:user).each do |order|
362
+ order.full_name # no N+1 — user is already loaded
363
+ order.email # no N+1 — user is already loaded
364
+ end
365
+ ```
366
+
367
+ EagerEye detects these by:
368
+ 1. Scanning model files for `delegate :method, to: :assoc` declarations
369
+ 2. Tracking which methods delegate to which associations
370
+ 3. Flagging calls to those methods inside iteration blocks when the association is not preloaded
371
+
342
372
  ## Inline Suppression
343
373
 
344
374
  Suppress false positives using inline comments (RuboCop-style):
@@ -381,6 +411,7 @@ Both CamelCase and snake_case formats are accepted:
381
411
  | Count in Iteration | `CountInIteration` | `count_in_iteration` |
382
412
  | Callback Query | `CallbackQuery` | `callback_query` |
383
413
  | Pluck to Array | `PluckToArray` | `pluck_to_array` |
414
+ | Delegation N+1 | `DelegationNPlusOne` | `delegation_n_plus_one` |
384
415
  | All Detectors | `all` | `all` |
385
416
 
386
417
  ## Auto-fix (Experimental)
@@ -483,6 +514,7 @@ enabled_detectors:
483
514
  - count_in_iteration
484
515
  - callback_query
485
516
  - pluck_to_array
517
+ - delegation_n_plus_one
486
518
 
487
519
  # Severity levels per detector (error, warning, info)
488
520
  severity_levels:
@@ -492,6 +524,7 @@ severity_levels:
492
524
  count_in_iteration: warning
493
525
  callback_query: warning
494
526
  pluck_to_array: warning # Optimization
527
+ delegation_n_plus_one: warning # Hidden delegation N+1
495
528
  missing_counter_cache: info # Suggestion
496
529
 
497
530
  # Minimum severity to report (default: info)
@@ -11,20 +11,23 @@ module EagerEye
11
11
  custom_method_query: Detectors::CustomMethodQuery,
12
12
  count_in_iteration: Detectors::CountInIteration,
13
13
  callback_query: Detectors::CallbackQuery,
14
- pluck_to_array: Detectors::PluckToArray
14
+ pluck_to_array: Detectors::PluckToArray,
15
+ delegation_n_plus_one: Detectors::DelegationNPlusOne
15
16
  }.freeze
16
17
 
17
- attr_reader :paths, :issues, :association_preloads
18
+ attr_reader :paths, :issues, :association_preloads, :delegation_maps
18
19
 
19
20
  def initialize(paths: nil)
20
21
  @paths = Array(paths || EagerEye.configuration.app_path)
21
22
  @issues = []
22
23
  @association_preloads = {}
24
+ @delegation_maps = {}
23
25
  end
24
26
 
25
27
  def run
26
28
  @issues = []
27
29
  collect_association_preloads
30
+ collect_delegation_maps
28
31
  analyze_files
29
32
  @issues
30
33
  end
@@ -44,6 +47,19 @@ module EagerEye
44
47
  nil
45
48
  end
46
49
 
50
+ def collect_delegation_maps
51
+ model_files.each do |file_path|
52
+ ast = parse_source(File.read(file_path))
53
+ next unless ast
54
+
55
+ parser = DelegationParser.new
56
+ parser.parse_model(ast, extract_model_name(file_path))
57
+ @delegation_maps.merge!(parser.delegation_maps)
58
+ end
59
+ rescue StandardError
60
+ nil
61
+ end
62
+
47
63
  def model_files
48
64
  Dir.glob(File.join(@paths[0], "models", "**", "*.rb"))
49
65
  end
@@ -85,10 +101,7 @@ module EagerEye
85
101
  min_severity = EagerEye.configuration.min_severity
86
102
 
87
103
  enabled_detectors.each do |detector|
88
- args = [ast, file_path]
89
- args << @association_preloads if detector.is_a?(Detectors::LoopAssociation)
90
-
91
- file_issues = detector.detect(*args)
104
+ file_issues = detector.detect(*detector_args(detector, ast, file_path))
92
105
  file_issues.reject! { |issue| comment_parser.disabled_at?(issue.line_number, issue.detector) }
93
106
  file_issues.select! { |issue| issue.meets_minimum_severity?(min_severity) }
94
107
  @issues.concat(file_issues)
@@ -103,6 +116,13 @@ module EagerEye
103
116
  nil
104
117
  end
105
118
 
119
+ def detector_args(detector, ast, file_path)
120
+ args = [ast, file_path]
121
+ args << @association_preloads if detector.is_a?(Detectors::LoopAssociation)
122
+ args << @delegation_maps if detector.is_a?(Detectors::DelegationNPlusOne)
123
+ args
124
+ end
125
+
106
126
  def enabled_detectors
107
127
  @enabled_detectors ||= EagerEye.configuration.enabled_detectors.filter_map do |name|
108
128
  detector_class = DETECTOR_CLASSES[name]
@@ -8,7 +8,7 @@ module EagerEye
8
8
  DEFAULT_DETECTORS = %i[
9
9
  loop_association serializer_nesting missing_counter_cache
10
10
  custom_method_query count_in_iteration callback_query
11
- pluck_to_array
11
+ pluck_to_array delegation_n_plus_one
12
12
  ].freeze
13
13
 
14
14
  DEFAULT_SEVERITY_LEVELS = {
@@ -18,7 +18,8 @@ module EagerEye
18
18
  custom_method_query: :warning,
19
19
  count_in_iteration: :warning,
20
20
  callback_query: :warning,
21
- pluck_to_array: :warning
21
+ pluck_to_array: :warning,
22
+ delegation_n_plus_one: :warning
22
23
  }.freeze
23
24
 
24
25
  VALID_SEVERITIES = %i[info warning error].freeze
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerEye
4
+ class DelegationParser
5
+ attr_reader :delegation_maps
6
+
7
+ def initialize
8
+ @delegation_maps = {}
9
+ end
10
+
11
+ def parse_model(ast, model_name)
12
+ return unless ast
13
+
14
+ traverse(ast, model_name)
15
+ end
16
+
17
+ private
18
+
19
+ def traverse(node, model_name)
20
+ return unless node.is_a?(Parser::AST::Node)
21
+
22
+ check_delegate(node, model_name)
23
+ node.children.each { |child| traverse(child, model_name) }
24
+ end
25
+
26
+ def check_delegate(node, model_name)
27
+ return unless bare_delegate_call?(node)
28
+
29
+ args = node.children[2..]
30
+ methods = delegate_methods(args)
31
+ return if methods.empty?
32
+
33
+ to_target = extract_to_target(args)
34
+ return unless to_target
35
+
36
+ register_delegates(model_name, methods, to_target)
37
+ end
38
+
39
+ def bare_delegate_call?(node)
40
+ node.type == :send && node.children[0].nil? && node.children[1] == :delegate
41
+ end
42
+
43
+ def delegate_methods(args)
44
+ args.select { |a| a&.type == :sym }.map { |a| a.children[0] }
45
+ end
46
+
47
+ def register_delegates(model_name, methods, to_target)
48
+ @delegation_maps[model_name] ||= {}
49
+ methods.each { |m| @delegation_maps[model_name][m] = to_target }
50
+ end
51
+
52
+ def extract_to_target(args)
53
+ hash_arg = args.find { |a| a&.type == :hash }
54
+ return unless hash_arg
55
+
56
+ to_pair = hash_arg.children.find { |p| to_key_pair?(p) }
57
+ extract_sym_value(to_pair)
58
+ end
59
+
60
+ def extract_sym_value(node)
61
+ return unless node
62
+
63
+ value = node.children[1]
64
+ value.children[0] if value&.type == :sym
65
+ end
66
+
67
+ def to_key_pair?(pair)
68
+ pair.type == :pair &&
69
+ pair.children[0]&.type == :sym &&
70
+ pair.children[0].children[0] == :to
71
+ end
72
+ end
73
+ end
@@ -26,7 +26,9 @@ module EagerEye
26
26
  reload
27
27
  ].freeze
28
28
 
29
- ITERATION_METHODS = %i[each map select find_all reject collect].freeze
29
+ ITERATION_METHODS = %i[each map select find_all reject collect
30
+ find_each find_in_batches in_batches].freeze
31
+ AR_BATCH_METHODS = %i[find_each find_in_batches in_batches].freeze
30
32
 
31
33
  def self.detector_name
32
34
  :callback_query
@@ -110,7 +112,10 @@ module EagerEye
110
112
 
111
113
  def iteration_block?(node)
112
114
  return false unless node.type == :block && node.children[0]&.type == :send
113
- return false unless ITERATION_METHODS.include?(node.children[0].children[1])
115
+
116
+ method_name = node.children[0].children[1]
117
+ return false unless ITERATION_METHODS.include?(method_name)
118
+ return true if AR_BATCH_METHODS.include?(method_name)
114
119
 
115
120
  !static_collection?(node.children[0].children[0])
116
121
  end
@@ -4,7 +4,8 @@ module EagerEye
4
4
  module Detectors
5
5
  class CountInIteration < Base
6
6
  COUNT_METHODS = %i[count].freeze
7
- ITERATION_METHODS = %i[each map select find_all reject collect each_with_index each_with_object flat_map].freeze
7
+ ITERATION_METHODS = %i[each map select find_all reject collect each_with_index each_with_object flat_map
8
+ find_each find_in_batches in_batches].freeze
8
9
 
9
10
  def self.detector_name
10
11
  :count_in_iteration
@@ -8,7 +8,8 @@ module EagerEye
8
8
  SAFE_QUERY_METHODS = %i[first last take count sum find size length ids].freeze
9
9
  SAFE_TRANSFORM_METHODS = %i[keys values split [] params sort pluck ids to_s to_a to_i chars bytes].freeze
10
10
  ARRAY_COLUMN_SUFFIXES = %w[_ids _tags _types _codes _names _values].freeze
11
- ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map].freeze
11
+ ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map
12
+ find_each find_in_batches in_batches].freeze
12
13
 
13
14
  def self.detector_name
14
15
  :custom_method_query
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EagerEye
4
+ module Detectors
5
+ class DelegationNPlusOne < Base
6
+ ITERATION_METHODS = %i[
7
+ each map collect select find_all reject filter filter_map flat_map
8
+ find_each find_in_batches in_batches
9
+ ].freeze
10
+ PRELOAD_METHODS = %i[includes preload eager_load].freeze
11
+
12
+ def self.detector_name
13
+ :delegation_n_plus_one
14
+ end
15
+
16
+ def detect(ast, file_path, delegation_maps = {})
17
+ return [] unless ast
18
+
19
+ issues = []
20
+ local_delegates = collect_local_delegates(ast)
21
+
22
+ traverse_ast(ast) do |node|
23
+ next unless iteration_block?(node)
24
+
25
+ block_var = extract_block_variable(node)
26
+ next unless block_var
27
+
28
+ block_body = node.children[2]
29
+ next unless block_body
30
+
31
+ collection_node = node.children[0]
32
+ model_name = infer_model_name(collection_node)
33
+ delegates = build_delegates(model_name, delegation_maps, local_delegates)
34
+ next if delegates.empty?
35
+
36
+ included = extract_included_associations(collection_node)
37
+ find_delegated_calls(block_body, block_var, delegates, included, file_path, issues)
38
+ end
39
+
40
+ issues
41
+ end
42
+
43
+ private
44
+
45
+ def collect_local_delegates(ast)
46
+ delegates = {}
47
+ traverse_ast(ast) do |node|
48
+ next unless delegate_call?(node)
49
+
50
+ extract_delegate_info(node, delegates)
51
+ end
52
+ delegates
53
+ end
54
+
55
+ def delegate_call?(node)
56
+ node.type == :send && node.children[0].nil? && node.children[1] == :delegate
57
+ end
58
+
59
+ def extract_delegate_info(node, delegates)
60
+ args = node.children[2..]
61
+ methods = args.select { |a| a&.type == :sym }.map { |a| a.children[0] }
62
+ return if methods.empty?
63
+
64
+ to_target = extract_to_target(args)
65
+ return unless to_target
66
+
67
+ methods.each { |m| delegates[m] = to_target }
68
+ end
69
+
70
+ def extract_to_target(args)
71
+ hash_arg = args.find { |a| a&.type == :hash }
72
+ return unless hash_arg
73
+
74
+ to_pair = hash_arg.children.find { |p| to_key_pair?(p) }
75
+ extract_sym_value(to_pair)
76
+ end
77
+
78
+ def extract_sym_value(node)
79
+ return unless node
80
+
81
+ value = node.children[1]
82
+ value.children[0] if value&.type == :sym
83
+ end
84
+
85
+ def to_key_pair?(pair)
86
+ pair.type == :pair &&
87
+ pair.children[0]&.type == :sym &&
88
+ pair.children[0].children[0] == :to
89
+ end
90
+
91
+ def build_delegates(model_name, delegation_maps, local_delegates)
92
+ cross_file = model_name ? (delegation_maps[model_name] || {}) : {}
93
+ cross_file.merge(local_delegates)
94
+ end
95
+
96
+ def iteration_block?(node)
97
+ node.type == :block &&
98
+ node.children[0]&.type == :send &&
99
+ ITERATION_METHODS.include?(node.children[0].children[1])
100
+ end
101
+
102
+ def extract_block_variable(block_node)
103
+ args = block_node&.children&.[](1)
104
+ first_arg = args&.children&.first
105
+ first_arg&.type == :arg ? first_arg.children[0] : nil
106
+ end
107
+
108
+ def infer_model_name(node)
109
+ root = find_root_receiver(node)
110
+ root&.type == :const ? root.children[1].to_s : nil
111
+ end
112
+
113
+ def find_root_receiver(node)
114
+ current = node
115
+ current = current.children[0] while current&.type == :send
116
+ current
117
+ end
118
+
119
+ def extract_included_associations(collection_node)
120
+ included = Set.new
121
+ return included unless collection_node&.type == :send
122
+
123
+ current = collection_node
124
+ while current&.type == :send
125
+ extract_from_preload(current, included) if PRELOAD_METHODS.include?(current.children[1])
126
+ current = current.children[0]
127
+ end
128
+ included
129
+ end
130
+
131
+ def extract_from_preload(method_node, included_set)
132
+ args = extract_method_args(method_node)
133
+ included_set.merge(extract_symbols_from_args(args))
134
+ end
135
+
136
+ def find_delegated_calls(block_body, block_var, delegates, included, file_path, issues)
137
+ reported = Set.new
138
+ traverse_ast(block_body) do |node|
139
+ target_assoc = delegation_target(node, block_var, delegates, included, reported)
140
+ next unless target_assoc
141
+
142
+ issues << create_delegation_issue(node, block_var, target_assoc, file_path)
143
+ end
144
+ end
145
+
146
+ def delegation_target(node, block_var, delegates, included, reported)
147
+ return unless node.type == :send
148
+ return unless block_var_receiver?(node, block_var)
149
+
150
+ method = node.children[1]
151
+ target_assoc = delegates[method]
152
+ return unless target_assoc
153
+ return if included.include?(target_assoc)
154
+ return unless reported.add?("#{node.loc.line}:#{method}")
155
+
156
+ target_assoc
157
+ end
158
+
159
+ def block_var_receiver?(node, block_var)
160
+ receiver = node.children[0]
161
+ receiver&.type == :lvar && receiver.children[0] == block_var
162
+ end
163
+
164
+ def create_delegation_issue(node, block_var, target_assoc, file_path)
165
+ method = node.children[1]
166
+ create_issue(
167
+ file_path: file_path,
168
+ line_number: node.loc.line,
169
+ message: "Potential N+1: `#{block_var}.#{method}` is delegated to `#{target_assoc}` — " \
170
+ "loads `#{target_assoc}` on each iteration",
171
+ suggestion: "Use `includes(:#{target_assoc})` before iterating"
172
+ )
173
+ end
174
+ end
175
+ end
176
+ end
@@ -3,7 +3,8 @@
3
3
  module EagerEye
4
4
  module Detectors
5
5
  class LoopAssociation < Base
6
- ITERATION_METHODS = %i[each map collect select find find_all reject filter filter_map flat_map].freeze
6
+ ITERATION_METHODS = %i[each map collect select find find_all reject filter filter_map flat_map
7
+ find_each find_in_batches in_batches].freeze
7
8
  PRELOAD_METHODS = %i[includes preload eager_load].freeze
8
9
  SINGLE_RECORD_METHODS = %i[find find_by find_by! first first! last last! take take! second third fourth fifth
9
10
  forty_two sole find_sole_by].freeze
@@ -10,7 +10,8 @@ module EagerEye
10
10
  likes favorites bookmarks votes children replies responses answers questions
11
11
  ].freeze
12
12
  ITERATION_METHODS = %i[each map collect select reject find_all filter filter_map flat_map
13
- each_with_index each_with_object reduce inject sum].freeze
13
+ each_with_index each_with_object reduce inject sum
14
+ find_each find_in_batches in_batches].freeze
14
15
 
15
16
  def self.detector_name
16
17
  :missing_counter_cache
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "1.2.2"
4
+ VERSION = "1.2.4"
5
5
  end
data/lib/eager_eye.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "eager_eye/version"
5
5
  require_relative "eager_eye/configuration"
6
6
  require_relative "eager_eye/issue"
7
7
  require_relative "eager_eye/association_parser"
8
+ require_relative "eager_eye/delegation_parser"
8
9
  require_relative "eager_eye/detectors/base"
9
10
  require_relative "eager_eye/detectors/loop_association"
10
11
  require_relative "eager_eye/detectors/serializer_nesting"
@@ -13,6 +14,7 @@ require_relative "eager_eye/detectors/custom_method_query"
13
14
  require_relative "eager_eye/detectors/count_in_iteration"
14
15
  require_relative "eager_eye/detectors/callback_query"
15
16
  require_relative "eager_eye/detectors/pluck_to_array"
17
+ require_relative "eager_eye/detectors/delegation_n_plus_one"
16
18
  require_relative "eager_eye/comment_parser"
17
19
  require_relative "eager_eye/analyzer"
18
20
  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.2
4
+ version: 1.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamzagedikkaya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-01 00:00:00.000000000 Z
11
+ date: 2026-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast
@@ -64,11 +64,13 @@ files:
64
64
  - lib/eager_eye/cli.rb
65
65
  - lib/eager_eye/comment_parser.rb
66
66
  - lib/eager_eye/configuration.rb
67
+ - lib/eager_eye/delegation_parser.rb
67
68
  - lib/eager_eye/detectors/base.rb
68
69
  - lib/eager_eye/detectors/callback_query.rb
69
70
  - lib/eager_eye/detectors/concerns/non_ar_source_detector.rb
70
71
  - lib/eager_eye/detectors/count_in_iteration.rb
71
72
  - lib/eager_eye/detectors/custom_method_query.rb
73
+ - lib/eager_eye/detectors/delegation_n_plus_one.rb
72
74
  - lib/eager_eye/detectors/loop_association.rb
73
75
  - lib/eager_eye/detectors/missing_counter_cache.rb
74
76
  - lib/eager_eye/detectors/pluck_to_array.rb