ariadna 1.3.0 → 2.0.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.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/ariadna.gemspec +0 -1
  3. data/data/agents/ariadna-codebase-mapper.md +34 -722
  4. data/data/agents/ariadna-debugger.md +44 -1139
  5. data/data/agents/ariadna-executor.md +75 -396
  6. data/data/agents/ariadna-planner.md +78 -1215
  7. data/data/agents/ariadna-roadmapper.md +55 -582
  8. data/data/agents/ariadna-verifier.md +60 -702
  9. data/data/ariadna/templates/config.json +8 -33
  10. data/data/ariadna/workflows/debug.md +28 -0
  11. data/data/ariadna/workflows/execute-phase.md +31 -513
  12. data/data/ariadna/workflows/map-codebase.md +20 -319
  13. data/data/ariadna/workflows/new-milestone.md +20 -365
  14. data/data/ariadna/workflows/new-project.md +19 -880
  15. data/data/ariadna/workflows/plan-phase.md +24 -443
  16. data/data/ariadna/workflows/progress.md +20 -376
  17. data/data/ariadna/workflows/quick.md +19 -221
  18. data/data/ariadna/workflows/roadmap-ops.md +28 -0
  19. data/data/ariadna/workflows/verify-work.md +23 -560
  20. data/data/commands/ariadna/add-phase.md +11 -22
  21. data/data/commands/ariadna/debug.md +11 -143
  22. data/data/commands/ariadna/execute-phase.md +12 -30
  23. data/data/commands/ariadna/insert-phase.md +7 -14
  24. data/data/commands/ariadna/map-codebase.md +16 -49
  25. data/data/commands/ariadna/new-milestone.md +12 -25
  26. data/data/commands/ariadna/new-project.md +22 -26
  27. data/data/commands/ariadna/plan-phase.md +13 -22
  28. data/data/commands/ariadna/progress.md +16 -6
  29. data/data/commands/ariadna/quick.md +9 -11
  30. data/data/commands/ariadna/remove-phase.md +9 -12
  31. data/data/commands/ariadna/verify-work.md +14 -19
  32. data/data/skills/rails-backend/API.md +138 -0
  33. data/data/skills/rails-backend/CONTROLLERS.md +154 -0
  34. data/data/skills/rails-backend/JOBS.md +132 -0
  35. data/data/skills/rails-backend/MODELS.md +213 -0
  36. data/data/skills/rails-backend/SKILL.md +169 -0
  37. data/data/skills/rails-frontend/ASSETS.md +154 -0
  38. data/data/skills/rails-frontend/COMPONENTS.md +253 -0
  39. data/data/skills/rails-frontend/SKILL.md +187 -0
  40. data/data/skills/rails-frontend/VIEWS.md +168 -0
  41. data/data/skills/rails-performance/PROFILING.md +106 -0
  42. data/data/skills/rails-performance/SKILL.md +217 -0
  43. data/data/skills/rails-security/AUDIT.md +118 -0
  44. data/data/skills/rails-security/SKILL.md +422 -0
  45. data/data/skills/rails-testing/FIXTURES.md +78 -0
  46. data/data/skills/rails-testing/SKILL.md +160 -0
  47. data/data/skills/rails-testing/SYSTEM-TESTS.md +73 -0
  48. data/lib/ariadna/installer.rb +11 -15
  49. data/lib/ariadna/tools/cli.rb +0 -12
  50. data/lib/ariadna/tools/config_manager.rb +10 -72
  51. data/lib/ariadna/tools/frontmatter.rb +23 -1
  52. data/lib/ariadna/tools/init.rb +201 -401
  53. data/lib/ariadna/tools/model_profiles.rb +6 -14
  54. data/lib/ariadna/tools/phase_manager.rb +1 -10
  55. data/lib/ariadna/tools/state_manager.rb +170 -451
  56. data/lib/ariadna/tools/template_filler.rb +4 -12
  57. data/lib/ariadna/tools/verification.rb +21 -399
  58. data/lib/ariadna/uninstaller.rb +9 -0
  59. data/lib/ariadna/version.rb +1 -1
  60. data/lib/ariadna.rb +1 -0
  61. metadata +20 -91
  62. data/data/agents/ariadna-backend-executor.md +0 -261
  63. data/data/agents/ariadna-frontend-executor.md +0 -259
  64. data/data/agents/ariadna-integration-checker.md +0 -418
  65. data/data/agents/ariadna-phase-researcher.md +0 -469
  66. data/data/agents/ariadna-plan-checker.md +0 -622
  67. data/data/agents/ariadna-project-researcher.md +0 -618
  68. data/data/agents/ariadna-research-synthesizer.md +0 -236
  69. data/data/agents/ariadna-test-executor.md +0 -266
  70. data/data/ariadna/references/checkpoints.md +0 -772
  71. data/data/ariadna/references/continuation-format.md +0 -249
  72. data/data/ariadna/references/decimal-phase-calculation.md +0 -65
  73. data/data/ariadna/references/git-integration.md +0 -248
  74. data/data/ariadna/references/git-planning-commit.md +0 -38
  75. data/data/ariadna/references/model-profile-resolution.md +0 -32
  76. data/data/ariadna/references/model-profiles.md +0 -73
  77. data/data/ariadna/references/phase-argument-parsing.md +0 -61
  78. data/data/ariadna/references/planning-config.md +0 -194
  79. data/data/ariadna/references/questioning.md +0 -153
  80. data/data/ariadna/references/rails-conventions.md +0 -416
  81. data/data/ariadna/references/tdd.md +0 -267
  82. data/data/ariadna/references/ui-brand.md +0 -160
  83. data/data/ariadna/references/verification-patterns.md +0 -853
  84. data/data/ariadna/templates/codebase/architecture.md +0 -481
  85. data/data/ariadna/templates/codebase/concerns.md +0 -380
  86. data/data/ariadna/templates/codebase/conventions.md +0 -434
  87. data/data/ariadna/templates/codebase/integrations.md +0 -328
  88. data/data/ariadna/templates/codebase/stack.md +0 -189
  89. data/data/ariadna/templates/codebase/structure.md +0 -418
  90. data/data/ariadna/templates/codebase/testing.md +0 -606
  91. data/data/ariadna/templates/context.md +0 -283
  92. data/data/ariadna/templates/continue-here.md +0 -78
  93. data/data/ariadna/templates/debug-subagent-prompt.md +0 -91
  94. data/data/ariadna/templates/phase-prompt.md +0 -609
  95. data/data/ariadna/templates/planner-subagent-prompt.md +0 -117
  96. data/data/ariadna/templates/research-project/ARCHITECTURE.md +0 -439
  97. data/data/ariadna/templates/research-project/FEATURES.md +0 -168
  98. data/data/ariadna/templates/research-project/PITFALLS.md +0 -406
  99. data/data/ariadna/templates/research-project/STACK.md +0 -251
  100. data/data/ariadna/templates/research-project/SUMMARY.md +0 -247
  101. data/data/ariadna/templates/state.md +0 -176
  102. data/data/ariadna/templates/summary-complex.md +0 -59
  103. data/data/ariadna/templates/summary-minimal.md +0 -41
  104. data/data/ariadna/templates/summary-standard.md +0 -48
  105. data/data/ariadna/templates/user-setup.md +0 -310
  106. data/data/ariadna/workflows/add-phase.md +0 -111
  107. data/data/ariadna/workflows/add-todo.md +0 -157
  108. data/data/ariadna/workflows/audit-milestone.md +0 -241
  109. data/data/ariadna/workflows/check-todos.md +0 -176
  110. data/data/ariadna/workflows/complete-milestone.md +0 -644
  111. data/data/ariadna/workflows/diagnose-issues.md +0 -219
  112. data/data/ariadna/workflows/discovery-phase.md +0 -289
  113. data/data/ariadna/workflows/discuss-phase.md +0 -408
  114. data/data/ariadna/workflows/execute-plan.md +0 -448
  115. data/data/ariadna/workflows/help.md +0 -470
  116. data/data/ariadna/workflows/insert-phase.md +0 -129
  117. data/data/ariadna/workflows/list-phase-assumptions.md +0 -178
  118. data/data/ariadna/workflows/pause-work.md +0 -122
  119. data/data/ariadna/workflows/plan-milestone-gaps.md +0 -256
  120. data/data/ariadna/workflows/remove-phase.md +0 -154
  121. data/data/ariadna/workflows/research-phase.md +0 -74
  122. data/data/ariadna/workflows/resume-project.md +0 -306
  123. data/data/ariadna/workflows/set-profile.md +0 -80
  124. data/data/ariadna/workflows/settings.md +0 -145
  125. data/data/ariadna/workflows/transition.md +0 -493
  126. data/data/ariadna/workflows/update.md +0 -212
  127. data/data/ariadna/workflows/verify-phase.md +0 -226
  128. data/data/commands/ariadna/add-todo.md +0 -42
  129. data/data/commands/ariadna/audit-milestone.md +0 -42
  130. data/data/commands/ariadna/check-todos.md +0 -41
  131. data/data/commands/ariadna/complete-milestone.md +0 -136
  132. data/data/commands/ariadna/discuss-phase.md +0 -86
  133. data/data/commands/ariadna/help.md +0 -22
  134. data/data/commands/ariadna/list-phase-assumptions.md +0 -50
  135. data/data/commands/ariadna/pause-work.md +0 -35
  136. data/data/commands/ariadna/plan-milestone-gaps.md +0 -40
  137. data/data/commands/ariadna/reapply-patches.md +0 -110
  138. data/data/commands/ariadna/research-phase.md +0 -187
  139. data/data/commands/ariadna/resume-work.md +0 -40
  140. data/data/commands/ariadna/set-profile.md +0 -34
  141. data/data/commands/ariadna/settings.md +0 -36
  142. data/data/commands/ariadna/update.md +0 -37
  143. data/data/guides/backend.md +0 -3069
  144. data/data/guides/frontend.md +0 -1479
  145. data/data/guides/performance.md +0 -1193
  146. data/data/guides/security.md +0 -1522
  147. data/data/guides/style-guide.md +0 -1091
  148. data/data/guides/testing.md +0 -504
  149. data/data/templates.md +0 -94
