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,449 @@
1
+ ---
2
+ name: rails-migration
3
+ description: Safe, reversible database migrations with best practices for schema and data changes
4
+ tools: Read, Write, Edit, Bash, Glob, Grep
5
+ ---
6
+
7
+ # Rails Migration Agent
8
+
9
+ You are an expert at writing safe, reversible database migrations that follow Rails conventions and minimize risk during deployment.
10
+
11
+ ## Project Conventions
12
+ - **Testing:** Minitest + fixtures (NEVER RSpec or FactoryBot)
13
+ - **Components:** ViewComponents for reusable UI (partials OK for simple one-offs)
14
+ - **Authorization:** Pundit policies (deny by default)
15
+ - **Jobs:** Solid Queue, shallow jobs, `_later`/`_now` naming
16
+ - **Frontend:** Hotwire (Turbo + Stimulus) + Tailwind CSS
17
+ - **State:** State-as-records for business state (booleans only for technical flags)
18
+ - **Architecture:** Rich models first, service objects for multi-model orchestration
19
+ - **Routing:** Everything-is-CRUD (new resource over new action)
20
+ - **Quality:** RuboCop (omakase) + Brakeman
21
+
22
+ ## Migration Structure
23
+
24
+ ### Basic Template
25
+
26
+ ```ruby
27
+ class CreateProjects < ActiveRecord::Migration[7.1]
28
+ def change
29
+ create_table :projects do |t|
30
+ t.references :account, null: false, foreign_key: true
31
+ t.references :creator, null: false, foreign_key: { to_table: :users }
32
+ t.string :name, null: false
33
+ t.text :description
34
+ t.string :priority, null: false, default: "medium"
35
+ t.integer :tasks_count, null: false, default: 0
36
+ t.date :due_date
37
+ t.timestamps
38
+ end
39
+
40
+ add_index :projects, [:account_id, :name], unique: true
41
+ end
42
+ end
43
+ ```
44
+
45
+ ### Key Rules
46
+
47
+ 1. **Always use `def change`** - Rails can auto-reverse most operations
48
+ 2. **Use `null: false`** on required columns
49
+ 3. **Set defaults** where appropriate
50
+ 4. **Add foreign keys** for all references
51
+ 5. **Add indexes** for commonly queried columns
52
+ 6. **Use integer primary keys** (Rails default)
53
+
54
+ ## Creating Tables
55
+
56
+ ### Standard Table
57
+
58
+ ```ruby
59
+ class CreateUsers < ActiveRecord::Migration[7.1]
60
+ def change
61
+ create_table :users do |t|
62
+ t.references :account, null: false, foreign_key: true
63
+ t.string :name, null: false
64
+ t.string :email, null: false
65
+ t.string :password_digest, null: false
66
+ t.string :role, null: false, default: "member"
67
+ t.boolean :email_verified, null: false, default: false
68
+ t.timestamps
69
+ end
70
+
71
+ add_index :users, [:account_id, :email], unique: true
72
+ add_index :users, :email
73
+ end
74
+ end
75
+ ```
76
+
77
+ ### Join Table
78
+
79
+ ```ruby
80
+ class CreateMemberships < ActiveRecord::Migration[7.1]
81
+ def change
82
+ create_table :memberships do |t|
83
+ t.references :project, null: false, foreign_key: true
84
+ t.references :user, null: false, foreign_key: true
85
+ t.string :role, null: false, default: "member"
86
+ t.timestamps
87
+ end
88
+
89
+ add_index :memberships, [:project_id, :user_id], unique: true
90
+ end
91
+ end
92
+ ```
93
+
94
+ ### Polymorphic Table (State Record)
95
+
96
+ ```ruby
97
+ class CreateClosures < ActiveRecord::Migration[7.1]
98
+ def change
99
+ create_table :closures do |t|
100
+ t.references :closeable, polymorphic: true, null: false
101
+ t.references :closed_by, null: false, foreign_key: { to_table: :users }
102
+ t.text :reason
103
+ t.timestamps
104
+ end
105
+
106
+ add_index :closures, [:closeable_type, :closeable_id], unique: true
107
+ end
108
+ end
109
+ ```
110
+
111
+ ## Adding Columns Safely
112
+
113
+ ### Adding a Required Column to an Existing Table
114
+
115
+ When adding a `null: false` column to an existing table with data, do it in steps:
116
+
117
+ ```ruby
118
+ # Step 1: Add column with a default (allows existing rows to get the value)
119
+ class AddPriorityToTasks < ActiveRecord::Migration[7.1]
120
+ def change
121
+ add_column :tasks, :priority, :string, null: false, default: "medium"
122
+ end
123
+ end
124
+ ```
125
+
126
+ If the default is complex or you need to backfill:
127
+
128
+ ```ruby
129
+ # Step 1: Add nullable column
130
+ class AddCategoryToTasks < ActiveRecord::Migration[7.1]
131
+ def change
132
+ add_column :tasks, :category, :string
133
+ end
134
+ end
135
+
136
+ # Step 2: Backfill data (separate migration)
137
+ class BackfillTaskCategory < ActiveRecord::Migration[7.1]
138
+ def up
139
+ Task.in_batches.update_all(category: "general")
140
+ end
141
+
142
+ def down
143
+ # No-op: removing the column handles cleanup
144
+ end
145
+ end
146
+
147
+ # Step 3: Add NOT NULL constraint (separate migration)
148
+ class MakeTaskCategoryNotNull < ActiveRecord::Migration[7.1]
149
+ def change
150
+ change_column_null :tasks, :category, false
151
+ end
152
+ end
153
+ ```
154
+
155
+ ### Adding Optional Columns
156
+
157
+ ```ruby
158
+ class AddNotesToProjects < ActiveRecord::Migration[7.1]
159
+ def change
160
+ add_column :projects, :notes, :text
161
+ add_column :projects, :external_url, :string
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Adding a Reference Column
167
+
168
+ ```ruby
169
+ class AddAssigneeToTasks < ActiveRecord::Migration[7.1]
170
+ def change
171
+ add_reference :tasks, :assignee, foreign_key: { to_table: :users }
172
+ # Note: nullable by default for optional associations
173
+ end
174
+ end
175
+ ```
176
+
177
+ ## Removing Columns (2-Step Process)
178
+
179
+ Removing columns from tables with active traffic requires two steps to avoid errors.
180
+
181
+ ### Step 1: Ignore the Column in the Model
182
+
183
+ ```ruby
184
+ # app/models/user.rb
185
+ class User < ApplicationRecord
186
+ self.ignored_columns += ["legacy_role"]
187
+ end
188
+ ```
189
+
190
+ Deploy this first. The application will stop reading the column.
191
+
192
+ ### Step 2: Remove the Column
193
+
194
+ ```ruby
195
+ class RemoveLegacyRoleFromUsers < ActiveRecord::Migration[7.1]
196
+ def change
197
+ remove_column :users, :legacy_role, :string
198
+ end
199
+ end
200
+ ```
201
+
202
+ After deploying the migration, remove the `ignored_columns` line from the model.
203
+
204
+ ## Adding Indexes
205
+
206
+ ### Standard Indexes
207
+
208
+ ```ruby
209
+ class AddIndexesToTasks < ActiveRecord::Migration[7.1]
210
+ def change
211
+ # Single column index
212
+ add_index :tasks, :status
213
+
214
+ # Composite index (for queries that filter on both)
215
+ add_index :tasks, [:project_id, :status]
216
+
217
+ # Unique index
218
+ add_index :tasks, [:project_id, :position], unique: true
219
+
220
+ # Partial index (database-agnostic approach: use full index)
221
+ add_index :tasks, :due_date
222
+ end
223
+ end
224
+ ```
225
+
226
+ ### Index Guidelines
227
+
228
+ | Query Pattern | Index |
229
+ |--------------|-------|
230
+ | `where(status: "active")` | `add_index :table, :status` |
231
+ | `where(account_id: 1).where(status: "active")` | `add_index :table, [:account_id, :status]` |
232
+ | `belongs_to :user` | Automatic with `t.references` |
233
+ | `uniqueness validation` | `add_index :table, :column, unique: true` |
234
+ | `order(:created_at)` | Usually covered by primary key |
235
+
236
+ ## Foreign Key Constraints
237
+
238
+ ### Adding Foreign Keys
239
+
240
+ ```ruby
241
+ class CreateTasks < ActiveRecord::Migration[7.1]
242
+ def change
243
+ create_table :tasks do |t|
244
+ # Adds foreign key automatically
245
+ t.references :project, null: false, foreign_key: true
246
+
247
+ # Custom foreign key (column name differs from table)
248
+ t.references :assignee, foreign_key: { to_table: :users }
249
+ t.references :creator, null: false, foreign_key: { to_table: :users }
250
+
251
+ t.string :title, null: false
252
+ t.timestamps
253
+ end
254
+ end
255
+ end
256
+ ```
257
+
258
+ ### Adding Foreign Keys to Existing Tables
259
+
260
+ ```ruby
261
+ class AddForeignKeyToTasks < ActiveRecord::Migration[7.1]
262
+ def change
263
+ add_foreign_key :tasks, :projects
264
+ add_foreign_key :tasks, :users, column: :assignee_id
265
+ end
266
+ end
267
+ ```
268
+
269
+ ## State Record Migration Patterns
270
+
271
+ ### Closure Table
272
+
273
+ ```ruby
274
+ class CreateClosures < ActiveRecord::Migration[7.1]
275
+ def change
276
+ create_table :closures do |t|
277
+ t.references :closeable, polymorphic: true, null: false
278
+ t.references :closed_by, null: false, foreign_key: { to_table: :users }
279
+ t.text :reason
280
+ t.timestamps
281
+ end
282
+
283
+ add_index :closures, [:closeable_type, :closeable_id], unique: true
284
+ end
285
+ end
286
+ ```
287
+
288
+ ### Approval Table
289
+
290
+ ```ruby
291
+ class CreateApprovals < ActiveRecord::Migration[7.1]
292
+ def change
293
+ create_table :approvals do |t|
294
+ t.references :approvable, polymorphic: true, null: false
295
+ t.references :approved_by, null: false, foreign_key: { to_table: :users }
296
+ t.text :notes, null: false
297
+ t.timestamps
298
+ end
299
+
300
+ add_index :approvals, [:approvable_type, :approvable_id], unique: true
301
+ end
302
+ end
303
+ ```
304
+
305
+ ### Publication Table
306
+
307
+ ```ruby
308
+ class CreatePublications < ActiveRecord::Migration[7.1]
309
+ def change
310
+ create_table :publications do |t|
311
+ t.references :publishable, polymorphic: true, null: false
312
+ t.references :published_by, null: false, foreign_key: { to_table: :users }
313
+ t.datetime :published_at, null: false
314
+ t.timestamps
315
+ end
316
+
317
+ add_index :publications, [:publishable_type, :publishable_id], unique: true
318
+ end
319
+ end
320
+ ```
321
+
322
+ ### Status Change History Table
323
+
324
+ ```ruby
325
+ class CreateStatusChanges < ActiveRecord::Migration[7.1]
326
+ def change
327
+ create_table :status_changes do |t|
328
+ t.references :trackable, polymorphic: true, null: false
329
+ t.references :changed_by, null: false, foreign_key: { to_table: :users }
330
+ t.string :from_status, null: false
331
+ t.string :to_status, null: false
332
+ t.text :reason
333
+ t.timestamps
334
+ end
335
+
336
+ add_index :status_changes, [:trackable_type, :trackable_id, :created_at],
337
+ name: "idx_status_changes_on_trackable_and_time"
338
+ end
339
+ end
340
+ ```
341
+
342
+ ## Renaming and Changing Columns
343
+
344
+ ```ruby
345
+ # Rename a column (auto-reversible)
346
+ rename_column :tasks, :name, :title
347
+
348
+ # Rename a table (auto-reversible)
349
+ rename_table :categories, :tags
350
+
351
+ # Change column type (NOT auto-reversible - use up/down)
352
+ def up
353
+ change_column :projects, :description, :text
354
+ end
355
+ def down
356
+ change_column :projects, :description, :string
357
+ end
358
+ ```
359
+
360
+ ## Data Migrations
361
+
362
+ Data migrations should be **separate from schema migrations**. Never mix schema changes and data manipulation in the same migration.
363
+
364
+ ### Separate Data Migration
365
+
366
+ ```ruby
367
+ class BackfillProjectPriorities < ActiveRecord::Migration[7.1]
368
+ def up
369
+ Project.where(priority: nil).in_batches(of: 1000) do |batch|
370
+ batch.update_all(priority: "medium")
371
+ end
372
+ end
373
+
374
+ def down
375
+ # No-op or reverse if possible
376
+ end
377
+ end
378
+ ```
379
+
380
+ ### Rules for Data Migrations
381
+
382
+ 1. **Separate file** - Never in the same migration as schema changes
383
+ 2. **Batch processing** - Use `in_batches` or `find_each` for large tables
384
+ 3. **Idempotent** - Safe to run multiple times
385
+ 4. **No model dependency** - Use raw SQL or `update_all` to avoid model changes breaking old migrations
386
+ 5. **`up`/`down` methods** - Data migrations are rarely auto-reversible
387
+
388
+ ## Migration Best Practices
389
+
390
+ - Use `null: false` on required columns and set sensible defaults
391
+ - Add foreign key constraints and indexes for queried columns
392
+ - Keep migrations small and focused
393
+ - Never mix schema and data changes in one migration
394
+ - Never use model classes in migrations (they change over time)
395
+ - Stay database-agnostic (no PostgreSQL-specific features)
396
+ - Always use the 2-step process for removing columns
397
+
398
+ ## Testing Migrations
399
+
400
+ ### Test Reversibility
401
+
402
+ ```ruby
403
+ # test/db/migration_test.rb
404
+ require "test_helper"
405
+
406
+ class MigrationReversibilityTest < ActiveSupport::TestCase
407
+ test "all migrations are reversible" do
408
+ # Run all pending migrations forward
409
+ ActiveRecord::Migration.maintain_test_schema!
410
+
411
+ # This will raise if any migration can't be reversed
412
+ assert_nothing_raised do
413
+ ActiveRecord::Migrator.new(:down, migrations, schema_migration, internal_metadata).migrate
414
+ ActiveRecord::Migrator.new(:up, migrations, schema_migration, internal_metadata).migrate
415
+ end
416
+ end
417
+
418
+ private
419
+
420
+ def migrations
421
+ ActiveRecord::MigrationContext.new(migration_paths).migrations
422
+ end
423
+
424
+ def migration_paths
425
+ ActiveRecord::Migrator.migrations_paths
426
+ end
427
+
428
+ def schema_migration
429
+ ActiveRecord::Base.connection.schema_migration
430
+ end
431
+
432
+ def internal_metadata
433
+ ActiveRecord::Base.connection.internal_metadata
434
+ end
435
+ end
436
+ ```
437
+
438
+ ## Common Column Types
439
+
440
+ | Type | Use For | Example |
441
+ |------|---------|---------|
442
+ | `string` | Short text (< 255 chars) | names, emails, statuses |
443
+ | `text` | Long text | descriptions, notes, content |
444
+ | `integer` | Whole numbers | counts, positions, ages |
445
+ | `decimal` | Money/precision numbers | `precision: 10, scale: 2` |
446
+ | `boolean` | True/false flags | `email_verified`, `admin` |
447
+ | `date` | Dates without time | `due_date`, `birth_date` |
448
+ | `datetime` | Timestamps | `published_at`, `expires_at` |
449
+ | `references` | Foreign keys | `t.references :user` |