eager_eye 1.2.1 → 1.2.3

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: 2e098f00c3f4cffd7e4450d5fd8e2adaa01d9e1fca4d7180d996e6b9c646a448
4
- data.tar.gz: 4a2abc06d7986eaa0629e076ffc7950dfe0a7af81f051bee9faee608ee5f0779
3
+ metadata.gz: 2fbde5efcfa326afaf12645241e817095fbe2ac39ad69576f101d81f294b8337
4
+ data.tar.gz: dbd0a6d263aab997fe358cbf2194ce55629d587feffcd4652e7f13d58d459343
5
5
  SHA512:
6
- metadata.gz: b7683c5e0209f89058a99a0e4fd438f2253c231df17646baf7cfe59a18c2b617ebe9664ef10cc7e0243ae0cd863056940ba792ba82c671c908b757f5774ac04f
7
- data.tar.gz: 489810690ff2bef7ccb7c36c55367ffacbedbe9191393d0b27eeb7133457d896096f54d593133944b6f265f97d4aadec96464811975bcaea7ab0f3108d2ce3b5
6
+ metadata.gz: 4b0ddfa32f3e65955cc6da31339ca86985554709f229a7cc2ebc1ce84262f767c5104451df34e359e3d9f9470d4c916192652ed44ea958dbfcfe680b057b8996
7
+ data.tar.gz: 1cf0da613bed275088977144260c1b44ddd3ddadc72f063bce5ac3706a827a63e526dd043cc1e76a544fabe809b7c48432782900ac1602f25c3f218d44b426d0
data/CHANGELOG.md CHANGED
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.3] - 2026-02-15
11
+
12
+ ### Added
13
+
14
+ - **Batch Iteration Support** - All detectors now recognize `find_each`, `find_in_batches`, and `in_batches`
15
+ - `LoopAssociation`: Detects association calls inside `find_each` blocks
16
+ - `CallbackQuery`: Detects query iterations inside `find_each` in callbacks
17
+ - `MissingCounterCache`: Detects `.count`/`.size`/`.length` inside `find_each` blocks
18
+ - `CountInIteration`: Detects `.count` inside `find_each` blocks
19
+ - `CustomMethodQuery`: Detects `.where`/`.find_by` etc. inside `find_each` blocks
20
+ - Previously, `User.find_each { |u| u.posts }` was not flagged — now correctly detected as N+1
21
+
22
+ ## [1.2.2] - 2026-01-31
23
+
24
+ ### Fixed
25
+
26
+ - **CustomMethodQuery False Positive** - Skip PostgreSQL array column methods
27
+ - Methods ending with `_ids`, `_tags`, `_types`, `_codes`, `_names`, `_values` now recognized as array attributes
28
+ - `sector_subcategory_ids.first` no longer flagged (Ruby Array#first, not AR query)
29
+
30
+ - **LoopAssociation False Positive** - Skip common non-association attribute methods
31
+ - Added `origin`, `priority`, `level`, `kind`, `label`, `code`, `reason`, `amount`, `price`, `quantity`, `url`, `path`, `email`, `phone`, `address`, `notes`, `memo`, `data`, `metadata`, `position`, `rank`, `score`, `rating`, `enabled`, `disabled`, `active`, `published`, `draft`, `archived`, `locked`, `visible`, `hidden` to excluded methods
32
+ - Note: `category` and `tag` remain detectable as they're common association names - use inline suppression if they're string attributes in your codebase
33
+
34
+ - **PluckToArray False Positive** - Skip params-originated values
35
+ - `params[:ids].split(',').map(&:to_i)` no longer flagged
36
+ - `params` method now recognized as non-ActiveRecord source
37
+
10
38
  ## [1.2.1] - 2026-01-31
11
39
 
12
40
  ### 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.1-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.3-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>
@@ -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
@@ -6,7 +6,7 @@ module EagerEye
6
6
  module NonArSourceDetector
7
7
  NON_AR_RECEIVERS = %w[Sidekiq Redis Resque DelayedJob Queue Job Hash Array Set].freeze
8
8
  NON_DB_SOURCE_METHODS = %i[smembers sinter sunion sdiff zrange zrangebyscore lrange hkeys hvals hgetall
9
- keys values entries args].freeze
9
+ keys values entries args params].freeze
10
10
 
11
11
  private
12
12
 
@@ -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
@@ -7,7 +7,9 @@ module EagerEye
7
7
  maximum].freeze
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
- ITERATION_METHODS = %i[each map select find_all reject collect detect find_index flat_map].freeze
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
12
+ find_each find_in_batches in_batches].freeze
11
13
 
12
14
  def self.detector_name
13
15
  :custom_method_query
@@ -124,7 +126,13 @@ module EagerEye
124
126
  def receiver_ends_with_safe_transform_method?(node)
125
127
  return false unless node.is_a?(Parser::AST::Node) && node.type == :send
126
128
 
127
- SAFE_TRANSFORM_METHODS.include?(node.children[1])
129
+ method_name = node.children[1]
130
+ SAFE_TRANSFORM_METHODS.include?(method_name) || array_column_method?(method_name)
131
+ end
132
+
133
+ def array_column_method?(method_name)
134
+ method_str = method_name.to_s
135
+ ARRAY_COLUMN_SUFFIXES.any? { |suffix| method_str.end_with?(suffix) }
128
136
  end
129
137
 
130
138
  def add_issue(node)
@@ -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
@@ -19,7 +20,10 @@ module EagerEye
19
20
  id to_s to_h to_a to_json to_xml inspect class object_id nil? blank? present? empty?
20
21
  any? none? size count length save save! update update! destroy destroy! delete delete!
21
22
  valid? invalid? errors new? persisted? changed? frozen? name title body content text
22
- description value key type status state created_at updated_at deleted_at
23
+ description value key type status state created_at updated_at deleted_at origin
24
+ priority level kind label code reason amount price quantity url path email phone
25
+ address notes memo data metadata position rank score rating enabled disabled active
26
+ published draft archived locked visible hidden
23
27
  ].freeze
24
28
 
25
29
  def self.detector_name
@@ -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.1"
4
+ VERSION = "1.2.3"
5
5
  end
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.1
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamzagedikkaya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-30 00:00:00.000000000 Z
11
+ date: 2026-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast