eager_eye 1.0.2 → 1.0.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: 9a37ac20117548282deb586bacdab0deab006b41581efa0810d3b64ffdcf1465
4
- data.tar.gz: fa2dc1e3b2fa8582c0b48606ee88a75de9f7d6638593ea3404493b1383ad9512
3
+ metadata.gz: ecc47488d2632329805448f55dfafc1f6670efd02b04a3dd0464c22ef9a3a4ba
4
+ data.tar.gz: b37b1be91854abeea6a11d33f28df1ada2a3fb0a427b28a892cb1249c22a7e67
5
5
  SHA512:
6
- metadata.gz: 8d4c41a777a9d79da47c9b0a5ce467acd951fdf1e72682e0ea235b5cc10329c403177c039ee7beb42a2f0b025796e6484d5e412f0e7697ad66902961f1348c2c
7
- data.tar.gz: e293e6c338bc7413d922e3d0daad30e0033477b7819f5b95b551dd8aebe53d0efd350615ece1ff6cadabc03267fcce7aaa08814deb8fb16620f0974daa41a597
6
+ metadata.gz: 19adae5e3c000cfa658f5d2615da31c9a702ff031766240b86968d69165445bbbc7b630b5a1596822eaa9683c23db84379127d0c83ed4a59366475b3585972b7
7
+ data.tar.gz: a87d19f36dcaf9bd7a360b31051ed32da241e7c0b867815e8e17962f4b29bdd43d6983d84981e54a350ba4d415a076a84a8b1abc6852311277f25398c51d6a1a
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.3] - 2025-12-20
11
+
12
+ ### Fixed
13
+
14
+ - Only flag queries in callbacks where the iteration variable is the receiver (reduces false positives)
15
+ - Updated README callback query documentation to accurately reflect current behavior
16
+
17
+ ## [1.0.2] - 2025-12-19
18
+
19
+ ### Fixed
20
+
21
+ - Only detect queries inside iterations in callback detector
22
+
10
23
  ## [1.0.1] - 2025-12-16
11
24
 
12
25
  ### Changed
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # EagerEye
2
2
 
3
3
  [![CI](https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml/badge.svg)](https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml)
