eager_eye 0.2.3 → 0.4.0
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 +31 -0
- data/README.md +72 -0
- data/lib/eager_eye/analyzer.rb +3 -1
- data/lib/eager_eye/configuration.rb +4 -1
- data/lib/eager_eye/detectors/callback_query.rb +166 -0
- data/lib/eager_eye/detectors/count_in_iteration.rb +152 -0
- data/lib/eager_eye/version.rb +1 -1
- data/lib/eager_eye.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a971d7fbafebc02b51a9767987541f67fdd216025ac04aa4bdf4f304cc3abad9
|
|
4
|
+
data.tar.gz: db6eeeb3e1f511856144c70031f63c5216c73b917c00a17a31dbcd44444757a8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b847b72aff2e758f0a32effdf68057c499503f97251802263d5a1109e5eac3afd8841044d5d144a48b351c4c0909789e5528574f911101f3949e8f78d015dd0a
|
|
7
|
+
data.tar.gz: 3938004802489b63234cc75ec7cfc79c580a0cf263c004762a63fb2d4d1a09350f49f158ab9f4e99cbe4a11b9a8772766be61531c1909d958bed5b82141b407c
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2025-12-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **New Detector: `CallbackQuery`** - Detects database queries and iterations inside ActiveRecord callbacks
|
|
15
|
+
- Identifies potential bulk operation performance issues
|
|
16
|
+
- Detects query methods (`.count`, `.sum`, `.update!`, etc.) in callbacks
|
|
17
|
+
- Detects iterations (`.each`, `.map`, etc.) in callbacks as errors
|
|
18
|
+
- Severity: `:error` for iterations in callbacks, `:warning` for query methods
|
|
19
|
+
- Suggests moving to background jobs or using conditional callbacks
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Updated default `enabled_detectors` to include `:callback_query`
|
|
24
|
+
- Updated README with new detector documentation
|
|
25
|
+
|
|
26
|
+
## [0.3.0] - 2025-12-15
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- **New Detector: `CountInIteration`** - Detects `.count` usage inside iterations
|
|
31
|
+
- `.count` always executes a COUNT query, even on preloaded associations
|
|
32
|
+
- Suggests using `.size` instead (uses loaded collection when available)
|
|
33
|
+
- Suggests `counter_cache: true` for frequently accessed counts
|
|
34
|
+
- Helps prevent unnecessary COUNT queries when associations are already loaded
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Updated default `enabled_detectors` to include `:count_in_iteration`
|
|
39
|
+
- Updated README with new detector documentation and comparison table
|
|
40
|
+
|
|
10
41
|
## [0.2.0] - 2025-12-15
|
|
11
42
|
|
|
12
43
|
### Added
|
data/README.md
CHANGED
|
@@ -176,6 +176,76 @@ end
|
|
|
176
176
|
|
|
177
177
|
**Detected methods:** `where`, `find_by`, `find_by!`, `exists?`, `find`, `first`, `last`, `take`, `pluck`, `ids`, `count`, `sum`, `average`, `minimum`, `maximum`
|
|
178
178
|
|
|
179
|
+
### 5. Count in Iteration
|
|
180
|
+
|
|
181
|
+
Detects `.count` called on associations inside loops. Unlike `.size`, `.count` always executes a COUNT query even when the association is preloaded.
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Bad - COUNT query for each user, even with includes!
|
|
185
|
+
@users = User.includes(:posts)
|
|
186
|
+
@users.each do |user|
|
|
187
|
+
user.posts.count # Executes: SELECT COUNT(*) FROM posts WHERE user_id = ?
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Good - Use .size (checks if loaded first)
|
|
191
|
+
@users.each do |user|
|
|
192
|
+
user.posts.size # No query - counts the loaded array
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Best - Use counter_cache for frequent counts
|
|
196
|
+
# In Post model: belongs_to :user, counter_cache: true
|
|
197
|
+
user.posts_count # Just reads the column
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Key differences:**
|
|
201
|
+
|
|
202
|
+
| Method | Loaded Collection | Not Loaded |
|
|
203
|
+
|--------|------------------|------------|
|
|
204
|
+
| `.count` | COUNT query | COUNT query |
|
|
205
|
+
| `.size` | Array#size | COUNT query |
|
|
206
|
+
| `.length` | Array#length | Loads all, then counts |
|
|
207
|
+
|
|
208
|
+
### 6. Callback Query Detection
|
|
209
|
+
|
|
210
|
+
Detects database queries and iterations inside ActiveRecord callbacks. These become performance disasters during bulk operations.
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
# Bad - Queries in callbacks
|
|
214
|
+
class Article < ApplicationRecord
|
|
215
|
+
after_save :recalculate_stats
|
|
216
|
+
|
|
217
|
+
def recalculate_stats
|
|
218
|
+
author.articles.published.count # Runs on EVERY save
|
|
219
|
+
category.update_article_count! # Another query on EVERY save
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Disaster scenario
|
|
224
|
+
Article.import(1000.times.map { |i| { title: "Post #{i}" } })
|
|
225
|
+
# = 2000+ queries from callbacks!
|
|
226
|
+
|
|
227
|
+
# Bad - N+1 in callback
|
|
228
|
+
class Order < ApplicationRecord
|
|
229
|
+
after_create :notify_subscribers
|
|
230
|
+
|
|
231
|
+
def notify_subscribers
|
|
232
|
+
customer.followers.each do |follower| # N+1!
|
|
233
|
+
NotificationMailer.new_order(follower).deliver_later
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# Good - Use conditional callbacks
|
|
239
|
+
after_save :recalculate_stats, if: :should_recalculate?
|
|
240
|
+
|
|
241
|
+
# Good - Move to background job
|
|
242
|
+
after_commit :schedule_stats_update, on: :create
|
|
243
|
+
|
|
244
|
+
def schedule_stats_update
|
|
245
|
+
RecalculateStatsJob.perform_later(id)
|
|
246
|
+
end
|
|
247
|
+
```
|
|
248
|
+
|
|
179
249
|
## Configuration
|
|
180
250
|
|
|
181
251
|
### Config File (.eager_eye.yml)
|
|
@@ -192,6 +262,8 @@ enabled_detectors:
|
|
|
192
262
|
- serializer_nesting
|
|
193
263
|
- missing_counter_cache
|
|
194
264
|
- custom_method_query
|
|
265
|
+
- count_in_iteration
|
|
266
|
+
- callback_query
|
|
195
267
|
|
|
196
268
|
# Base path to analyze (default: app)
|
|
197
269
|
app_path: app
|
data/lib/eager_eye/analyzer.rb
CHANGED
|
@@ -8,7 +8,9 @@ module EagerEye
|
|
|
8
8
|
loop_association: Detectors::LoopAssociation,
|
|
9
9
|
serializer_nesting: Detectors::SerializerNesting,
|
|
10
10
|
missing_counter_cache: Detectors::MissingCounterCache,
|
|
11
|
-
custom_method_query: Detectors::CustomMethodQuery
|
|
11
|
+
custom_method_query: Detectors::CustomMethodQuery,
|
|
12
|
+
count_in_iteration: Detectors::CountInIteration,
|
|
13
|
+
callback_query: Detectors::CallbackQuery
|
|
12
14
|
}.freeze
|
|
13
15
|
|
|
14
16
|
attr_reader :paths, :issues
|
|
@@ -4,7 +4,10 @@ module EagerEye
|
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :excluded_paths, :enabled_detectors, :app_path, :fail_on_issues
|
|
6
6
|
|
|
7
|
-
DEFAULT_DETECTORS = %i[
|
|
7
|
+
DEFAULT_DETECTORS = %i[
|
|
8
|
+
loop_association serializer_nesting missing_counter_cache
|
|
9
|
+
custom_method_query count_in_iteration callback_query
|
|
10
|
+
].freeze
|
|
8
11
|
|
|
9
12
|
def initialize
|
|
10
13
|
@excluded_paths = []
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EagerEye
|
|
4
|
+
module Detectors
|
|
5
|
+
class CallbackQuery < Base
|
|
6
|
+
CALLBACK_METHODS = %i[
|
|
7
|
+
before_validation after_validation
|
|
8
|
+
before_save after_save around_save
|
|
9
|
+
before_create after_create around_create
|
|
10
|
+
before_update after_update around_update
|
|
11
|
+
before_destroy after_destroy around_destroy
|
|
12
|
+
after_commit after_rollback
|
|
13
|
+
after_create_commit after_update_commit after_destroy_commit
|
|
14
|
+
after_save_commit
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
QUERY_INDICATORS = %i[
|
|
18
|
+
where find find_by find_by! first last take
|
|
19
|
+
exists? any? none? many? one?
|
|
20
|
+
count sum average minimum maximum
|
|
21
|
+
pluck ids select
|
|
22
|
+
update update_all update! update_attribute update_column update_columns
|
|
23
|
+
destroy destroy_all destroy! delete delete_all
|
|
24
|
+
create create! save save! insert insert_all insert! upsert upsert_all
|
|
25
|
+
increment! decrement! toggle!
|
|
26
|
+
reload
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
ITERATION_METHODS = %i[each map select find_all reject collect].freeze
|
|
30
|
+
|
|
31
|
+
def self.detector_name
|
|
32
|
+
:callback_query
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def detect(ast, file_path)
|
|
36
|
+
@issues = []
|
|
37
|
+
@file_path = file_path
|
|
38
|
+
@callback_methods = {}
|
|
39
|
+
|
|
40
|
+
return @issues unless ast
|
|
41
|
+
|
|
42
|
+
find_callback_definitions(ast)
|
|
43
|
+
check_callback_methods(ast)
|
|
44
|
+
|
|
45
|
+
@issues
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def find_callback_definitions(node)
|
|
51
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
52
|
+
|
|
53
|
+
extract_callback_method_name(node) if callback_definition?(node)
|
|
54
|
+
|
|
55
|
+
node.children.each do |child|
|
|
56
|
+
find_callback_definitions(child)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def callback_definition?(node)
|
|
61
|
+
return false unless node.type == :send
|
|
62
|
+
return false unless node.children[0].nil?
|
|
63
|
+
|
|
64
|
+
method_name = node.children[1]
|
|
65
|
+
CALLBACK_METHODS.include?(method_name)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def extract_callback_method_name(node)
|
|
69
|
+
node.children[2..].each do |arg|
|
|
70
|
+
next unless arg.is_a?(Parser::AST::Node) && arg.type == :sym
|
|
71
|
+
|
|
72
|
+
method_name = arg.children[0]
|
|
73
|
+
callback_type = node.children[1]
|
|
74
|
+
@callback_methods[method_name] = callback_type
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check_callback_methods(node)
|
|
79
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
80
|
+
|
|
81
|
+
if method_definition?(node)
|
|
82
|
+
method_name = node.children[0]
|
|
83
|
+
if @callback_methods.key?(method_name)
|
|
84
|
+
callback_type = @callback_methods[method_name]
|
|
85
|
+
check_method_body_for_queries(node, method_name, callback_type)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
node.children.each do |child|
|
|
90
|
+
check_callback_methods(child)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def method_definition?(node)
|
|
95
|
+
node.type == :def
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def check_method_body_for_queries(method_node, method_name, callback_type)
|
|
99
|
+
method_body = method_node.children[2]
|
|
100
|
+
return unless method_body
|
|
101
|
+
|
|
102
|
+
find_query_calls(method_body, method_name, callback_type)
|
|
103
|
+
find_iteration_with_queries(method_body, method_name, callback_type)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def find_query_calls(node, method_name, callback_type)
|
|
107
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
108
|
+
|
|
109
|
+
add_query_issue(node, method_name, callback_type) if query_call?(node)
|
|
110
|
+
|
|
111
|
+
node.children.each do |child|
|
|
112
|
+
find_query_calls(child, method_name, callback_type)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def find_iteration_with_queries(node, method_name, callback_type)
|
|
117
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
118
|
+
|
|
119
|
+
add_iteration_issue(node, method_name, callback_type) if iteration_block?(node)
|
|
120
|
+
|
|
121
|
+
node.children.each do |child|
|
|
122
|
+
find_iteration_with_queries(child, method_name, callback_type)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def query_call?(node)
|
|
127
|
+
return false unless node.type == :send
|
|
128
|
+
|
|
129
|
+
method = node.children[1]
|
|
130
|
+
QUERY_INDICATORS.include?(method)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def iteration_block?(node)
|
|
134
|
+
return false unless node.type == :block
|
|
135
|
+
|
|
136
|
+
send_node = node.children[0]
|
|
137
|
+
return false unless send_node&.type == :send
|
|
138
|
+
|
|
139
|
+
method_name = send_node.children[1]
|
|
140
|
+
ITERATION_METHODS.include?(method_name)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def add_query_issue(node, method_name, callback_type)
|
|
144
|
+
query_method = node.children[1]
|
|
145
|
+
|
|
146
|
+
@issues << create_issue(
|
|
147
|
+
file_path: @file_path,
|
|
148
|
+
line_number: node.loc.line,
|
|
149
|
+
message: "Query method `.#{query_method}` found in `#{callback_type}` callback `:#{method_name}`",
|
|
150
|
+
severity: :warning,
|
|
151
|
+
suggestion: "Callbacks run on every save/create/update. Consider moving to a background job"
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def add_iteration_issue(node, method_name, callback_type)
|
|
156
|
+
@issues << create_issue(
|
|
157
|
+
file_path: @file_path,
|
|
158
|
+
line_number: node.loc.line,
|
|
159
|
+
message: "Iteration found in `#{callback_type}` callback `:#{method_name}` - potential N+1",
|
|
160
|
+
severity: :error,
|
|
161
|
+
suggestion: "Avoid iterations in callbacks. Use background jobs for bulk operations"
|
|
162
|
+
)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EagerEye
|
|
4
|
+
module Detectors
|
|
5
|
+
class CountInIteration < Base
|
|
6
|
+
# count always executes a COUNT query
|
|
7
|
+
# size and length use memory when collection is loaded
|
|
8
|
+
COUNT_METHODS = %i[count].freeze
|
|
9
|
+
|
|
10
|
+
ITERATION_METHODS = %i[
|
|
11
|
+
each map select find_all reject collect
|
|
12
|
+
each_with_index each_with_object flat_map
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
def self.detector_name
|
|
16
|
+
:count_in_iteration
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def detect(ast, file_path)
|
|
20
|
+
@issues = []
|
|
21
|
+
@file_path = file_path
|
|
22
|
+
|
|
23
|
+
return @issues unless ast
|
|
24
|
+
|
|
25
|
+
find_iteration_blocks(ast) do |block_body, block_var|
|
|
26
|
+
check_for_count_calls(block_body, block_var)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@issues
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def find_iteration_blocks(node, &block)
|
|
35
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
36
|
+
|
|
37
|
+
if iteration_block?(node)
|
|
38
|
+
block_var = extract_block_variable(node)
|
|
39
|
+
block_body = extract_block_body(node)
|
|
40
|
+
yield(block_body, block_var) if block_var && block_body
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
node.children.each do |child|
|
|
44
|
+
find_iteration_blocks(child, &block)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def iteration_block?(node)
|
|
49
|
+
return false unless node.type == :block
|
|
50
|
+
|
|
51
|
+
send_node = node.children[0]
|
|
52
|
+
return false unless send_node&.type == :send
|
|
53
|
+
|
|
54
|
+
method_name = send_node.children[1]
|
|
55
|
+
ITERATION_METHODS.include?(method_name)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def check_for_count_calls(node, block_var)
|
|
59
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
60
|
+
|
|
61
|
+
add_issue(node) if count_on_association?(node, block_var)
|
|
62
|
+
|
|
63
|
+
node.children.each do |child|
|
|
64
|
+
check_for_count_calls(child, block_var)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def count_on_association?(node, block_var)
|
|
69
|
+
return false unless node.type == :send
|
|
70
|
+
|
|
71
|
+
method_name = node.children[1]
|
|
72
|
+
return false unless COUNT_METHODS.include?(method_name)
|
|
73
|
+
|
|
74
|
+
receiver = node.children[0]
|
|
75
|
+
association_call_on_block_var?(receiver, block_var)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def association_call_on_block_var?(node, block_var)
|
|
79
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
80
|
+
return false unless node.type == :send
|
|
81
|
+
|
|
82
|
+
receiver = node.children[0]
|
|
83
|
+
return false unless receiver.is_a?(Parser::AST::Node)
|
|
84
|
+
|
|
85
|
+
# post.comments.count -> receiver is post.comments
|
|
86
|
+
# post.comments -> receiver is post (lvar)
|
|
87
|
+
if receiver.type == :lvar && receiver.children[0] == block_var
|
|
88
|
+
true
|
|
89
|
+
elsif receiver.type == :send
|
|
90
|
+
# Nested: post.author.posts.count
|
|
91
|
+
chain_starts_with_block_var?(receiver, block_var)
|
|
92
|
+
else
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def chain_starts_with_block_var?(node, block_var)
|
|
98
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
99
|
+
|
|
100
|
+
case node.type
|
|
101
|
+
when :lvar
|
|
102
|
+
node.children[0] == block_var
|
|
103
|
+
when :send
|
|
104
|
+
chain_starts_with_block_var?(node.children[0], block_var)
|
|
105
|
+
else
|
|
106
|
+
false
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def extract_block_variable(block_node)
|
|
111
|
+
args_node = block_node.children[1]
|
|
112
|
+
return nil unless args_node&.type == :args
|
|
113
|
+
|
|
114
|
+
first_arg = args_node.children[0]
|
|
115
|
+
return nil unless first_arg&.type == :arg
|
|
116
|
+
|
|
117
|
+
first_arg.children[0]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def extract_block_body(block_node)
|
|
121
|
+
block_node.children[2]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def add_issue(node)
|
|
125
|
+
receiver_chain = reconstruct_chain(node.children[0])
|
|
126
|
+
|
|
127
|
+
@issues << create_issue(
|
|
128
|
+
file_path: @file_path,
|
|
129
|
+
line_number: node.loc.line,
|
|
130
|
+
message: "`.count` called on `#{receiver_chain}` inside iteration always executes a COUNT query",
|
|
131
|
+
severity: :warning,
|
|
132
|
+
suggestion: "Use `.size` instead (uses loaded collection) or add `counter_cache: true`"
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def reconstruct_chain(node)
|
|
137
|
+
return "" unless node.is_a?(Parser::AST::Node)
|
|
138
|
+
|
|
139
|
+
case node.type
|
|
140
|
+
when :lvar
|
|
141
|
+
node.children[0].to_s
|
|
142
|
+
when :send
|
|
143
|
+
receiver_str = reconstruct_chain(node.children[0])
|
|
144
|
+
method = node.children[1]
|
|
145
|
+
receiver_str.empty? ? method.to_s : "#{receiver_str}.#{method}"
|
|
146
|
+
else
|
|
147
|
+
""
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
data/lib/eager_eye/version.rb
CHANGED
data/lib/eager_eye.rb
CHANGED
|
@@ -8,6 +8,8 @@ require_relative "eager_eye/detectors/loop_association"
|
|
|
8
8
|
require_relative "eager_eye/detectors/serializer_nesting"
|
|
9
9
|
require_relative "eager_eye/detectors/missing_counter_cache"
|
|
10
10
|
require_relative "eager_eye/detectors/custom_method_query"
|
|
11
|
+
require_relative "eager_eye/detectors/count_in_iteration"
|
|
12
|
+
require_relative "eager_eye/detectors/callback_query"
|
|
11
13
|
require_relative "eager_eye/analyzer"
|
|
12
14
|
require_relative "eager_eye/reporters/base"
|
|
13
15
|
require_relative "eager_eye/reporters/console"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eager_eye
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamzagedikkaya
|
|
@@ -61,6 +61,8 @@ files:
|
|
|
61
61
|
- lib/eager_eye/cli.rb
|
|
62
62
|
- lib/eager_eye/configuration.rb
|
|
63
63
|
- lib/eager_eye/detectors/base.rb
|
|
64
|
+
- lib/eager_eye/detectors/callback_query.rb
|
|
65
|
+
- lib/eager_eye/detectors/count_in_iteration.rb
|
|
64
66
|
- lib/eager_eye/detectors/custom_method_query.rb
|
|
65
67
|
- lib/eager_eye/detectors/loop_association.rb
|
|
66
68
|
- lib/eager_eye/detectors/missing_counter_cache.rb
|