eager_eye 1.0.3 → 1.0.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 +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +12 -2
- data/lib/eager_eye/detectors/loop_association.rb +61 -9
- 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: f7ebed3daa9046b0b6bbda1cbb7a9cce182af014ac31334973b4d98a5a1059bc
|
|
4
|
+
data.tar.gz: 61e220f6737089f52ab495d422cac8b91bf4720d6551c6a42abad7801c7d3c79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 198f7d4c064f940a73e2ac41f401869274482f1388f9031f405c89cdeddd3e8e1cf829f8d107c7c3fa1a88fa1a5de07fe44a3d08acf4cb269aa063ebfafae729
|
|
7
|
+
data.tar.gz: 814ce257bfc024ca1d83b61cddd3df9b56ed877dfb40d9eaf5564c951bebf11f4351d0c4965ff1510509f62aad3f97af8f0e4933b5a23b4daf048b186d5c748b
|
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.4] - 2025-12-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Fixed false positive N+1 warnings when associations are preloaded via `includes`, `preload`, or `eager_load` on a separate line
|
|
15
|
+
- Now correctly tracks variable assignments with preload methods (e.g., `posts = Post.includes(:author)`)
|
|
16
|
+
- Supports both local variables and instance variables
|
|
17
|
+
- Works with all three preload methods: `includes`, `preload`, `eager_load`
|
|
18
|
+
|
|
10
19
|
## [1.0.3] - 2025-12-20
|
|
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)
|
|
@@ -99,11 +99,21 @@ posts.each do |post|
|
|
|
99
99
|
post.comments.count # Another query for each post!
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
# Good - Eager load associations
|
|
102
|
+
# Good - Eager load associations (chained)
|
|
103
103
|
posts.includes(:author, :comments).each do |post|
|
|
104
104
|
post.author.name # No additional query
|
|
105
105
|
post.comments.count # No additional query
|
|
106
106
|
end
|
|
107
|
+
|
|
108
|
+
# Good - Eager load on separate line (also detected correctly!)
|
|
109
|
+
@posts = Post.includes(:author)
|
|
110
|
+
@posts.each do |post|
|
|
111
|
+
post.author.name # No warning - EagerEye tracks the preload
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Also works with preload and eager_load
|
|
115
|
+
posts = Post.preload(:comments)
|
|
116
|
+
posts.each { |post| post.comments.size } # No warning
|
|
107
117
|
```
|
|
108
118
|
|
|
109
119
|
### 2. Serializer Nesting (N+1 in serializers)
|
|
@@ -4,6 +4,7 @@ module EagerEye
|
|
|
4
4
|
module Detectors
|
|
5
5
|
class LoopAssociation < Base
|
|
6
6
|
ITERATION_METHODS = %i[each map collect select find find_all reject filter filter_map flat_map].freeze
|
|
7
|
+
PRELOAD_METHODS = %i[includes preload eager_load].freeze
|
|
7
8
|
|
|
8
9
|
# Common singular association names (belongs_to pattern)
|
|
9
10
|
SINGULAR_ASSOCIATIONS = %w[
|
|
@@ -23,12 +24,10 @@ module EagerEye
|
|
|
23
24
|
|
|
24
25
|
# Methods that should NOT be treated as associations
|
|
25
26
|
EXCLUDED_METHODS = %i[
|
|
26
|
-
id to_s to_h to_a to_json to_xml inspect class object_id
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
name title body content text description value key type status state
|
|
31
|
-
created_at updated_at deleted_at
|
|
27
|
+
id to_s to_h to_a to_json to_xml inspect class object_id nil? blank? present? empty?
|
|
28
|
+
any? none? size count length save save! update update! destroy destroy! delete delete!
|
|
29
|
+
valid? invalid? errors new? persisted? changed? frozen? name title body content text
|
|
30
|
+
description value key type status state created_at updated_at deleted_at
|
|
32
31
|
].freeze
|
|
33
32
|
|
|
34
33
|
def self.detector_name
|
|
@@ -40,6 +39,9 @@ module EagerEye
|
|
|
40
39
|
|
|
41
40
|
issues = []
|
|
42
41
|
|
|
42
|
+
# Build a map of variable names to their preloaded associations
|
|
43
|
+
@variable_preloads = build_variable_preloads_map(ast)
|
|
44
|
+
|
|
43
45
|
traverse_ast(ast) do |node|
|
|
44
46
|
next unless iteration_block?(node)
|
|
45
47
|
|
|
@@ -49,10 +51,14 @@ module EagerEye
|
|
|
49
51
|
block_body = node.children[2]
|
|
50
52
|
next unless block_body
|
|
51
53
|
|
|
52
|
-
# Check if the collection already has includes
|
|
54
|
+
# Check if the collection already has includes (both chained and from variable assignment)
|
|
53
55
|
collection_node = node.children[0]
|
|
54
56
|
included_associations = extract_included_associations(collection_node)
|
|
55
57
|
|
|
58
|
+
# Also check if the collection comes from a variable that was assigned with preloads
|
|
59
|
+
variable_preloads = extract_variable_preloads(collection_node)
|
|
60
|
+
included_associations.merge(variable_preloads)
|
|
61
|
+
|
|
56
62
|
find_association_calls(block_body, block_var, file_path, issues, included_associations)
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -86,11 +92,11 @@ module EagerEye
|
|
|
86
92
|
included = Set.new
|
|
87
93
|
return included unless collection_node&.type == :send
|
|
88
94
|
|
|
89
|
-
# Traverse through chained method calls to find includes
|
|
95
|
+
# Traverse through chained method calls to find includes/preload/eager_load
|
|
90
96
|
current = collection_node
|
|
91
97
|
while current&.type == :send
|
|
92
98
|
method_name = current.children[1]
|
|
93
|
-
extract_includes_from_method(current, included) if method_name
|
|
99
|
+
extract_includes_from_method(current, included) if PRELOAD_METHODS.include?(method_name)
|
|
94
100
|
|
|
95
101
|
current = current.children[0]
|
|
96
102
|
end
|
|
@@ -98,6 +104,52 @@ module EagerEye
|
|
|
98
104
|
included
|
|
99
105
|
end
|
|
100
106
|
|
|
107
|
+
def build_variable_preloads_map(ast)
|
|
108
|
+
preloads_map = {}
|
|
109
|
+
|
|
110
|
+
traverse_ast(ast) do |node|
|
|
111
|
+
case node.type
|
|
112
|
+
when :lvasgn
|
|
113
|
+
record_variable_preloads(preloads_map, :lvar, node)
|
|
114
|
+
when :ivasgn
|
|
115
|
+
record_variable_preloads(preloads_map, :ivar, node)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
preloads_map
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def record_variable_preloads(preloads_map, var_type, node)
|
|
123
|
+
var_name = node.children[0]
|
|
124
|
+
value_node = node.children[1]
|
|
125
|
+
return unless value_node
|
|
126
|
+
|
|
127
|
+
preloaded = extract_included_associations(value_node)
|
|
128
|
+
preloads_map[[var_type, var_name]] = preloaded unless preloaded.empty?
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def extract_variable_preloads(collection_node)
|
|
132
|
+
preloads = Set.new
|
|
133
|
+
return preloads unless @variable_preloads
|
|
134
|
+
|
|
135
|
+
key = variable_key_for_node(collection_node)
|
|
136
|
+
merge_preloads_for_key(preloads, key) if key
|
|
137
|
+
|
|
138
|
+
preloads
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def variable_key_for_node(node)
|
|
142
|
+
case node&.type
|
|
143
|
+
when :lvar then [:lvar, node.children[0]]
|
|
144
|
+
when :ivar then [:ivar, node.children[0]]
|
|
145
|
+
when :send then variable_key_for_node(node.children[0])
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def merge_preloads_for_key(preloads, key)
|
|
150
|
+
preloads.merge(@variable_preloads[key]) if @variable_preloads[key]
|
|
151
|
+
end
|
|
152
|
+
|
|
101
153
|
def extract_includes_from_method(method_node, included_set)
|
|
102
154
|
args = method_node.children[2..]
|
|
103
155
|
args&.each do |arg|
|
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.4
|
|
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-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ast
|