eager_eye 1.0.5 → 1.0.6
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 +9 -0
- data/README.md +14 -7
- data/lib/eager_eye/detectors/missing_counter_cache.rb +47 -1
- data/lib/eager_eye/version.rb +1 -1
- 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: c0eccf7e9da999d8e273e9b41a11c96eb1284fdd944ce6af0e547f4201a3fbb9
|
|
4
|
+
data.tar.gz: 55b8ab8e753245bc5b638705bff5a0c10115c0f39f44d69f25f5b50dcb823ffa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f41668f38474f848cffe0e70473c689c27eb9a102a20cdc932f1039a2c4dce71cb91eda4fa2bb1012136d48b93976cb33ad8455a3e01db2a2c400037a75420e
|
|
7
|
+
data.tar.gz: 2d8f591ee31948302c8a14fe2deef868bc98e2d7fd23083c39a6ee8e9242296e25080938f4e00cef24757c2d2ec6df512d7e6ef144945420de593168e0d71e96
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.6] - 2025-12-22
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed false positive in `MissingCounterCache` detector for single `.count`/`.size`/`.length` calls
|
|
15
|
+
- Now only detects count calls **inside iterations** where N+1 queries actually occur
|
|
16
|
+
- Single calls like `post.comments.count` are no longer flagged (not N+1)
|
|
17
|
+
- Iteration patterns like `posts.each { |p| p.comments.count }` are correctly detected
|
|
18
|
+
|
|
10
19
|
## [1.0.5] - 2025-12-21
|
|
11
20
|
|
|
12
21
|
### Fixed
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# EagerEye
|
|
2
2
|
|
|
3
3
|
[](https://github.com/hamzagedikkaya/eager_eye/actions/workflows/main.yml)
|
|
4
|
-
[](https://rubygems.org/gems/eager_eye)
|
|
5
5
|
[](https://github.com/hamzagedikkaya/eager_eye)
|
|
6
6
|
[](https://www.ruby-lang.org/)
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -150,19 +150,26 @@ Supports multiple serializer libraries:
|
|
|
150
150
|
|
|
151
151
|
### 3. Missing Counter Cache
|
|
152
152
|
|
|
153
|
-
Detects `.count
|
|
153
|
+
Detects `.count`, `.size`, or `.length` calls on associations **inside iterations** that could benefit from counter caches. Single calls outside loops are not flagged since they don't cause N+1 issues.
|
|
154
154
|
|
|
155
155
|
```ruby
|
|
156
|
-
# Bad - COUNT query
|
|
157
|
-
post
|
|
158
|
-
post.comments.
|
|
156
|
+
# Bad - COUNT query for each post in iteration
|
|
157
|
+
posts.each do |post|
|
|
158
|
+
post.comments.count # Detected: N+1 query!
|
|
159
|
+
post.likes.size # Detected: N+1 query!
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# OK - Single count call (not in iteration, no N+1)
|
|
163
|
+
post.comments.count # Not flagged - single query is fine
|
|
159
164
|
|
|
160
|
-
# Good - Add counter cache
|
|
165
|
+
# Good - Add counter cache for iteration use cases
|
|
161
166
|
# In Comment model:
|
|
162
167
|
belongs_to :post, counter_cache: true
|
|
163
168
|
|
|
164
169
|
# Then this is a simple column read:
|
|
165
|
-
post
|
|
170
|
+
posts.each do |post|
|
|
171
|
+
post.comments_count # No query - just reads the column
|
|
172
|
+
end
|
|
166
173
|
```
|
|
167
174
|
|
|
168
175
|
### 4. Custom Method Query (N+1 in query methods)
|
|
@@ -15,6 +15,11 @@ module EagerEye
|
|
|
15
15
|
children replies responses answers questions
|
|
16
16
|
].freeze
|
|
17
17
|
|
|
18
|
+
# Iteration methods that indicate a loop context
|
|
19
|
+
ITERATION_METHODS = %i[each map collect select reject find_all
|
|
20
|
+
filter filter_map flat_map each_with_index
|
|
21
|
+
each_with_object reduce inject sum].freeze
|
|
22
|
+
|
|
18
23
|
def self.detector_name
|
|
19
24
|
:missing_counter_cache
|
|
20
25
|
end
|
|
@@ -26,6 +31,7 @@ module EagerEye
|
|
|
26
31
|
|
|
27
32
|
traverse_ast(ast) do |node|
|
|
28
33
|
next unless count_on_association?(node)
|
|
34
|
+
next unless inside_iteration?(node)
|
|
29
35
|
|
|
30
36
|
association_name = extract_association_name(node)
|
|
31
37
|
next unless association_name
|
|
@@ -33,7 +39,7 @@ module EagerEye
|
|
|
33
39
|
issues << create_issue(
|
|
34
40
|
file_path: file_path,
|
|
35
41
|
line_number: node.loc.line,
|
|
36
|
-
message: "`.#{node.children[1]}` called on `#{association_name}` may cause N+1 queries",
|
|
42
|
+
message: "`.#{node.children[1]}` called on `#{association_name}` inside iteration may cause N+1 queries",
|
|
37
43
|
suggestion: "Consider adding `counter_cache: true` to the belongs_to association"
|
|
38
44
|
)
|
|
39
45
|
end
|
|
@@ -68,6 +74,46 @@ module EagerEye
|
|
|
68
74
|
|
|
69
75
|
receiver.children[1].to_s
|
|
70
76
|
end
|
|
77
|
+
|
|
78
|
+
# Check if the node is inside an iteration block
|
|
79
|
+
def inside_iteration?(node)
|
|
80
|
+
parent = node
|
|
81
|
+
while (parent = find_parent(parent))
|
|
82
|
+
return true if iteration_block?(parent)
|
|
83
|
+
end
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def find_parent(node)
|
|
88
|
+
@parent_map ||= {}
|
|
89
|
+
@parent_map[node]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Override traverse_ast to build parent map
|
|
93
|
+
def traverse_ast(node, &block)
|
|
94
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
95
|
+
|
|
96
|
+
@parent_map ||= {}
|
|
97
|
+
|
|
98
|
+
yield node
|
|
99
|
+
|
|
100
|
+
node.children.each do |child|
|
|
101
|
+
if child.is_a?(Parser::AST::Node)
|
|
102
|
+
@parent_map[child] = node
|
|
103
|
+
traverse_ast(child, &block)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def iteration_block?(node)
|
|
109
|
+
return false unless node.type == :block
|
|
110
|
+
|
|
111
|
+
send_node = node.children[0]
|
|
112
|
+
return false unless send_node&.type == :send
|
|
113
|
+
|
|
114
|
+
method_name = send_node.children[1]
|
|
115
|
+
ITERATION_METHODS.include?(method_name)
|
|
116
|
+
end
|
|
71
117
|
end
|
|
72
118
|
end
|
|
73
119
|
end
|
data/lib/eager_eye/version.rb
CHANGED
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.
|
|
4
|
+
version: 1.0.6
|
|
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-
|
|
11
|
+
date: 2025-12-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ast
|