source_monitor 0.2.0 → 0.3.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 (196) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/agents/rails-concern.md +464 -0
  3. data/.claude/agents/rails-controller.md +424 -0
  4. data/.claude/agents/rails-hotwire.md +446 -0
  5. data/.claude/agents/rails-implement.md +374 -0
  6. data/.claude/agents/rails-job.md +334 -0
  7. data/.claude/agents/rails-lint.md +294 -0
  8. data/.claude/agents/rails-mailer.md +371 -0
  9. data/.claude/agents/rails-migration.md +449 -0
  10. data/.claude/agents/rails-model.md +420 -0
  11. data/.claude/agents/rails-policy.md +443 -0
  12. data/.claude/agents/rails-presenter.md +427 -0
  13. data/.claude/agents/rails-query.md +412 -0
  14. data/.claude/agents/rails-review.md +490 -0
  15. data/.claude/agents/rails-service.md +458 -0
  16. data/.claude/agents/rails-state-records.md +465 -0
  17. data/.claude/agents/rails-tdd.md +314 -0
  18. data/.claude/agents/rails-test.md +441 -0
  19. data/.claude/agents/rails-view-component.md +418 -0
  20. data/.claude/hooks/block-secrets.sh +52 -0
  21. data/.claude/settings.json +85 -0
  22. data/.claude/skills/action-cable-patterns/SKILL.md +296 -0
  23. data/.claude/skills/action-mailer-patterns/SKILL.md +295 -0
  24. data/.claude/skills/active-storage-setup/SKILL.md +311 -0
  25. data/.claude/skills/api-versioning/SKILL.md +294 -0
  26. data/.claude/skills/authentication-flow/SKILL.md +335 -0
  27. data/.claude/skills/authentication-flow/reference/current.md +248 -0
  28. data/.claude/skills/authentication-flow/reference/passwordless.md +253 -0
  29. data/.claude/skills/authentication-flow/reference/sessions.md +201 -0
  30. data/.claude/skills/authorization-pundit/SKILL.md +462 -0
  31. data/.claude/skills/caching-strategies/SKILL.md +350 -0
  32. data/.claude/skills/database-migrations/SKILL.md +354 -0
  33. data/.claude/skills/form-object-patterns/SKILL.md +399 -0
  34. data/.claude/skills/hotwire-patterns/SKILL.md +247 -0
  35. data/.claude/skills/hotwire-patterns/reference/stimulus.md +307 -0
  36. data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +112 -0
  37. data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +158 -0
  38. data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +218 -0
  39. data/.claude/skills/i18n-patterns/SKILL.md +320 -0
  40. data/.claude/skills/install/SKILL.md +367 -0
  41. data/.claude/skills/performance-optimization/SKILL.md +311 -0
  42. data/.claude/skills/rails-architecture/SKILL.md +259 -0
  43. data/.claude/skills/rails-architecture/reference/error-handling.md +333 -0
  44. data/.claude/skills/rails-architecture/reference/event-tracking.md +142 -0
  45. data/.claude/skills/rails-architecture/reference/layer-interactions.md +417 -0
  46. data/.claude/skills/rails-architecture/reference/multi-tenancy.md +152 -0
  47. data/.claude/skills/rails-architecture/reference/query-patterns.md +342 -0
  48. data/.claude/skills/rails-architecture/reference/service-patterns.md +286 -0
  49. data/.claude/skills/rails-architecture/reference/state-records.md +250 -0
  50. data/.claude/skills/rails-architecture/reference/testing-strategy.md +326 -0
  51. data/.claude/skills/rails-concern/SKILL.md +399 -0
  52. data/.claude/skills/rails-controller/SKILL.md +336 -0
  53. data/.claude/skills/rails-model-generator/SKILL.md +321 -0
  54. data/.claude/skills/rails-model-generator/reference/validations.md +298 -0
  55. data/.claude/skills/rails-presenter/SKILL.md +274 -0
  56. data/.claude/skills/rails-query-object/SKILL.md +289 -0
  57. data/.claude/skills/rails-service-object/SKILL.md +349 -0
  58. data/.claude/skills/solid-queue-setup/SKILL.md +307 -0
  59. data/.claude/skills/tdd-cycle/SKILL.md +359 -0
  60. data/.claude/skills/viewcomponent-patterns/SKILL.md +333 -0
  61. data/.gitignore +1 -0
  62. data/.rubocop.yml +2 -0
  63. data/.ruby-version +1 -1
  64. data/.vbw-planning/.notification-log.jsonl +192 -0
  65. data/.vbw-planning/.session-log.jsonl +871 -0
  66. data/.vbw-planning/PROJECT.md +51 -0
  67. data/.vbw-planning/REQUIREMENTS.md +50 -0
  68. data/.vbw-planning/SHIPPED.md +28 -0
  69. data/.vbw-planning/codebase/ARCHITECTURE.md +147 -0
  70. data/.vbw-planning/codebase/CONCERNS.md +99 -0
  71. data/.vbw-planning/codebase/CONVENTIONS.md +97 -0
  72. data/.vbw-planning/codebase/DEPENDENCIES.md +100 -0
  73. data/.vbw-planning/codebase/INDEX.md +86 -0
  74. data/.vbw-planning/codebase/META.md +42 -0
  75. data/.vbw-planning/codebase/PATTERNS.md +262 -0
  76. data/.vbw-planning/codebase/STACK.md +101 -0
  77. data/.vbw-planning/codebase/STRUCTURE.md +324 -0
  78. data/.vbw-planning/codebase/TESTING.md +154 -0
  79. data/.vbw-planning/config.json +12 -0
  80. data/.vbw-planning/discovery.json +24 -0
  81. data/.vbw-planning/milestones/default/ROADMAP.md +115 -0
  82. data/.vbw-planning/milestones/default/STATE.md +83 -0
  83. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +56 -0
  84. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +187 -0
  85. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +64 -0
  86. data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +137 -0
  87. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +67 -0
  88. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +142 -0
  89. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +64 -0
  90. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +138 -0
  91. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +85 -0
  92. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +147 -0
  93. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +63 -0
  94. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +129 -0
  95. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +74 -0
  96. data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +154 -0
  97. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +303 -0
  98. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +510 -0
  99. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +61 -0
  100. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +161 -0
  101. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +66 -0
  102. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +132 -0
  103. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +59 -0
  104. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +171 -0
  105. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +56 -0
  106. data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +152 -0
  107. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +33 -0
  108. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +42 -0
  109. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +119 -0
  110. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +52 -0
  111. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +195 -0
  112. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +79 -0
  113. data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +130 -0
  114. data/CHANGELOG.md +28 -0
  115. data/CLAUDE.md +179 -0
  116. data/Gemfile +8 -0
  117. data/Gemfile.lock +114 -101
  118. data/Rakefile +2 -0
  119. data/app/assets/builds/source_monitor/application.css +2076 -0
  120. data/app/assets/builds/source_monitor/application.js +2758 -0
  121. data/app/assets/builds/source_monitor/application.js.map +7 -0
  122. data/app/controllers/source_monitor/application_controller.rb +2 -0
  123. data/app/controllers/source_monitor/health_controller.rb +2 -0
  124. data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +106 -0
  125. data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +187 -0
  126. data/app/controllers/source_monitor/import_sessions/health_check_management.rb +112 -0
  127. data/app/controllers/source_monitor/import_sessions/opml_parser.rb +130 -0
  128. data/app/controllers/source_monitor/import_sessions_controller.rb +6 -507
  129. data/app/controllers/source_monitor/items_controller.rb +2 -0
  130. data/app/controllers/source_monitor/sources_controller.rb +0 -14
  131. data/app/helpers/source_monitor/application_helper.rb +4 -112
  132. data/app/helpers/source_monitor/health_badge_helper.rb +69 -0
  133. data/app/helpers/source_monitor/table_sort_helper.rb +53 -0
  134. data/app/jobs/source_monitor/application_job.rb +2 -0
  135. data/app/models/source_monitor/application_record.rb +2 -0
  136. data/app/models/source_monitor/log_entry.rb +0 -2
  137. data/config/coverage_baseline.json +217 -1862
  138. data/config/routes.rb +2 -0
  139. data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +2 -0
  140. data/db/migrate/20251014171659_add_performance_indexes.rb +2 -0
  141. data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +2 -0
  142. data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +2 -0
  143. data/db/migrate/20260210204022_add_composite_index_to_log_entries.rb +17 -0
  144. data/lib/source_monitor/assets/bundler.rb +2 -0
  145. data/lib/source_monitor/assets.rb +2 -0
  146. data/lib/source_monitor/configuration/authentication_settings.rb +62 -0
  147. data/lib/source_monitor/configuration/events.rb +60 -0
  148. data/lib/source_monitor/configuration/fetching_settings.rb +27 -0
  149. data/lib/source_monitor/configuration/health_settings.rb +27 -0
  150. data/lib/source_monitor/configuration/http_settings.rb +43 -0
  151. data/lib/source_monitor/configuration/model_definition.rb +108 -0
  152. data/lib/source_monitor/configuration/models.rb +36 -0
  153. data/lib/source_monitor/configuration/realtime_settings.rb +95 -0
  154. data/lib/source_monitor/configuration/retention_settings.rb +45 -0
  155. data/lib/source_monitor/configuration/scraper_registry.rb +67 -0
  156. data/lib/source_monitor/configuration/scraping_settings.rb +39 -0
  157. data/lib/source_monitor/configuration/validation_definition.rb +32 -0
  158. data/lib/source_monitor/configuration.rb +12 -579
  159. data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +138 -0
  160. data/lib/source_monitor/dashboard/queries/stats_query.rb +71 -0
  161. data/lib/source_monitor/dashboard/queries.rb +2 -195
  162. data/lib/source_monitor/engine.rb +2 -0
  163. data/lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb +141 -0
  164. data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +89 -0
  165. data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +200 -0
  166. data/lib/source_monitor/fetching/feed_fetcher.rb +37 -379
  167. data/lib/source_monitor/items/item_creator/content_extractor.rb +113 -0
  168. data/lib/source_monitor/items/item_creator/entry_parser/media_extraction.rb +96 -0
  169. data/lib/source_monitor/items/item_creator/entry_parser.rb +294 -0
  170. data/lib/source_monitor/items/item_creator.rb +28 -455
  171. data/lib/source_monitor/setup/bundle_installer.rb +2 -0
  172. data/lib/source_monitor/setup/cli.rb +2 -0
  173. data/lib/source_monitor/setup/dependency_checker.rb +2 -0
  174. data/lib/source_monitor/setup/detectors.rb +2 -0
  175. data/lib/source_monitor/setup/gemfile_editor.rb +2 -0
  176. data/lib/source_monitor/setup/initializer_patcher.rb +2 -0
  177. data/lib/source_monitor/setup/install_generator.rb +2 -0
  178. data/lib/source_monitor/setup/migration_installer.rb +2 -0
  179. data/lib/source_monitor/setup/node_installer.rb +2 -0
  180. data/lib/source_monitor/setup/prompter.rb +2 -0
  181. data/lib/source_monitor/setup/requirements.rb +2 -0
  182. data/lib/source_monitor/setup/shell_runner.rb +2 -0
  183. data/lib/source_monitor/setup/verification/action_cable_verifier.rb +2 -0
  184. data/lib/source_monitor/setup/verification/printer.rb +2 -0
  185. data/lib/source_monitor/setup/verification/result.rb +2 -0
  186. data/lib/source_monitor/setup/verification/runner.rb +2 -0
  187. data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +2 -0
  188. data/lib/source_monitor/setup/verification/telemetry_logger.rb +2 -0
  189. data/lib/source_monitor/setup/workflow.rb +2 -0
  190. data/lib/source_monitor/version.rb +3 -1
  191. data/lib/source_monitor.rb +140 -58
  192. data/lib/tasks/source_monitor_assets.rake +2 -0
  193. data/lib/tasks/source_monitor_setup.rake +2 -0
  194. data/lib/tasks/source_monitor_tasks.rake +2 -0
  195. data/source_monitor.gemspec +3 -1
  196. metadata +144 -4