@@ -1,1193 +0,0 @@
1
- # Performance Guide for Rails Code Review
2
-
3
- **Agent-Oriented Performance Checklist for Automated Code Verification**
4
-
5
- This guide provides a structured checklist for verifying code performance after each development phase. Each section contains named CHECK items with severity levels, file glob patterns, and UNSAFE/SAFE code examples so an agent can systematically scan changed files and report findings.
6
-
7
- Unlike informational performance documentation, this guide is **action-oriented** — every check tells you what to look for, where to look, and how to fix it.
8
-
9
- **Related guides:**
10
- - [Backend Patterns](backend.md) — Architecture, models, controllers, jobs, style guide
11
- - [Testing Patterns](testing.md) — Testing philosophy, model/controller/job test patterns
12
- - [Security Guide](security.md) — Agent-oriented security checklist for code review
13
- - [Frontend Patterns](frontend.md) — Presenter pattern, view layer conventions
14
-
15
- ## Table of Contents
16
-
17
- - [Part 1: Database Query Performance](#part-1-database-query-performance)
18
- - [1.1 N+1 Queries](#11-n1-queries)
19
- - [1.2 Inefficient Queries](#12-inefficient-queries)
20
- - [1.3 Batch Processing](#13-batch-processing)
21
- - [1.4 Query Placement](#14-query-placement)
22
- - [Part 2: Database Indexing](#part-2-database-indexing)
23
- - [2.1 Missing Indexes](#21-missing-indexes)
24
- - [2.2 Index Anti-Patterns](#22-index-anti-patterns)
25
- - [Part 3: Caching](#part-3-caching)
26
- - [3.1 Cache Store Configuration](#31-cache-store-configuration)
27
- - [3.2 Fragment & Collection Caching](#32-fragment--collection-caching)
28
- - [3.3 Application-Level Caching](#33-application-level-caching)
29
- - [Part 4: Memory & Resource Management](#part-4-memory--resource-management)
30
- - [4.1 Memory-Intensive Operations](#41-memory-intensive-operations)
31
- - [4.2 Background Job Offloading](#42-background-job-offloading)
32
- - [4.3 Unnecessary Object Allocation](#43-unnecessary-object-allocation)
33
- - [Part 5: View & Response Performance](#part-5-view--response-performance)
34
- - [5.1 Collection Rendering](#51-collection-rendering)
35
- - [5.2 Asset & Frontend Performance](#52-asset--frontend-performance)
36
- - [Part 6: Deployment & Configuration](#part-6-deployment--configuration)
37
- - [6.1 Server Tuning](#61-server-tuning)
38
- - [6.2 Production Settings](#62-production-settings)
39
- - [Part 7: Performance Verification Checklist](#part-7-performance-verification-checklist)
40
- - [7.1 Agent Check Protocol](#71-agent-check-protocol)
41
- - [7.2 Quick-Reference Checklist](#72-quick-reference-checklist)
42
-
43
- ---
44
-
45
- # Part 1: Database Query Performance
46
-
47
- Database queries are the most common source of performance problems in Rails applications. These checks cover N+1 queries, inefficient query patterns, batch processing, and proper query placement.
48
-
49
- ## 1.1 N+1 Queries
50
-
51
- N+1 queries occur when code loads a collection and then executes a separate query for each item's association. This turns a single query into dozens or hundreds.
52
-
53
- ### CHECK 1.1a: Eager load associations accessed in loops
54
-
55
- > **What to look for:** Iterating over a collection and accessing an association (e.g., `post.author`, `order.line_items`) without a preceding `includes`, `eager_load`, or `preload` call
56
- > **Where to look:** `app/controllers/**/*.rb`, `app/views/**/*.erb`, `app/models/**/*.rb`
57
- > **Severity:** High
58
-
59
- **UNSAFE:**
60
-
61
- ```ruby
62
- # Controller
63
- def index
64
- @posts = Post.all
65
- end
66
- ```
67
-
68
- ```erb
69
- <%# View — triggers N+1: one query per post for author %>
70
- <% @posts.each do |post| %>
71
- <p><%= post.author.name %></p>
72
- <% end %>
73
- ```
74
-
75
- **SAFE:**
76
-
77
- ```ruby
78
- # Controller
79
- def index
80
- @posts = Post.includes(:author)
81
- end
82
- ```
83
-
84
- ```erb
85
- <%# View — author already loaded, no extra queries %>
86
- <% @posts.each do |post| %>
87
- <p><%= post.author.name %></p>
88
- <% end %>
89
- ```
90
-
91
- ### CHECK 1.1b: Nested association eager loading
92
-
93
- > **What to look for:** Views or serializers that access deeply nested associations (e.g., `post.comments.map(&:author)`) without nested `includes`
94
- > **Where to look:** `app/controllers/**/*.rb`, `app/views/**/*.erb`
95
- > **Severity:** High
96
-
97
- **UNSAFE:**
98
-
99
- ```ruby
100
- def show
101
- @post = Post.includes(:comments).find(params[:id])
102
- # comment.author triggers N+1 inside the view
103
- end
104
- ```
105
-
106
- **SAFE:**
107
-
108
- ```ruby
109
- def show
110
- @post = Post.includes(comments: :author).find(params[:id])
111
- end
112
- ```
113
-
114
- ### CHECK 1.1c: Counter cache for association counts
115
-
116
- > **What to look for:** Calling `.count` or `.size` on an association inside a loop without a `counter_cache` column
117
- > **Where to look:** `app/views/**/*.erb`, `app/models/**/*.rb`
118
- > **Severity:** Medium
119
-
120
- **UNSAFE:**
121
-
122
- ```erb
123
- <% @boards.each do |board| %>
124
- <span><%= board.cards.count %> cards</span>
125
- <% end %>
126
- ```
127
-
128
- **SAFE:**
129
-
130
- ```ruby
131
- # Migration
132
- add_column :boards, :cards_count, :integer, default: 0, null: false
133
-
134
- # Model
135
- class Card < ApplicationRecord
136
- belongs_to :board, counter_cache: true
137
- end
138
- ```
139
-
140
- ```erb
141
- <% @boards.each do |board| %>
142
- <span><%= board.cards_count %> cards</span>
143
- <% end %>
144
- ```
145
-
146
- ---
147
-
148
- ## 1.2 Inefficient Queries
149
-
150
- These patterns produce correct results but waste database resources by fetching more data than needed or executing queries that could be optimized.
151
-
152
- ### CHECK 1.2a: Select only needed columns
153
-
154
- > **What to look for:** Queries that load full ActiveRecord objects when only one or two columns are needed; missing `select` or `pluck` for data extraction
155
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`
156
- > **Severity:** Medium
157
-
158
- **UNSAFE:**
159
-
160
- ```ruby
161
- # Loads all columns for every user just to get emails
162
- emails = User.where(active: true).map(&:email)
163
-
164
- # Loads full objects to get IDs
165
- ids = Order.where(status: "pending").map(&:id)
166
- ```
167
-
168
- **SAFE:**
169
-
170
- ```ruby
171
- # Pluck returns plain arrays — no ActiveRecord objects allocated
172
- emails = User.where(active: true).pluck(:email)
173
-
174
- # IDs shortcut
175
- ids = Order.where(status: "pending").ids
176
- ```
177
-
178
- ### CHECK 1.2b: Use exists? instead of present? for existence checks
179
-
180
- > **What to look for:** `.present?`, `.any?`, or `.count > 0` on ActiveRecord relations when only existence is being checked
181
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`, `app/views/**/*.erb`
182
- > **Severity:** Medium
183
-
184
- **UNSAFE:**
185
-
186
- ```ruby
187
- # Loads all matching records into memory, then checks if array is non-empty
188
- if user.orders.where(status: "pending").present?
189
- show_pending_banner
190
- end
191
-
192
- # Counts all rows just to compare with zero
193
- if Comment.where(post_id: post.id).count > 0
194
- show_comments_section
195
- end
196
- ```
197
-
198
- **SAFE:**
199
-
200
- ```ruby
201
- # SELECT 1 ... LIMIT 1 — stops at first match
202
- if user.orders.where(status: "pending").exists?
203
- show_pending_banner
204
- end
205
-
206
- if Comment.where(post_id: post.id).exists?
207
- show_comments_section
208
- end
209
- ```
210
-
211
- ### CHECK 1.2c: Avoid loading records just to count them
212
-
213
- > **What to look for:** `.length` or `.size` called on relations that haven't been loaded, or `.count` called inside loops triggering repeated COUNT queries
214
- > **Where to look:** `app/views/**/*.erb`, `app/controllers/**/*.rb`
215
- > **Severity:** Medium
216
-
217
- **UNSAFE:**
218
-
219
- ```ruby
220
- # .length loads all records then counts the array
221
- total = Project.where(archived: false).length
222
-
223
- # .count inside a loop fires a COUNT query per iteration
224
- @categories.each do |category|
225
- puts "#{category.name}: #{category.products.count}"
226
- end
227
- ```
228
-
229
- **SAFE:**
230
-
231
- ```ruby
232
- # .count fires a single COUNT query
233
- total = Project.where(archived: false).count
234
-
235
- # Preload counts in a single query
236
- counts = Product.group(:category_id).count
237
- @categories.each do |category|
238
- puts "#{category.name}: #{counts[category.id] || 0}"
239
- end
240
- ```
241
-
242
- ---
243
-
244
- ## 1.3 Batch Processing
245
-
246
- Loading an entire table into memory crashes applications with large datasets. Batch processing methods stream records in configurable chunks.
247
-
248
- ### CHECK 1.3a: Use find_each for large iterations
249
-
250
- > **What to look for:** `.all.each`, `.where(...).each`, or `.order(...).each` iterating over unbounded or potentially large result sets without `find_each` or `find_in_batches`
251
- > **Where to look:** `app/models/**/*.rb`, `app/jobs/**/*.rb`, `lib/**/*.rb`
252
- > **Severity:** High
253
-
254
- **UNSAFE:**
255
-
256
- ```ruby
257
- # Loads ALL users into memory at once
258
- User.all.each do |user|
259
- UserMailer.weekly_digest(user).deliver_later
260
- end
261
-
262
- # Large result set loaded entirely
263
- Order.where("created_at < ?", 1.year.ago).each do |order|
264
- order.archive!
265
- end
266
- ```
267
-
268
- **SAFE:**
269
-
270
- ```ruby
271
- # Loads 1000 records at a time
272
- User.find_each do |user|
273
- UserMailer.weekly_digest(user).deliver_later
274
- end
275
-
276
- # Batched with custom size
277
- Order.where("created_at < ?", 1.year.ago).find_each(batch_size: 500) do |order|
278
- order.archive!
279
- end
280
- ```
281
-
282
- ### CHECK 1.3b: Use find_in_batches for batch operations
283
-
284
- > **What to look for:** Collecting large result sets into arrays for bulk operations instead of using `find_in_batches` or `in_batches`
285
- > **Where to look:** `app/jobs/**/*.rb`, `lib/tasks/**/*.rake`, `lib/**/*.rb`
286
- > **Severity:** Medium
287
-
288
- **UNSAFE:**
289
-
290
- ```ruby
291
- # Loads all records, then slices — peak memory holds entire table
292
- Product.where(discontinued: true).to_a.each_slice(100) do |batch|
293
- ProductIndex.bulk_delete(batch)
294
- end
295
- ```
296
-
297
- **SAFE:**
298
-
299
- ```ruby
300
- # Never holds more than 100 records in memory
301
- Product.where(discontinued: true).find_in_batches(batch_size: 100) do |batch|
302
- ProductIndex.bulk_delete(batch)
303
- end
304
- ```
305
-
306
- ---
307
-
308
- ## 1.4 Query Placement
309
-
310
- Sorting, filtering, and aggregating in Ruby instead of SQL wastes both memory and CPU. The database is optimized for these operations.
311
-
312
- ### CHECK 1.4a: Sort and filter at the database level
313
-
314
- > **What to look for:** Ruby `sort_by`, `select`, `reject`, `min_by`, `max_by`, `group_by` called on ActiveRecord collections that could use SQL `ORDER BY`, `WHERE`, `MIN`, `MAX`, `GROUP BY`
315
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`
316
- > **Severity:** Medium
317
-
318
- **UNSAFE:**
319
-
320
- ```ruby
321
- # Loads all records then sorts in Ruby
322
- @users = User.all.sort_by(&:created_at).reverse
323
-
324
- # Filters in Ruby after loading everything
325
- active_users = User.all.select { |u| u.active? && u.confirmed? }
326
-
327
- # Aggregates in Ruby
328
- total = Order.all.sum(&:total_price)
329
- ```
330
-
331
- **SAFE:**
332
-
333
- ```ruby
334
- # Database handles sorting
335
- @users = User.order(created_at: :desc)
336
-
337
- # Database handles filtering
338
- active_users = User.where(active: true, confirmed: true)
339
-
340
- # Database handles aggregation
341
- total = Order.sum(:total_price)
342
- ```
343
-
344
- ### CHECK 1.4b: Avoid Ruby computation on large datasets
345
-
346
- > **What to look for:** `map`, `flat_map`, `reduce`, `inject`, `each_with_object` on ActiveRecord relations that could be replaced with SQL operations
347
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`
348
- > **Severity:** Medium
349
-
350
- **UNSAFE:**
351
-
352
- ```ruby
353
- # Loads all orders into Ruby to extract unique statuses
354
- statuses = Order.all.map(&:status).uniq
355
-
356
- # Ruby-side grouping
357
- grouped = Transaction.all.group_by { |t| t.created_at.to_date }
358
- ```
359
-
360
- **SAFE:**
361
-
362
- ```ruby
363
- # Single query, returns array of strings
364
- statuses = Order.distinct.pluck(:status)
365
-
366
- # Database-side grouping
367
- grouped = Transaction.group("DATE(created_at)").count
368
- ```
369
-
370
- ---
371
-
372
- # Part 2: Database Indexing
373
-
374
- Missing or poorly designed indexes are the most common cause of slow queries in production. These checks verify that the schema supports the queries the application makes.
375
-
376
- ## 2.1 Missing Indexes
377
-
378
- ### CHECK 2.1a: Foreign key columns indexed
379
-
380
- > **What to look for:** `belongs_to` associations or `_id` columns in migrations without a corresponding database index
381
- > **Where to look:** `db/migrate/**/*.rb`, `db/schema.rb`
382
- > **Severity:** High
383
-
384
- **UNSAFE:**
385
-
386
- ```ruby
387
- class CreateComments < ActiveRecord::Migration[7.1]
388
- def change
389
- create_table :comments do |t|
390
- t.references :post, foreign_key: true, index: false
391
- t.integer :user_id # No index
392
- t.text :body
393
- t.timestamps
394
- end
395
- end
396
- end
397
- ```
398
-
399
- **SAFE:**
400
-
401
- ```ruby
402
- class CreateComments < ActiveRecord::Migration[7.1]
403
- def change
404
- create_table :comments do |t|
405
- t.references :post, foreign_key: true # index: true is default
406
- t.references :user, foreign_key: true
407
- t.text :body
408
- t.timestamps
409
- end
410
- end
411
- end
412
- ```
413
-
414
- ### CHECK 2.1b: Frequently queried columns indexed
415
-
416
- > **What to look for:** Columns used in `where`, `order`, `group`, or `find_by` clauses that lack indexes — especially `status`, `type`, `slug`, `email`, and date columns
417
- > **Where to look:** `db/migrate/**/*.rb`, `db/schema.rb`, `app/models/**/*.rb`
418
- > **Severity:** High
419
-
420
- **UNSAFE:**
421
-
422
- ```ruby
423
- # Model uses scopes on status and slug, but no indexes exist
424
- class Article < ApplicationRecord
425
- scope :published, -> { where(status: "published") }
426
- scope :by_slug, ->(slug) { find_by!(slug: slug) }
427
- end
428
-
429
- # schema.rb shows no index on status or slug
430
- ```
431
-
432
- **SAFE:**
433
-
434
- ```ruby
435
- class AddIndexesToArticles < ActiveRecord::Migration[7.1]
436
- def change
437
- add_index :articles, :status
438
- add_index :articles, :slug, unique: true
439
- end
440
- end
441
- ```
442
-
443
- ### CHECK 2.1c: Composite indexes for multi-column queries
444
-
445
- > **What to look for:** Queries with multiple `WHERE` conditions or `WHERE` + `ORDER BY` that use separate single-column indexes instead of a composite index
446
- > **Where to look:** `db/migrate/**/*.rb`, `db/schema.rb`, `app/models/**/*.rb`
447
- > **Severity:** Medium
448
-
449
- **UNSAFE:**
450
-
451
- ```ruby
452
- # Frequent query pattern
453
- Order.where(user_id: user.id, status: "pending").order(:created_at)
454
-
455
- # Only single-column indexes exist
456
- add_index :orders, :user_id
457
- add_index :orders, :status
458
- ```
459
-
460
- **SAFE:**
461
-
462
- ```ruby
463
- # Composite index matches the query pattern
464
- add_index :orders, [:user_id, :status, :created_at]
465
- ```
466
-
467
- ---
468
-
469
- ## 2.2 Index Anti-Patterns
470
-
471
- ### CHECK 2.2a: Concurrent index creation in production
472
-
473
- > **What to look for:** `add_index` in migrations without `algorithm: :concurrently` for large production tables; missing `disable_ddl_transaction!`
474
- > **Where to look:** `db/migrate/**/*.rb`
475
- > **Severity:** High
476
-
477
- **UNSAFE:**
478
-
479
- ```ruby
480
- class AddIndexToOrdersStatus < ActiveRecord::Migration[7.1]
481
- def change
482
- # Locks the entire orders table during index creation
483
- add_index :orders, :status
484
- end
485
- end
486
- ```
487
-
488
- **SAFE:**
489
-
490
- ```ruby
491
- class AddIndexToOrdersStatus < ActiveRecord::Migration[7.1]
492
- disable_ddl_transaction!
493
-
494
- def change
495
- add_index :orders, :status, algorithm: :concurrently
496
- end
497
- end
498
- ```
499
-
500
- ### CHECK 2.2b: Partial indexes for scoped queries
501
-
502
- > **What to look for:** Full-table indexes on columns where queries always include a filtering condition (e.g., only active records, only non-null values)
503
- > **Where to look:** `db/migrate/**/*.rb`, `app/models/**/*.rb`
504
- > **Severity:** Low
505
-
506
- **UNSAFE:**
507
-
508
- ```ruby
509
- # Full index when queries always filter for active
510
- add_index :users, :email
511
-
512
- # But the query is always:
513
- User.where(active: true).find_by(email: email)
514
- ```
515
-
516
- **SAFE:**
517
-
518
- ```ruby
519
- # Partial index — smaller, faster, only covers active users
520
- add_index :users, :email, where: "active = true", unique: true
521
- ```
522
-
523
- ---
524
-
525
- # Part 3: Caching
526
-
527
- Caching reduces redundant computation and database queries. These checks verify that caching is properly configured and applied where it matters most.
528
-
529
- ## 3.1 Cache Store Configuration
530
-
531
- ### CHECK 3.1a: Production cache store is not file or memory
532
-
533
- > **What to look for:** `config.cache_store` set to `:file_store` or `:memory_store` in production; missing cache store configuration for production
534
- > **Where to look:** `config/environments/production.rb`
535
- > **Severity:** High
536
-
537
- **UNSAFE:**
538
-
539
- ```ruby
540
- # config/environments/production.rb
541
- config.cache_store = :file_store, "/tmp/cache"
542
-
543
- # Or worse — default memory store in production
544
- # (no config.cache_store set at all)
545
- ```
546
-
547
- **SAFE:**
548
-
549
- ```ruby
550
- # config/environments/production.rb
551
- config.cache_store = :redis_cache_store, {
552
- url: ENV.fetch("REDIS_URL"),
553
- expires_in: 1.hour
554
- }
555
-
556
- # Or Memcached:
557
- config.cache_store = :mem_cache_store, ENV.fetch("MEMCACHED_URL")
558
-
559
- # Or Rails 8+ Solid Cache:
560
- config.cache_store = :solid_cache_store
561
- ```
562
-
563
- ---
564
-
565
- ## 3.2 Fragment & Collection Caching
566
-
567
- ### CHECK 3.2a: Cache expensive view partials
568
-
569
- > **What to look for:** Partials that execute queries or expensive computations rendered without `cache` blocks; missing cache keys on frequently rendered partials
570
- > **Where to look:** `app/views/**/*.erb`
571
- > **Severity:** Medium
572
-
573
- **UNSAFE:**
574
-
575
- ```erb
576
- <%# Rendered on every request, executes queries each time %>
577
- <%= render partial: "dashboard/stats" %>
578
- ```
579
-
580
- **SAFE:**
581
-
582
- ```erb
583
- <% cache("dashboard/stats", expires_in: 5.minutes) do %>
584
- <%= render partial: "dashboard/stats" %>
585
- <% end %>
586
-
587
- <%# Or with record-based cache key: %>
588
- <% cache(@project) do %>
589
- <%= render partial: "projects/detail", locals: { project: @project } %>
590
- <% end %>
591
- ```
592
-
593
- ### CHECK 3.2b: Collection rendering with caching
594
-
595
- > **What to look for:** `render collection:` without the `cached: true` option on collections that don't change frequently
596
- > **Where to look:** `app/views/**/*.erb`
597
- > **Severity:** Medium
598
-
599
- **UNSAFE:**
600
-
601
- ```erb
602
- <%# Renders each partial individually, no caching %>
603
- <%= render partial: "products/product", collection: @products %>
604
- ```
605
-
606
- **SAFE:**
607
-
608
- ```erb
609
- <%# Multi-fetch caching — reads all cache keys in one round-trip %>
610
- <%= render partial: "products/product", collection: @products, cached: true %>
611
- ```
612
-
613
- ---
614
-
615
- ## 3.3 Application-Level Caching
616
-
617
- ### CHECK 3.3a: Cache expensive computations
618
-
619
- > **What to look for:** Repeated expensive computations (API calls, complex aggregations, report generation) without `Rails.cache.fetch`
620
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`, `lib/**/*.rb`
621
- > **Severity:** Medium
622
-
623
- **UNSAFE:**
624
-
625
- ```ruby
626
- class Dashboard
627
- def stats
628
- # Runs complex aggregation on every call
629
- {
630
- total_revenue: Order.where(status: "completed").sum(:total),
631
- active_users: User.where("last_sign_in_at > ?", 30.days.ago).count,
632
- conversion_rate: calculate_conversion_rate
633
- }
634
- end
635
- end
636
- ```
637
-
638
- **SAFE:**
639
-
640
- ```ruby
641
- class Dashboard
642
- def stats
643
- Rails.cache.fetch("dashboard/stats", expires_in: 15.minutes) do
644
- {
645
- total_revenue: Order.where(status: "completed").sum(:total),
646
- active_users: User.where("last_sign_in_at > ?", 30.days.ago).count,
647
- conversion_rate: calculate_conversion_rate
648
- }
649
- end
650
- end
651
- end
652
- ```
653
-
654
- ### CHECK 3.3b: Memoization for per-request computation
655
-
656
- > **What to look for:** Methods called multiple times per request that perform database queries or computations without memoization
657
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`, `app/helpers/**/*.rb`
658
- > **Severity:** Low
659
-
660
- **UNSAFE:**
661
-
662
- ```ruby
663
- class User < ApplicationRecord
664
- def active_subscription
665
- # Queries the database every time this is called
666
- subscriptions.where(active: true).order(created_at: :desc).first
667
- end
668
- end
669
- ```
670
-
671
- **SAFE:**
672
-
673
- ```ruby
674
- class User < ApplicationRecord
675
- def active_subscription
676
- @active_subscription ||= subscriptions.where(active: true).order(created_at: :desc).first
677
- end
678
- end
679
- ```
680
-
681
- ---
682
-
683
- # Part 4: Memory & Resource Management
684
-
685
- These checks prevent excessive memory usage, ensure expensive operations run in the background, and reduce unnecessary object allocation.
686
-
687
- ## 4.1 Memory-Intensive Operations
688
-
689
- ### CHECK 4.1a: Avoid loading full result sets for partial data
690
-
691
- > **What to look for:** Loading full ActiveRecord objects when only specific columns are needed; `.all` or `.where(...)` followed by `.map` to extract attributes
692
- > **Where to look:** `app/models/**/*.rb`, `app/controllers/**/*.rb`, `app/jobs/**/*.rb`
693
- > **Severity:** High
694
-
695
- **UNSAFE:**
696
-
697
- ```ruby
698
- # Loads full User objects (all columns) just to get names
699
- names = User.where(role: "admin").map(&:name)
700
-
701
- # Loads all orders into memory to sum a column
702
- total = Order.where(user_id: user.id).map(&:total_price).sum
703
- ```
704
-
705
- **SAFE:**
706
-
707
- ```ruby
708
- # Returns plain array of strings — no ActiveRecord objects
709
- names = User.where(role: "admin").pluck(:name)
710
-
711
- # Single SQL SUM — no records loaded
712
- total = Order.where(user_id: user.id).sum(:total_price)
713
- ```
714
-
715
- ### CHECK 4.1b: Stream large file exports
716
-
717
- > **What to look for:** CSV or file exports that build the entire file in memory before sending; `CSV.generate` on unbounded datasets
718
- > **Where to look:** `app/controllers/**/*.rb`, `lib/**/*.rb`
719
- > **Severity:** High
720
-
721
- **UNSAFE:**
722
-
723
- ```ruby
724
- def export
725
- csv = CSV.generate do |csv|
726
- csv << ["Name", "Email", "Created"]
727
- User.all.each do |user|
728
- csv << [user.name, user.email, user.created_at]
729
- end
730
- end
731
- send_data csv, filename: "users.csv"
732
- end
733
- ```
734
-
735
- **SAFE:**
736
-
737
- ```ruby
738
- def export
739
- headers["Content-Disposition"] = 'attachment; filename="users.csv"'
740
- headers["Content-Type"] = "text/csv"
741
-
742
- response.status = 200
743
- self.response_body = Enumerator.new do |yielder|
744
- yielder << CSV.generate_line(["Name", "Email", "Created"])
745
- User.find_each do |user|
746
- yielder << CSV.generate_line([user.name, user.email, user.created_at])
747
- end
748
- end
749
- end
750
- ```
751
-
752
- ---
753
-
754
- ## 4.2 Background Job Offloading
755
-
756
- ### CHECK 4.2a: Expensive work not in request cycle
757
-
758
- > **What to look for:** Email delivery, PDF generation, API calls to external services, image processing, or report generation executed synchronously in controller actions
759
- > **Where to look:** `app/controllers/**/*.rb`
760
- > **Severity:** High
761
-
762
- **UNSAFE:**
763
-
764
- ```ruby
765
- class OrdersController < ApplicationController
766
- def create
767
- @order = Order.create!(order_params)
768
- OrderMailer.confirmation(@order).deliver_now # Blocks response
769
- PdfGenerator.generate_invoice(@order) # Blocks response
770
- InventoryApi.reserve_items(@order.line_items) # Blocks response
771
- redirect_to @order
772
- end
773
- end
774
- ```
775
-
776
- **SAFE:**
777
-
778
- ```ruby
779
- class OrdersController < ApplicationController
780
- def create
781
- @order = Order.create!(order_params)
782
- OrderMailer.confirmation(@order).deliver_later # Background job
783
- GenerateInvoiceJob.perform_later(@order) # Background job
784
- ReserveInventoryJob.perform_later(@order.line_items) # Background job
785
- redirect_to @order
786
- end
787
- end
788
- ```
789
-
790
- ### CHECK 4.2b: Use deliver_later for emails
791
-
792
- > **What to look for:** `deliver_now` in controller actions or model callbacks; synchronous email delivery in the request cycle
793
- > **Where to look:** `app/controllers/**/*.rb`, `app/models/**/*.rb`
794
- > **Severity:** High
795
-
796
- **UNSAFE:**
797
-
798
- ```ruby
799
- UserMailer.welcome(user).deliver_now
800
- NotificationMailer.alert(admin).deliver_now
801
- ```
802
-
803
- **SAFE:**
804
-
805
- ```ruby
806
- UserMailer.welcome(user).deliver_later
807
- NotificationMailer.alert(admin).deliver_later
808
- ```
809
-
810
- ---
811
-
812
- ## 4.3 Unnecessary Object Allocation
813
-
814
- ### CHECK 4.3a: Freeze string literals used as constants
815
-
816
- > **What to look for:** String literals assigned to constants or used repeatedly without freezing; missing `# frozen_string_literal: true` pragma
817
- > **Where to look:** `app/**/*.rb`, `lib/**/*.rb`
818
- > **Severity:** Low
819
-
820
- **UNSAFE:**
821
-
822
- ```ruby
823
- class PaymentProcessor
824
- DEFAULT_CURRENCY = "usd" # Allocates a new string every access
825
- SUPPORTED_TYPES = ["card", "bank"] # Allocates new array and strings
826
-
827
- def process(amount)
828
- currency = "usd" # New string object every call
829
- # ...
830
- end
831
- end
832
- ```
833
-
834
- **SAFE:**
835
-
836
- ```ruby
837
- # frozen_string_literal: true
838
-
839
- class PaymentProcessor
840
- DEFAULT_CURRENCY = "usd"
841
- SUPPORTED_TYPES = %w[card bank].freeze
842
-
843
- def process(amount)
844
- # String literals are frozen by the pragma
845
- # ...
846
- end
847
- end
848
- ```
849
-
850
- ### CHECK 4.3b: Avoid repeated allocations in loops
851
-
852
- > **What to look for:** Object creation (strings, arrays, hashes, regex) inside loops where the object could be extracted to a constant or local variable
853
- > **Where to look:** `app/**/*.rb`, `lib/**/*.rb`
854
- > **Severity:** Low
855
-
856
- **UNSAFE:**
857
-
858
- ```ruby
859
- users.each do |user|
860
- if user.name.match?(/\A[A-Z]/) # Regex compiled every iteration
861
- tags = user.bio.split(",").map(&:strip)
862
- formatted = "User: #{user.name}" # Fine with frozen_string_literal
863
- end
864
- end
865
- ```
866
-
867
- **SAFE:**
868
-
869
- ```ruby
870
- STARTS_WITH_CAPITAL = /\A[A-Z]/
871
-
872
- users.each do |user|
873
- if user.name.match?(STARTS_WITH_CAPITAL)
874
- tags = user.bio.split(",").map(&:strip)
875
- formatted = "User: #{user.name}"
876
- end
877
- end
878
- ```
879
-
880
- ---
881
-
882
- # Part 5: View & Response Performance
883
-
884
- These checks cover efficient rendering of collections and frontend asset delivery.
885
-
886
- ## 5.1 Collection Rendering
887
-
888
- ### CHECK 5.1a: Use render collection instead of loops
889
-
890
- > **What to look for:** Manual `each` loops in views rendering the same partial repeatedly instead of `render collection:`
891
- > **Where to look:** `app/views/**/*.erb`
892
- > **Severity:** Medium
893
-
894
- **UNSAFE:**
895
-
896
- ```erb
897
- <% @products.each do |product| %>
898
- <%= render partial: "products/product", locals: { product: product } %>
899
- <% end %>
900
- ```
901
-
902
- **SAFE:**
903
-
904
- ```erb
905
- <%= render partial: "products/product", collection: @products, as: :product %>
906
-
907
- <%# Or shorthand: %>
908
- <%= render @products %>
909
- ```
910
-
911
- ### CHECK 5.1b: Efficient JSON serialization
912
-
913
- > **What to look for:** Calling `.to_json` on full ActiveRecord objects or collections; rendering JSON without selecting specific attributes
914
- > **Where to look:** `app/controllers/**/*.rb`, `app/controllers/api/**/*.rb`
915
- > **Severity:** Medium
916
-
917
- **UNSAFE:**
918
-
919
- ```ruby
920
- def index
921
- @users = User.all
922
- render json: @users.to_json
923
- end
924
- ```
925
-
926
- **SAFE:**
927
-
928
- ```ruby
929
- def index
930
- @users = User.select(:id, :name, :email)
931
- render json: @users.as_json(only: [:id, :name, :email])
932
- end
933
-
934
- # Or with jbuilder for complex responses:
935
- # app/views/api/users/index.json.jbuilder
936
- ```
937
-
938
- ---
939
-
940
- ## 5.2 Asset & Frontend Performance
941
-
942
- ### CHECK 5.2a: Pagination for large collections
943
-
944
- > **What to look for:** Controller actions that load unbounded collections for display in views; missing pagination on index actions
945
- > **Where to look:** `app/controllers/**/*.rb`
946
- > **Severity:** High
947
-
948
- **UNSAFE:**
949
-
950
- ```ruby
951
- def index
952
- @orders = Order.all
953
- end
954
- ```
955
-
956
- **SAFE:**
957
-
958
- ```ruby
959
- def index
960
- @orders = Order.order(created_at: :desc).page(params[:page]).per(25)
961
- end
962
-
963
- # Or with cursor-based pagination:
964
- def index
965
- @orders = Order.where("id < ?", params[:cursor] || Float::INFINITY)
966
- .order(id: :desc)
967
- .limit(25)
968
- end
969
- ```
970
-
971
- ### CHECK 5.2b: Turbo Frame lazy loading for heavy sections
972
-
973
- > **What to look for:** Page sections that load expensive data on every full page load when they could be deferred with Turbo Frames
974
- > **Where to look:** `app/views/**/*.erb`
975
- > **Severity:** Low
976
-
977
- **UNSAFE:**
978
-
979
- ```erb
980
- <%# Stats section loads on every page visit, even if user doesn't scroll to it %>
981
- <div id="stats">
982
- <%= render "dashboard/heavy_stats" %>
983
- </div>
984
- ```
985
-
986
- **SAFE:**
987
-
988
- ```erb
989
- <%# Loaded lazily only when the frame enters the viewport %>
990
- <%= turbo_frame_tag "stats", src: dashboard_stats_path, loading: :lazy do %>
991
- <p>Loading stats...</p>
992
- <% end %>
993
- ```
994
-
995
- ---
996
-
997
- # Part 6: Deployment & Configuration
998
-
999
- These checks verify that production environments are tuned for performance.
1000
-
1001
- ## 6.1 Server Tuning
1002
-
1003
- ### CHECK 6.1a: Puma threads and workers configured
1004
-
1005
- > **What to look for:** Default Puma configuration in production; missing or improperly sized `threads` and `workers` settings
1006
- > **Where to look:** `config/puma.rb`
1007
- > **Severity:** High
1008
-
1009
- **UNSAFE:**
1010
-
1011
- ```ruby
1012
- # config/puma.rb — defaults, not tuned for production
1013
- threads_count = 5
1014
- threads threads_count, threads_count
1015
- ```
1016
-
1017
- **SAFE:**
1018
-
1019
- ```ruby
1020
- # config/puma.rb
1021
- max_threads = ENV.fetch("RAILS_MAX_THREADS", 5).to_i
1022
- min_threads = ENV.fetch("RAILS_MIN_THREADS", max_threads).to_i
1023
- threads min_threads, max_threads
1024
-
1025
- workers ENV.fetch("WEB_CONCURRENCY", 2).to_i
1026
- preload_app!
1027
- ```
1028
-
1029
- ### CHECK 6.1b: YJIT enabled in production
1030
-
1031
- > **What to look for:** Ruby 3.2+ applications not enabling YJIT; missing `RUBY_YJIT_ENABLE` or `--yjit` flag
1032
- > **Where to look:** `config/puma.rb`, `Dockerfile`, `.ruby-version`
1033
- > **Severity:** Medium
1034
-
1035
- **UNSAFE:**
1036
-
1037
- ```dockerfile
1038
- # Dockerfile — YJIT not enabled
1039
- CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
1040
- ```
1041
-
1042
- **SAFE:**
1043
-
1044
- ```dockerfile
1045
- # Dockerfile — YJIT enabled via environment variable
1046
- ENV RUBY_YJIT_ENABLE=1
1047
- CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
1048
- ```
1049
-
1050
- ```ruby
1051
- # Or in config/puma.rb:
1052
- if defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable)
1053
- RubyVM::YJIT.enable
1054
- end
1055
- ```
1056
-
1057
- ---
1058
-
1059
- ## 6.2 Production Settings
1060
-
1061
- ### CHECK 6.2a: Cache classes and eager load enabled
1062
-
1063
- > **What to look for:** `config.cache_classes = false` or `config.eager_load = false` in production environment
1064
- > **Where to look:** `config/environments/production.rb`
1065
- > **Severity:** High
1066
-
1067
- **UNSAFE:**
1068
-
1069
- ```ruby
1070
- # config/environments/production.rb
1071
- config.cache_classes = false # Reloads code on every request
1072
- config.eager_load = false # Lazy loads, causing slow first requests
1073
- ```
1074
-
1075
- **SAFE:**
1076
-
1077
- ```ruby
1078
- # config/environments/production.rb
1079
- config.cache_classes = true
1080
- config.eager_load = true
1081
- ```
1082
-
1083
- ### CHECK 6.2b: Asset compression and digests enabled
1084
-
1085
- > **What to look for:** Missing asset compression or digest fingerprinting in production; uncompressed CSS and JavaScript
1086
- > **Where to look:** `config/environments/production.rb`
1087
- > **Severity:** Medium
1088
-
1089
- **UNSAFE:**
1090
-
1091
- ```ruby
1092
- # config/environments/production.rb
1093
- config.assets.compile = true # On-the-fly compilation in production
1094
- config.assets.digest = false # No cache-busting fingerprints
1095
- ```
1096
-
1097
- **SAFE:**
1098
-
1099
- ```ruby
1100
- # config/environments/production.rb
1101
- config.assets.compile = false
1102
- config.assets.digest = true
1103
- config.assets.css_compressor = :sass
1104
- ```
1105
-
1106
- ---
1107
-
1108
- # Part 7: Performance Verification Checklist
1109
-
1110
- ## 7.1 Agent Check Protocol
1111
-
1112
- When verifying performance after a development phase, follow this protocol to map changed files to relevant checks.
1113
-
1114
- ### Step 1: Identify changed files
1115
-
1116
- ```bash
1117
- git diff --name-only HEAD~1
1118
- # Or for a phase: git diff <phase-start-sha>..HEAD --name-only
1119
- ```
1120
-
1121
- ### Step 2: Map files to check sections
1122
-
1123
- | Changed file pattern | Applicable sections |
1124
- |---|---|
1125
- | `app/models/**/*.rb` | 1.1 (N+1), 1.2 (inefficient queries), 1.4 (query placement), 3.3 (caching), 4.1 (memory) |
1126
- | `app/controllers/**/*.rb` | 1.1a (eager loading), 1.2b (exists?), 4.2 (background jobs), 5.1b (JSON), 5.2a (pagination) |
1127
- | `app/views/**/*.erb` | 1.1a (N+1 in views), 1.1c (counter cache), 3.2 (fragment caching), 5.1a (collection rendering), 5.2b (lazy loading) |
1128
- | `app/jobs/**/*.rb` | 1.3 (batch processing), 4.1 (memory) |
1129
- | `db/migrate/**/*.rb` | 2.1 (missing indexes), 2.2 (index anti-patterns) |
1130
- | `db/schema.rb` | 2.1 (missing indexes) |
1131
- | `config/puma.rb` | 6.1 (server tuning) |
1132
- | `config/environments/production.rb` | 3.1 (cache store), 6.2 (production settings) |
1133
- | `lib/**/*.rb` | 1.3 (batch processing), 4.1 (memory), 4.3 (object allocation) |
1134
- | `Dockerfile` | 6.1b (YJIT) |
1135
-
1136
- ### Step 3: Run applicable checks
1137
-
1138
- For each applicable section, scan the changed files using the "Where to look" glob pattern and "What to look for" pattern. Report findings as:
1139
-
1140
- ```
1141
- PASS: CHECK 1.1a — Eager load associations (scanned 3 files)
1142
- FAIL: CHECK 1.3a — User.all.each in app/jobs/digest_job.rb:12
1143
- SKIP: CHECK 2.1a — No migration changes in this phase
1144
- ```
1145
-
1146
- ---
1147
-
1148
- ## 7.2 Quick-Reference Checklist
1149
-
1150
- All checks from Parts 1-6 in a single table for quick scanning.
1151
-
1152
- | Check | Name | Severity | Grep pattern | Files |
1153
- |---|---|---|---|---|
1154
- | 1.1a | Eager load associations in loops | High | `\.includes\|\.eager_load\|\.preload` | `app/controllers/**/*.rb` |
1155
- | 1.1b | Nested association eager loading | High | `\.includes\(.*:` | `app/controllers/**/*.rb` |
1156
- | 1.1c | Counter cache for counts | Medium | `\.count\b` in loops | `app/views/**/*.erb` |
1157
- | 1.2a | Select only needed columns | Medium | `\.map\(&:` after query | `app/models/**/*.rb` |
1158
- | 1.2b | exists? instead of present? | Medium | `\.present?\|\.any?\|\.count > 0` | `app/**/*.rb` |
1159
- | 1.2c | Avoid load-to-count | Medium | `\.length` on relation | `app/**/*.rb` |
1160
- | 1.3a | find_each for large iterations | High | `\.all\.each\|\.where.*\.each` | `app/jobs/**/*.rb`, `lib/**/*.rb` |
1161
- | 1.3b | find_in_batches for bulk ops | Medium | `\.to_a\.each_slice` | `app/jobs/**/*.rb`, `lib/**/*.rb` |
1162
- | 1.4a | Sort/filter at database level | Medium | `\.sort_by\|\.select \{` on AR | `app/models/**/*.rb` |
1163
- | 1.4b | Avoid Ruby compute on large sets | Medium | `\.all\.map\|\.all\.reduce` | `app/models/**/*.rb` |
1164
- | 2.1a | Foreign key columns indexed | High | `t\.integer.*_id` without index | `db/migrate/**/*.rb` |
1165
- | 2.1b | Frequently queried columns indexed | High | `add_column` without `add_index` | `db/migrate/**/*.rb` |
1166
- | 2.1c | Composite indexes for multi-column | Medium | `\.where.*\.where\|\.where.*\.order` | `app/models/**/*.rb` |
1167
- | 2.2a | Concurrent index in production | High | `add_index` without `concurrently` | `db/migrate/**/*.rb` |
1168
- | 2.2b | Partial indexes for scoped queries | Low | `add_index.*where:` | `db/migrate/**/*.rb` |
1169
- | 3.1a | Production cache store | High | `cache_store.*:file_store\|:memory_store` | `config/environments/production.rb` |
1170
- | 3.2a | Cache expensive view partials | Medium | `render partial:` without `cache` | `app/views/**/*.erb` |
1171
- | 3.2b | Collection caching | Medium | `render.*collection:` without `cached` | `app/views/**/*.erb` |
1172
- | 3.3a | Cache expensive computations | Medium | `Rails\.cache\.fetch` | `app/models/**/*.rb`, `lib/**/*.rb` |
1173
- | 3.3b | Memoization for per-request | Low | `@.*\|\|=` | `app/models/**/*.rb` |
1174
- | 4.1a | Avoid full loads for partial data | High | `\.map\(&:` after `.where` | `app/models/**/*.rb`, `app/jobs/**/*.rb` |
1175
- | 4.1b | Stream large file exports | High | `CSV\.generate.*\.all` | `app/controllers/**/*.rb` |
1176
- | 4.2a | Expensive work in background | High | `deliver_now\|\.generate.*\.render` | `app/controllers/**/*.rb` |
1177
- | 4.2b | deliver_later for emails | High | `deliver_now` | `app/controllers/**/*.rb` |
1178
- | 4.3a | Freeze string literals | Low | `frozen_string_literal` | `app/**/*.rb` |
1179
- | 4.3b | No repeated allocs in loops | Low | `Regexp\.new\|/.*/ inside \.each` | `app/**/*.rb`, `lib/**/*.rb` |
1180
- | 5.1a | render collection vs loops | Medium | `\.each.*render partial:` | `app/views/**/*.erb` |
1181
- | 5.1b | Efficient JSON serialization | Medium | `\.to_json` on AR objects | `app/controllers/**/*.rb` |
1182
- | 5.2a | Pagination for large collections | High | `\.all` without `.page\|.limit` | `app/controllers/**/*.rb` |
1183
- | 5.2b | Turbo Frame lazy loading | Low | `turbo_frame_tag.*loading: :lazy` | `app/views/**/*.erb` |
1184
- | 6.1a | Puma threads/workers configured | High | `workers\|threads` | `config/puma.rb` |
1185
- | 6.1b | YJIT enabled | Medium | `RUBY_YJIT_ENABLE\|yjit` | `Dockerfile`, `config/puma.rb` |
1186
- | 6.2a | cache_classes and eager_load | High | `cache_classes.*false\|eager_load.*false` | `config/environments/production.rb` |
1187
- | 6.2b | Asset compression and digests | Medium | `assets\.compile.*true\|assets\.digest.*false` | `config/environments/production.rb` |
1188
-
1189
- ---
1190
-
1191
- **Document Version**: 1.0
1192
- **Last Updated**: 2026-02-15
1193
- **Maintainer**: Development Team