eager_eye 0.3.0 → 0.5.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 +69 -0
- data/lib/eager_eye/analyzer.rb +3 -1
- data/lib/eager_eye/configuration.rb +2 -1
- data/lib/eager_eye/detectors/callback_query.rb +166 -0
- data/lib/eager_eye/detectors/pluck_to_array.rb +149 -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: 031e0f3547538856718932cd6121ac990ad8c11a43682ec076f68bb591943d55
|
|
4
|
+
data.tar.gz: 433563980a743704c361304dff56866fe4cc9795a1d6f927cd0b4a8fc0367c8f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5c9f874001fb0c71eb8c1858ef2739181482992b7455925afc696acfa1b7bebfa91888d305936ab6189f64ea1542afd7844b95c4ba453218d0c706d40d17304b
|
|
7
|
+
data.tar.gz: 93750c566392f234cb5009d30873130182a4e80722b9f29402b662caacc0c961a31a8c109937c378f9a04b3d0f6e134326a0edeb0d138255debc2c9fb4ceb9a3
|
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.5.0] - 2025-12-15
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **New Detector: `PluckToArray`** - Detects pluck/map results used in where clauses
|
|
15
|
+
- Catches `.pluck(:id)` and `.ids` results used in `where` clauses
|
|
16
|
+
- Catches `.map(&:id)` and `.collect(&:id)` patterns
|
|
17
|
+
- Suggests using `.select(:id)` subquery pattern for better performance
|
|
18
|
+
- Prevents two queries and memory overhead from holding IDs in arrays
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Updated default `enabled_detectors` to include `:pluck_to_array`
|
|
23
|
+
- Updated README with new detector documentation and performance comparison
|
|
24
|
+
|
|
25
|
+
## [0.4.0] - 2025-12-15
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **New Detector: `CallbackQuery`** - Detects database queries and iterations inside ActiveRecord callbacks
|
|
30
|
+
- Identifies potential bulk operation performance issues
|
|
31
|
+
- Detects query methods (`.count`, `.sum`, `.update!`, etc.) in callbacks
|
|
32
|
+
- Detects iterations (`.each`, `.map`, etc.) in callbacks as errors
|
|
33
|
+
- Severity: `:error` for iterations in callbacks, `:warning` for query methods
|
|
34
|
+
- Suggests moving to background jobs or using conditional callbacks
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Updated default `enabled_detectors` to include `:callback_query`
|
|
39
|
+
- Updated README with new detector documentation
|
|
40
|
+
|
|
10
41
|
## [0.3.0] - 2025-12-15
|
|
11
42
|
|
|
12
43
|
### Added
|
data/README.md
CHANGED
|
@@ -205,6 +205,73 @@ user.posts_count # Just reads the column
|
|
|
205
205
|
| `.size` | Array#size | COUNT query |
|
|
206
206
|
| `.length` | Array#length | Loads all, then counts |
|
|
207
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
|
+
|
|
249
|
+
### 7. Pluck to Array Misuse
|
|
250
|
+
|
|
251
|
+
Detects when `.pluck(:id)` or `.map(&:id)` results are used in `where` clauses instead of subqueries.
|
|
252
|
+
|
|
253
|
+
```ruby
|
|
254
|
+
# Bad - Two queries + memory overhead
|
|
255
|
+
user_ids = User.active.pluck(:id) # Query 1: SELECT id FROM users
|
|
256
|
+
Post.where(user_id: user_ids) # Query 2: SELECT * FROM posts WHERE user_id IN (1,2,3...)
|
|
257
|
+
# Also holds potentially thousands of IDs in memory
|
|
258
|
+
|
|
259
|
+
# Bad - Same problem with map
|
|
260
|
+
user_ids = users.map(&:id)
|
|
261
|
+
Post.where(user_id: user_ids)
|
|
262
|
+
|
|
263
|
+
# Good - Single subquery, no memory overhead
|
|
264
|
+
Post.where(user_id: User.active.select(:id))
|
|
265
|
+
# Single query: SELECT * FROM posts WHERE user_id IN (SELECT id FROM users WHERE active = true)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Performance comparison with 10,000 users:**
|
|
269
|
+
|
|
270
|
+
| Approach | Queries | Memory | Time |
|
|
271
|
+
|----------|---------|--------|------|
|
|
272
|
+
| `pluck` + `where` | 2 | ~80KB for IDs | ~45ms |
|
|
273
|
+
| `select` subquery | 1 | None | ~20ms |
|
|
274
|
+
|
|
208
275
|
## Configuration
|
|
209
276
|
|
|
210
277
|
### Config File (.eager_eye.yml)
|
|
@@ -222,6 +289,8 @@ enabled_detectors:
|
|
|
222
289
|
- missing_counter_cache
|
|
223
290
|
- custom_method_query
|
|
224
291
|
- count_in_iteration
|
|
292
|
+
- callback_query
|
|
293
|
+
- pluck_to_array
|
|
225
294
|
|
|
226
295
|
# Base path to analyze (default: app)
|
|
227
296
|
app_path: app
|
data/lib/eager_eye/analyzer.rb
CHANGED
|
@@ -9,7 +9,9 @@ module EagerEye
|
|
|
9
9
|
serializer_nesting: Detectors::SerializerNesting,
|
|
10
10
|
missing_counter_cache: Detectors::MissingCounterCache,
|
|
11
11
|
custom_method_query: Detectors::CustomMethodQuery,
|
|
12
|
-
count_in_iteration: Detectors::CountInIteration
|
|
12
|
+
count_in_iteration: Detectors::CountInIteration,
|
|
13
|
+
callback_query: Detectors::CallbackQuery,
|
|
14
|
+
pluck_to_array: Detectors::PluckToArray
|
|
13
15
|
}.freeze
|
|
14
16
|
|
|
15
17
|
attr_reader :paths, :issues
|
|
@@ -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,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EagerEye
|
|
4
|
+
module Detectors
|
|
5
|
+
class PluckToArray < Base
|
|
6
|
+
def self.detector_name
|
|
7
|
+
:pluck_to_array
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def detect(ast, file_path)
|
|
11
|
+
@issues = []
|
|
12
|
+
@file_path = file_path
|
|
13
|
+
@pluck_variables = {}
|
|
14
|
+
@map_id_variables = {}
|
|
15
|
+
|
|
16
|
+
return @issues unless ast
|
|
17
|
+
|
|
18
|
+
collect_pluck_assignments(ast)
|
|
19
|
+
collect_map_id_assignments(ast)
|
|
20
|
+
find_where_with_pluck_var(ast)
|
|
21
|
+
|
|
22
|
+
@issues
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def collect_pluck_assignments(node)
|
|
28
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
29
|
+
|
|
30
|
+
if local_variable_assignment?(node)
|
|
31
|
+
var_name = node.children[0]
|
|
32
|
+
value = node.children[1]
|
|
33
|
+
|
|
34
|
+
@pluck_variables[var_name] = node.loc.line if pluck_call?(value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
node.children.each do |child|
|
|
38
|
+
collect_pluck_assignments(child)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def collect_map_id_assignments(node)
|
|
43
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
44
|
+
|
|
45
|
+
if local_variable_assignment?(node)
|
|
46
|
+
var_name = node.children[0]
|
|
47
|
+
value = node.children[1]
|
|
48
|
+
|
|
49
|
+
@map_id_variables[var_name] = node.loc.line if map_id_call?(value)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
node.children.each do |child|
|
|
53
|
+
collect_map_id_assignments(child)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_where_with_pluck_var(node)
|
|
58
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
59
|
+
|
|
60
|
+
add_issue(node) if where_call_with_pluck_var?(node)
|
|
61
|
+
|
|
62
|
+
node.children.each do |child|
|
|
63
|
+
find_where_with_pluck_var(child)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def local_variable_assignment?(node)
|
|
68
|
+
node.type == :lvasgn
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def pluck_call?(node)
|
|
72
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
73
|
+
return false unless node.type == :send
|
|
74
|
+
|
|
75
|
+
method_name = node.children[1]
|
|
76
|
+
%i[pluck ids].include?(method_name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def map_id_call?(node)
|
|
80
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
81
|
+
|
|
82
|
+
case node.type
|
|
83
|
+
when :block then block_map_call?(node)
|
|
84
|
+
when :send then send_map_id_call?(node)
|
|
85
|
+
else false
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def block_map_call?(node)
|
|
90
|
+
send_node = node.children[0]
|
|
91
|
+
return false unless send_node&.type == :send
|
|
92
|
+
|
|
93
|
+
%i[map collect].include?(send_node.children[1])
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def send_map_id_call?(node)
|
|
97
|
+
method_name = node.children[1]
|
|
98
|
+
return false unless %i[map collect].include?(method_name)
|
|
99
|
+
|
|
100
|
+
node.children[2..].any? { |arg| symbol_to_proc_id?(arg) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def symbol_to_proc_id?(node)
|
|
104
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
105
|
+
return false unless node.type == :block_pass
|
|
106
|
+
|
|
107
|
+
sym_node = node.children[0]
|
|
108
|
+
return false unless sym_node&.type == :sym
|
|
109
|
+
|
|
110
|
+
%i[id to_i].include?(sym_node.children[0])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def where_call_with_pluck_var?(node)
|
|
114
|
+
return false unless node.type == :send
|
|
115
|
+
return false unless node.children[1] == :where
|
|
116
|
+
|
|
117
|
+
args = node.children[2..]
|
|
118
|
+
args.any? { |arg| hash_with_pluck_var?(arg) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def hash_with_pluck_var?(node)
|
|
122
|
+
return false unless node.is_a?(Parser::AST::Node)
|
|
123
|
+
return false unless node.type == :hash
|
|
124
|
+
|
|
125
|
+
node.children.any? do |pair|
|
|
126
|
+
next false unless pair.type == :pair
|
|
127
|
+
|
|
128
|
+
value = pair.children[1]
|
|
129
|
+
if value.type == :lvar
|
|
130
|
+
var_name = value.children[0]
|
|
131
|
+
@pluck_variables.key?(var_name) || @map_id_variables.key?(var_name)
|
|
132
|
+
else
|
|
133
|
+
false
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def add_issue(node)
|
|
139
|
+
@issues << create_issue(
|
|
140
|
+
file_path: @file_path,
|
|
141
|
+
line_number: node.loc.line,
|
|
142
|
+
message: "Using plucked/mapped array in `where` causes two queries and holds IDs in memory",
|
|
143
|
+
severity: :warning,
|
|
144
|
+
suggestion: "Use `.select(:id)` subquery: `Model.where(col: OtherModel.condition.select(:id))`"
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
data/lib/eager_eye/version.rb
CHANGED
data/lib/eager_eye.rb
CHANGED
|
@@ -9,6 +9,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
11
|
require_relative "eager_eye/detectors/count_in_iteration"
|
|
12
|
+
require_relative "eager_eye/detectors/callback_query"
|
|
13
|
+
require_relative "eager_eye/detectors/pluck_to_array"
|
|
12
14
|
require_relative "eager_eye/analyzer"
|
|
13
15
|
require_relative "eager_eye/reporters/base"
|
|
14
16
|
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.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- hamzagedikkaya
|
|
@@ -61,10 +61,12 @@ 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
|
|
64
65
|
- lib/eager_eye/detectors/count_in_iteration.rb
|
|
65
66
|
- lib/eager_eye/detectors/custom_method_query.rb
|
|
66
67
|
- lib/eager_eye/detectors/loop_association.rb
|
|
67
68
|
- lib/eager_eye/detectors/missing_counter_cache.rb
|
|
69
|
+
- lib/eager_eye/detectors/pluck_to_array.rb
|
|
68
70
|
- lib/eager_eye/detectors/serializer_nesting.rb
|
|
69
71
|
- lib/eager_eye/generators/install_generator.rb
|
|
70
72
|
- lib/eager_eye/issue.rb
|