@@ -0,0 +1,367 @@
1
+ ---
2
+ name: install
3
+ description: Sets up a new Rails 8 application with recommended defaults for TDD, authentication, multi-tenancy, and Hotwire. Use when creating a new Rails project, bootstrapping an app, or when user mentions new app, rails new, project setup, or initial setup.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Rails 8 Application Setup
8
+
9
+ ## Overview
10
+
11
+ Bootstrap a new Rails 8 application with production-ready defaults:
12
+ - Rails 8 with Solid Queue, Solid Cache, Solid Cable
13
+ - Built-in authentication (has_secure_password)
14
+ - Multi-tenancy via Account scoping
15
+ - Hotwire (Turbo + Stimulus)
16
+ - Minitest + fixtures for testing
17
+ - Tailwind CSS for styling
18
+
19
+ ## Quick Start
20
+
21
+ ```bash
22
+ rails new myapp --css tailwind --database sqlite3
23
+ cd myapp
24
+ ```
25
+
26
+ ## Post-Generation Setup
27
+
28
+ ### 1. Generate Authentication
29
+
30
+ ```bash
31
+ bin/rails generate authentication
32
+ bin/rails db:migrate
33
+ ```
34
+
35
+ This creates:
36
+ - `User` model with `email_address` and `password_digest`
37
+ - `Session` model with token-based sessions
38
+ - `Current` model (CurrentAttributes)
39
+ - `Authentication` concern
40
+ - `SessionsController`
41
+
42
+ ### 2. Add Account Model (Multi-Tenancy)
43
+
44
+ ```bash
45
+ bin/rails generate model Account name:string
46
+ bin/rails generate migration AddAccountToUsers account:references
47
+ bin/rails db:migrate
48
+ ```
49
+
50
+ ```ruby
51
+ # app/models/account.rb
52
+ class Account < ApplicationRecord
53
+ has_many :users, dependent: :destroy
54
+
55
+ validates :name, presence: true
56
+ end
57
+ ```
58
+
59
+ ```ruby
60
+ # app/models/user.rb
61
+ class User < ApplicationRecord
62
+ has_secure_password
63
+ has_many :sessions, dependent: :destroy
64
+ belongs_to :account
65
+
66
+ normalizes :email_address, with: ->(e) { e.strip.downcase }
67
+
68
+ validates :email_address, presence: true,
69
+ uniqueness: true,
70
+ format: { with: URI::MailTo::EMAIL_REGEXP }
71
+ end
72
+ ```
73
+
74
+ ### 3. Add Current Account
75
+
76
+ ```ruby
77
+ # app/models/current.rb
78
+ class Current < ActiveSupport::CurrentAttributes
79
+ attribute :session
80
+ attribute :user_agent, :ip_address
81
+
82
+ delegate :user, to: :session, allow_nil: true
83
+
84
+ def account
85
+ user&.account
86
+ end
87
+ end
88
+ ```
89
+
90
+ ### 4. Configure Test Helper
91
+
92
+ ```ruby
93
+ # test/test_helper.rb
94
+ ENV["RAILS_ENV"] ||= "test"
95
+ require_relative "../config/environment"
96
+ require "rails/test_help"
97
+
98
+ module ActiveSupport
99
+ class TestCase
100
+ parallelize(workers: :number_of_processors)
101
+ fixtures :all
102
+
103
+ private
104
+
105
+ def sign_in(user)
106
+ session = user.sessions.create!
107
+ cookies.signed.permanent[:session_token] = {
108
+ value: session.token,
109
+ httponly: true
110
+ }
111
+ Current.session = session
112
+ end
113
+
114
+ def sign_out
115
+ Current.session&.destroy
116
+ cookies.delete(:session_token)
117
+ end
118
+ end
119
+ end
120
+ ```
121
+
122
+ ### 5. Create Fixtures
123
+
124
+ ```yaml
125
+ # test/fixtures/accounts.yml
126
+ one:
127
+ name: Acme Corp
128
+
129
+ other:
130
+ name: Other Corp
131
+ ```
132
+
133
+ ```yaml
134
+ # test/fixtures/users.yml
135
+ one:
136
+ email_address: user@example.com
137
+ password_digest: <%= BCrypt::Password.create("password123") %>
138
+ account: one
139
+
140
+ admin:
141
+ email_address: admin@example.com
142
+ password_digest: <%= BCrypt::Password.create("password123") %>
143
+ account: one
144
+
145
+ other_account:
146
+ email_address: other@example.com
147
+ password_digest: <%= BCrypt::Password.create("password123") %>
148
+ account: other
149
+ ```
150
+
151
+ ```yaml
152
+ # test/fixtures/sessions.yml
153
+ one:
154
+ user: one
155
+ token: <%= SecureRandom.urlsafe_base64(32) %>
156
+ ```
157
+
158
+ ### 6. Configure Solid Queue
159
+
160
+ ```yaml
161
+ # config/queue.yml (already generated by Rails 8)
162
+ default: &default
163
+ dispatchers:
164
+ - polling_interval: 1
165
+ batch_size: 500
166
+ workers:
167
+ - queues: "*"
168
+ threads: 3
169
+ processes: 1
170
+ polling_interval: 0.1
171
+ ```
172
+
173
+ ```ruby
174
+ # config/environments/production.rb
175
+ config.active_job.queue_adapter = :solid_queue
176
+ config.solid_queue.connects_to = { database: { writing: :queue } }
177
+ ```
178
+
179
+ ### 7. Configure Solid Cache
180
+
181
+ ```ruby
182
+ # config/environments/production.rb
183
+ config.cache_store = :solid_cache_store
184
+ config.solid_cache.connects_to = { database: { writing: :cache } }
185
+ ```
186
+
187
+ ### 8. Add Development Gems
188
+
189
+ ```ruby
190
+ # Gemfile
191
+ group :development, :test do
192
+ gem "debug", platforms: %i[mri windows], require: "debug/prelude"
193
+ gem "brakeman", require: false
194
+ gem "rubocop-rails-omakase", require: false
195
+ gem "bullet"
196
+ end
197
+ ```
198
+
199
+ ```bash
200
+ bundle install
201
+ ```
202
+
203
+ ### 9. Configure Bullet (N+1 Detection)
204
+
205
+ ```ruby
206
+ # config/environments/development.rb
207
+ config.after_initialize do
208
+ Bullet.enable = true
209
+ Bullet.alert = true
210
+ Bullet.bullet_logger = true
211
+ Bullet.console = true
212
+ Bullet.rails_logger = true
213
+ end
214
+
215
+ # config/environments/test.rb
216
+ config.after_initialize do
217
+ Bullet.enable = true
218
+ Bullet.raise = true
219
+ end
220
+ ```
221
+
222
+ ### 10. Add Application Layout Defaults
223
+
224
+ ```erb
225
+ <%# app/views/layouts/application.html.erb %>
226
+ <!DOCTYPE html>
227
+ <html>
228
+ <head>
229
+ <title><%= content_for(:title) || "MyApp" %></title>
230
+ <meta name="viewport" content="width=device-width,initial-scale=1">
231
+ <%= csrf_meta_tags %>
232
+ <%= csp_meta_tag %>
233
+ <%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
234
+ <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
235
+ <%= javascript_importmap_tags %>
236
+ </head>
237
+ <body class="min-h-screen bg-white">
238
+ <div id="flash">
239
+ <% flash.each do |type, message| %>
240
+ <div class="flash flash-<%= type %>"><%= message %></div>
241
+ <% end %>
242
+ </div>
243
+
244
+ <main class="container mx-auto px-4 py-8">
245
+ <%= yield %>
246
+ </main>
247
+ </body>
248
+ </html>
249
+ ```
250
+
251
+ ## Verification
252
+
253
+ Run the test suite to confirm everything is wired up:
254
+
255
+ ```bash
256
+ bin/rails test
257
+ ```
258
+
259
+ ### Smoke Test
260
+
261
+ ```ruby
262
+ # test/models/user_test.rb
263
+ require "test_helper"
264
+
265
+ class UserTest < ActiveSupport::TestCase
266
+ test "fixture user is valid" do
267
+ user = users(:one)
268
+ assert user.valid?
269
+ end
270
+
271
+ test "requires email_address" do
272
+ user = User.new(password: "password123", account: accounts(:one))
273
+ assert_not user.valid?
274
+ assert user.errors[:email_address].any?
275
+ end
276
+
277
+ test "requires unique email_address" do
278
+ existing = users(:one)
279
+ user = User.new(
280
+ email_address: existing.email_address,
281
+ password: "password123",
282
+ account: accounts(:one)
283
+ )
284
+ assert_not user.valid?
285
+ assert user.errors[:email_address].any?
286
+ end
287
+ end
288
+ ```
289
+
290
+ ```ruby
291
+ # test/models/account_test.rb
292
+ require "test_helper"
293
+
294
+ class AccountTest < ActiveSupport::TestCase
295
+ test "fixture account is valid" do
296
+ account = accounts(:one)
297
+ assert account.valid?
298
+ end
299
+
300
+ test "requires name" do
301
+ account = Account.new
302
+ assert_not account.valid?
303
+ assert account.errors[:name].any?
304
+ end
305
+ end
306
+ ```
307
+
308
+ ## Directory Structure After Setup
309
+
310
+ ```
311
+ app/
312
+ ├── controllers/
313
+ │ ├── application_controller.rb
314
+ │ ├── concerns/
315
+ │ │ └── authentication.rb
316
+ │ └── sessions_controller.rb
317
+ ├── models/
318
+ │ ├── account.rb
319
+ │ ├── current.rb
320
+ │ ├── session.rb
321
+ │ └── user.rb
322
+ ├── views/
323
+ │ └── layouts/
324
+ │ └── application.html.erb
325
+ config/
326
+ ├── database.yml
327
+ ├── queue.yml
328
+ ├── cable.yml
329
+ └── environments/
330
+ ├── development.rb
331
+ ├── production.rb
332
+ └── test.rb
333
+ test/
334
+ ├── test_helper.rb
335
+ ├── fixtures/
336
+ │ ├── accounts.yml
337
+ │ ├── users.yml
338
+ │ └── sessions.yml
339
+ └── models/
340
+ ├── user_test.rb
341
+ └── account_test.rb
342
+ ```
343
+
344
+ ## Rails 8 Defaults
345
+
346
+ Rails 8 ships with these defaults already configured:
347
+ - **Solid Queue** - Database-backed Active Job backend (replaces Redis/Sidekiq)
348
+ - **Solid Cache** - Database-backed cache store (replaces Redis/Memcached)
349
+ - **Solid Cable** - Database-backed Action Cable adapter
350
+ - **Propshaft** - Asset pipeline (replaces Sprockets)
351
+ - **Import Maps** - JavaScript without bundling
352
+ - **Kamal** - Deployment
353
+ - **Thruster** - HTTP/2 proxy with asset caching
354
+
355
+ ## Checklist
356
+
357
+ - [ ] Rails app generated
358
+ - [ ] Authentication generated and migrated
359
+ - [ ] Account model created (multi-tenancy)
360
+ - [ ] Current model configured with account
361
+ - [ ] Test helper with sign_in/sign_out
362
+ - [ ] Fixtures created (accounts, users, sessions)
363
+ - [ ] Solid Queue configured
364
+ - [ ] Solid Cache configured
365
+ - [ ] Bullet configured for N+1 detection
366
+ - [ ] Smoke tests pass
367
+ - [ ] All tests GREEN
@@ -0,0 +1,311 @@
1
+ ---
2
+ name: performance-optimization
3
+ description: Identifies and fixes Rails performance issues including N+1 queries, slow queries, and memory problems. Use when optimizing queries, fixing N+1 issues, improving response times, or when user mentions performance, slow, optimization, or Bullet gem.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Performance Optimization for Rails 8
8
+
9
+ ## Overview
10
+
11
+ Performance optimization focuses on:
12
+ - N+1 query detection and prevention
13
+ - Query optimization with eager loading
14
+ - Database indexing
15
+ - Memory management
16
+ - Batch processing
17
+
18
+ ## Quick Start
19
+
20
+ ```ruby
21
+ # Gemfile
22
+ group :development, :test do
23
+ gem "bullet" # N+1 detection
24
+ gem "rack-mini-profiler" # Request profiling
25
+ end
26
+ ```
27
+
28
+ ## Bullet Configuration
29
+
30
+ ```ruby
31
+ # config/environments/development.rb
32
+ config.after_initialize do
33
+ Bullet.enable = true
34
+ Bullet.alert = true
35
+ Bullet.bullet_logger = true
36
+ Bullet.console = true
37
+ Bullet.rails_logger = true
38
+ end
39
+
40
+ # config/environments/test.rb
41
+ config.after_initialize do
42
+ Bullet.enable = true
43
+ Bullet.raise = true # Fail tests on N+1
44
+ end
45
+ ```
46
+
47
+ ## N+1 Query Problems
48
+
49
+ ### The Problem
50
+
51
+ ```ruby
52
+ # BAD: N+1 - 1 query for events, N queries for venues
53
+ @events = Event.all
54
+ @events.each { |e| e.venue.name } # Query per event!
55
+ ```
56
+
57
+ ### The Solution
58
+
59
+ ```ruby
60
+ # GOOD: 2 queries total
61
+ @events = Event.includes(:venue)
62
+ @events.each { |e| e.venue.name } # No additional query
63
+ ```
64
+
65
+ ## Eager Loading Methods
66
+
67
+ | Method | Use When |
68
+ |--------|----------|
69
+ | `includes` | Most cases (Rails chooses strategy) |
70
+ | `preload` | Force separate queries, large datasets |
71
+ | `eager_load` | Filtering on association, need LEFT JOIN |
72
+ | `joins` | Only filtering, don't need association data |
73
+
74
+ ```ruby
75
+ # Single association
76
+ Event.includes(:venue)
77
+
78
+ # Multiple
79
+ Event.includes(:venue, :organizer)
80
+
81
+ # Nested
82
+ Event.includes(venue: :address)
83
+
84
+ # Deep nesting
85
+ Event.includes(
86
+ :venue, :organizer,
87
+ vendors: [:category, :reviews],
88
+ comments: :user
89
+ )
90
+ ```
91
+
92
+ ## Query Optimization Patterns
93
+
94
+ ### Pattern 1: Scoped Eager Loading
95
+
96
+ ```ruby
97
+ class Event < ApplicationRecord
98
+ scope :with_details, -> {
99
+ includes(:venue, :organizer, vendors: :category)
100
+ }
101
+ end
102
+
103
+ # Controller
104
+ @events = Event.with_details.where(account: current_account)
105
+ ```
106
+
107
+ ### Pattern 2: Counter Caches
108
+
109
+ ```ruby
110
+ # Migration
111
+ add_column :events, :comments_count, :integer, default: 0, null: false
112
+
113
+ # Model
114
+ class Comment < ApplicationRecord
115
+ belongs_to :event, counter_cache: true
116
+ end
117
+
118
+ # Usage (no query)
119
+ event.comments_count
120
+ ```
121
+
122
+ ### Pattern 3: Select Only Needed Columns
123
+
124
+ ```ruby
125
+ # BAD
126
+ User.all.map(&:name)
127
+
128
+ # GOOD
129
+ User.pluck(:name)
130
+
131
+ # For objects with limited columns
132
+ User.select(:id, :name, :email)
133
+ ```
134
+
135
+ ### Pattern 4: Batch Processing
136
+
137
+ ```ruby
138
+ # BAD: Loads all records
139
+ Event.all.each { |e| process(e) }
140
+
141
+ # GOOD: Processes in batches
142
+ Event.find_each(batch_size: 500) { |e| process(e) }
143
+
144
+ # For updates
145
+ Event.in_batches(of: 1000) do |batch|
146
+ batch.update_all(status: :archived)
147
+ end
148
+ ```
149
+
150
+ ### Pattern 5: Exists? vs Present?
151
+
152
+ ```ruby
153
+ # BAD: Loads all records
154
+ if Event.where(status: :active).any?
155
+ if Event.where(status: :active).present?
156
+
157
+ # GOOD: SELECT 1 LIMIT 1
158
+ if Event.where(status: :active).exists?
159
+ ```
160
+
161
+ ### Pattern 6: Size vs Count vs Length
162
+
163
+ ```ruby
164
+ # count: Always queries database
165
+ # size: Uses counter cache if available, else count
166
+ # length: Loads collection if not loaded
167
+
168
+ # Use size (handles both cases)
169
+ events.size
170
+ ```
171
+
172
+ ## Database Indexing
173
+
174
+ ### When to Add Indexes
175
+
176
+ | Add Index For | Example |
177
+ |--------------|---------|
178
+ | Foreign keys | `account_id`, `user_id` |
179
+ | WHERE columns | `WHERE status = 'active'` |
180
+ | ORDER BY columns | `ORDER BY created_at DESC` |
181
+ | JOIN columns | `JOIN ON events.venue_id` |
182
+ | Unique constraints | `email`, `uuid` |
183
+
184
+ ### Index Types
185
+
186
+ ```ruby
187
+ add_index :events, :status # Single
188
+ add_index :events, [:account_id, :status] # Composite
189
+ add_index :users, :email, unique: true # Unique
190
+ add_index :events, :event_date, where: "status = 0" # Partial
191
+ ```
192
+
193
+ ## Testing for Performance
194
+
195
+ ### N+1 Detection in Tests
196
+
197
+ ```ruby
198
+ # test/test_helper.rb
199
+ class ActiveSupport::TestCase
200
+ setup do
201
+ Bullet.start_request if Bullet.enable?
202
+ end
203
+
204
+ teardown do
205
+ if Bullet.enable?
206
+ Bullet.perform_out_of_channel_notifications if Bullet.notification?
207
+ Bullet.end_request
208
+ end
209
+ end
210
+ end
211
+ ```
212
+
213
+ ### Query Count Assertions
214
+
215
+ ```ruby
216
+ # test/support/query_counter.rb
217
+ module QueryCounter
218
+ def count_queries(&block)
219
+ count = 0
220
+ counter = ->(*, _) { count += 1 }
221
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record", &block)
222
+ count
223
+ end
224
+ end
225
+
226
+ # test/test_helper.rb
227
+ class ActiveSupport::TestCase
228
+ include QueryCounter
229
+ end
230
+ ```
231
+
232
+ ```ruby
233
+ # test/models/event_test.rb
234
+ require "test_helper"
235
+
236
+ class EventPerformanceTest < ActiveSupport::TestCase
237
+ test "with_details makes minimal queries" do
238
+ query_count = count_queries do
239
+ Event.with_details.where(account: accounts(:one)).each do |e|
240
+ e.venue&.name
241
+ e.organizer&.name
242
+ end
243
+ end
244
+
245
+ # events + venues + organizers = 3 queries max
246
+ assert query_count <= 3, "Expected <= 3 queries, got #{query_count}"
247
+ end
248
+ end
249
+ ```
250
+
251
+ ### Missing Index Detection
252
+
253
+ ```ruby
254
+ # test/db/schema_test.rb
255
+ require "test_helper"
256
+
257
+ class SchemaPerformanceTest < ActiveSupport::TestCase
258
+ test "all foreign keys have indexes" do
259
+ connection = ActiveRecord::Base.connection
260
+
261
+ connection.tables.each do |table|
262
+ columns = connection.columns(table)
263
+ fk_columns = columns.select { |c| c.name.end_with?("_id") }
264
+ indexes = connection.indexes(table)
265
+
266
+ fk_columns.each do |col|
267
+ indexed = indexes.any? { |idx| idx.columns.include?(col.name) }
268
+ assert indexed, "Missing index: #{table}.#{col.name}"
269
+ end
270
+ end
271
+ end
272
+ end
273
+ ```
274
+
275
+ ## Memory Optimization
276
+
277
+ ```ruby
278
+ # BAD: Builds large array
279
+ Event.all.map(&:name).join(", ")
280
+
281
+ # GOOD: Streams results
282
+ Event.pluck(:name).join(", ")
283
+
284
+ # BAD: Instantiates all AR objects
285
+ Event.all.each { |e| e.update!(processed: true) }
286
+
287
+ # GOOD: Direct SQL update in batches
288
+ Event.in_batches.update_all(processed: true)
289
+ ```
290
+
291
+ ## Quick Fixes Reference
292
+
293
+ | Problem | Solution |
294
+ |---------|----------|
295
+ | N+1 on belongs_to | `includes(:association)` |
296
+ | N+1 on has_many | `includes(:association)` |
297
+ | Slow COUNT | Add counter_cache |
298
+ | Loading all columns | Use `select` or `pluck` |
299
+ | Large dataset iteration | Use `find_each` |
300
+ | Missing index on FK | Add index on `*_id` columns |
301
+ | Slow WHERE clause | Add index on filtered column |
302
+
303
+ ## Checklist
304
+
305
+ - [ ] Bullet enabled in development/test
306
+ - [ ] No N+1 queries in critical paths
307
+ - [ ] Foreign keys have indexes
308
+ - [ ] Counter caches for frequent counts
309
+ - [ ] Eager loading in controllers
310
+ - [ ] Batch processing for large datasets
311
+ - [ ] All tests GREEN