4
- [![Gem Version](https://img.shields.io/badge/gem-v1.0.1-red.svg)](https://rubygems.org/gems/eager_eye)
4
+ [![Gem Version](https://img.shields.io/badge/gem-v1.0.3-red.svg)](https://rubygems.org/gems/eager_eye)
5
5
  [![Coverage](https://img.shields.io/badge/coverage-95%25-brightgreen.svg)](https://github.com/hamzagedikkaya/eager_eye)
6
6
  [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-ruby.svg)](https://www.ruby-lang.org/)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
@@ -209,42 +209,45 @@ user.posts_count # Just reads the column
209
209
 
210
210
  ### 6. Callback Query Detection
211
211
 
212
- Detects database queries and iterations inside ActiveRecord callbacks. These become performance disasters during bulk operations.
212
+ Detects N+1 patterns inside ActiveRecord callbacks - specifically iterations that execute queries on each loop.
213
213
 
214
214
  ```ruby
215
- # Bad - Queries in callbacks
216
- class Article < ApplicationRecord
217
- after_save :recalculate_stats
215
+ # Bad - N+1 in callback (DETECTED)
216
+ class Order < ApplicationRecord
217
+ after_create :notify_subscribers
218
218
 
219
- def recalculate_stats
220
- author.articles.published.count # Runs on EVERY save
221
- category.update_article_count! # Another query on EVERY save
219
+ def notify_subscribers
220
+ customer.followers.each do |follower| # Error: Iteration in callback
221
+ follower.notifications.create!(...) # Warning: Query on iteration variable
222
+ end
222
223
  end
223
224
  end
224
225
 
225
- # Disaster scenario
226
- Article.import(1000.times.map { |i| { title: "Post #{i}" } })
227
- # = 2000+ queries from callbacks!
226
+ # OK - Single query in callback (NOT flagged - not N+1)
227
+ class Article < ApplicationRecord
228
+ after_save :update_stats
228
229
 
229
- # Bad - N+1 in callback
230
- class Order < ApplicationRecord
231
- after_create :notify_subscribers
230
+ def update_stats
231
+ author.articles.count # Single query, acceptable
232
+ end
233
+ end
232
234
 
233
- def notify_subscribers
234
- customer.followers.each do |follower| # N+1!
235
- NotificationMailer.new_order(follower).deliver_later
235
+ # OK - Query not on iteration variable (NOT flagged)
236
+ class Post < ApplicationRecord
237
+ after_save :process_items
238
+
239
+ def process_items
240
+ items.each do |item|
241
+ OtherModel.where(name: item.name).first # OtherModel is receiver, not item
236
242
  end
237
243
  end
238
244
  end
239
245
 
240
- # Good - Use conditional callbacks
241
- after_save :recalculate_stats, if: :should_recalculate?
242
-
243
- # Good - Move to background job
244
- after_commit :schedule_stats_update, on: :create
246
+ # Good - Move iterations to background job
247
+ after_commit :schedule_notifications, on: :create
245
248
 
246
- def schedule_stats_update
247
- RecalculateStatsJob.perform_later(id)
249
+ def schedule_notifications
250
+ NotifySubscribersJob.perform_later(id)
248
251
  end
249
252
  ```
250
253
 
@@ -106,8 +106,9 @@ module EagerEye
106
106
  return unless node.is_a?(Parser::AST::Node)
107
107
 
108
108
  if iteration_block?(node)
109
+ block_var = extract_block_variable(node)
109
110
  add_iteration_issue(node, method_name, callback_type)
110
- find_query_calls_in_block(node, method_name, callback_type)
111
+ find_query_calls_in_block(node, method_name, callback_type, block_var) if block_var
111
112
  end
112
113
 
113
114
  node.children.each do |child|
@@ -115,13 +116,15 @@ module EagerEye
115
116
  end
116
117
  end
117
118
 
118
- def find_query_calls_in_block(node, method_name, callback_type)
119
+ def find_query_calls_in_block(node, method_name, callback_type, block_var)
119
120
  return unless node.is_a?(Parser::AST::Node)
120
121
 
121
- add_query_issue(node, method_name, callback_type) if query_call?(node)
122
+ if query_call?(node) && receiver_chain_starts_with?(node.children[0], block_var)
123
+ add_query_issue(node, method_name, callback_type)
124
+ end
122
125
 
123
126
  node.children.each do |child|
124
- find_query_calls_in_block(child, method_name, callback_type)
127
+ find_query_calls_in_block(child, method_name, callback_type, block_var)
125
128
  end
126
129
  end
127
130
 
@@ -163,6 +166,29 @@ module EagerEye
163
166
  suggestion: "Avoid iterations in callbacks. Use background jobs for bulk operations"
164
167
  )
165
168
  end
169
+
170
+ def extract_block_variable(block_node)
171
+ args_node = block_node.children[1]
172
+ return nil unless args_node&.type == :args
173
+
174
+ first_arg = args_node.children[0]
175
+ return nil unless first_arg&.type == :arg
176
+
177
+ first_arg.children[0]
178
+ end
179
+
180
+ def receiver_chain_starts_with?(node, block_var)
181
+ return false unless node.is_a?(Parser::AST::Node)
182
+
183
+ case node.type
184
+ when :lvar
185
+ node.children[0] == block_var
186
+ when :send
187
+ receiver_chain_starts_with?(node.children[0], block_var)
188
+ else
189
+ false
190
+ end
191
+ end
166
192
  end
167
193
  end
168
194
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EagerEye
4
- VERSION = "1.0.2"
4
+ VERSION = "1.0.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.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - hamzagedikkaya
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-12-19 00:00:00.000000000 Z
11
+ date: 2025-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ast