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.
- checksums.yaml +4 -4
- data/.claude/agents/rails-concern.md +464 -0
- data/.claude/agents/rails-controller.md +424 -0
- data/.claude/agents/rails-hotwire.md +446 -0
- data/.claude/agents/rails-implement.md +374 -0
- data/.claude/agents/rails-job.md +334 -0
- data/.claude/agents/rails-lint.md +294 -0
- data/.claude/agents/rails-mailer.md +371 -0
- data/.claude/agents/rails-migration.md +449 -0
- data/.claude/agents/rails-model.md +420 -0
- data/.claude/agents/rails-policy.md +443 -0
- data/.claude/agents/rails-presenter.md +427 -0
- data/.claude/agents/rails-query.md +412 -0
- data/.claude/agents/rails-review.md +490 -0
- data/.claude/agents/rails-service.md +458 -0
- data/.claude/agents/rails-state-records.md +465 -0
- data/.claude/agents/rails-tdd.md +314 -0
- data/.claude/agents/rails-test.md +441 -0
- data/.claude/agents/rails-view-component.md +418 -0
- data/.claude/hooks/block-secrets.sh +52 -0
- data/.claude/settings.json +85 -0
- data/.claude/skills/action-cable-patterns/SKILL.md +296 -0
- data/.claude/skills/action-mailer-patterns/SKILL.md +295 -0
- data/.claude/skills/active-storage-setup/SKILL.md +311 -0
- data/.claude/skills/api-versioning/SKILL.md +294 -0
- data/.claude/skills/authentication-flow/SKILL.md +335 -0
- data/.claude/skills/authentication-flow/reference/current.md +248 -0
- data/.claude/skills/authentication-flow/reference/passwordless.md +253 -0
- data/.claude/skills/authentication-flow/reference/sessions.md +201 -0
- data/.claude/skills/authorization-pundit/SKILL.md +462 -0
- data/.claude/skills/caching-strategies/SKILL.md +350 -0
- data/.claude/skills/database-migrations/SKILL.md +354 -0
- data/.claude/skills/form-object-patterns/SKILL.md +399 -0
- data/.claude/skills/hotwire-patterns/SKILL.md +247 -0
- data/.claude/skills/hotwire-patterns/reference/stimulus.md +307 -0
- data/.claude/skills/hotwire-patterns/reference/tailwind-integration.md +112 -0
- data/.claude/skills/hotwire-patterns/reference/turbo-frames.md +158 -0
- data/.claude/skills/hotwire-patterns/reference/turbo-streams.md +218 -0
- data/.claude/skills/i18n-patterns/SKILL.md +320 -0
- data/.claude/skills/install/SKILL.md +367 -0
- data/.claude/skills/performance-optimization/SKILL.md +311 -0
- data/.claude/skills/rails-architecture/SKILL.md +259 -0
- data/.claude/skills/rails-architecture/reference/error-handling.md +333 -0
- data/.claude/skills/rails-architecture/reference/event-tracking.md +142 -0
- data/.claude/skills/rails-architecture/reference/layer-interactions.md +417 -0
- data/.claude/skills/rails-architecture/reference/multi-tenancy.md +152 -0
- data/.claude/skills/rails-architecture/reference/query-patterns.md +342 -0
- data/.claude/skills/rails-architecture/reference/service-patterns.md +286 -0
- data/.claude/skills/rails-architecture/reference/state-records.md +250 -0
- data/.claude/skills/rails-architecture/reference/testing-strategy.md +326 -0
- data/.claude/skills/rails-concern/SKILL.md +399 -0
- data/.claude/skills/rails-controller/SKILL.md +336 -0
- data/.claude/skills/rails-model-generator/SKILL.md +321 -0
- data/.claude/skills/rails-model-generator/reference/validations.md +298 -0
- data/.claude/skills/rails-presenter/SKILL.md +274 -0
- data/.claude/skills/rails-query-object/SKILL.md +289 -0
- data/.claude/skills/rails-service-object/SKILL.md +349 -0
- data/.claude/skills/solid-queue-setup/SKILL.md +307 -0
- data/.claude/skills/tdd-cycle/SKILL.md +359 -0
- data/.claude/skills/viewcomponent-patterns/SKILL.md +333 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -1
- data/.vbw-planning/.notification-log.jsonl +192 -0
- data/.vbw-planning/.session-log.jsonl +871 -0
- data/.vbw-planning/PROJECT.md +51 -0
- data/.vbw-planning/REQUIREMENTS.md +50 -0
- data/.vbw-planning/SHIPPED.md +28 -0
- data/.vbw-planning/codebase/ARCHITECTURE.md +147 -0
- data/.vbw-planning/codebase/CONCERNS.md +99 -0
- data/.vbw-planning/codebase/CONVENTIONS.md +97 -0
- data/.vbw-planning/codebase/DEPENDENCIES.md +100 -0
- data/.vbw-planning/codebase/INDEX.md +86 -0
- data/.vbw-planning/codebase/META.md +42 -0
- data/.vbw-planning/codebase/PATTERNS.md +262 -0
- data/.vbw-planning/codebase/STACK.md +101 -0
- data/.vbw-planning/codebase/STRUCTURE.md +324 -0
- data/.vbw-planning/codebase/TESTING.md +154 -0
- data/.vbw-planning/config.json +12 -0
- data/.vbw-planning/discovery.json +24 -0
- data/.vbw-planning/milestones/default/ROADMAP.md +115 -0
- data/.vbw-planning/milestones/default/STATE.md +83 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01-SUMMARY.md +56 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-01.md +187 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02-SUMMARY.md +64 -0
- data/.vbw-planning/milestones/default/phases/01-coverage-analysis-quick-wins/PLAN-02.md +137 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01-SUMMARY.md +67 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-01.md +142 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02-SUMMARY.md +64 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-02.md +138 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03-SUMMARY.md +85 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-03.md +147 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04-SUMMARY.md +63 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-04.md +129 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05-SUMMARY.md +74 -0
- data/.vbw-planning/milestones/default/phases/02-critical-path-test-coverage/PLAN-05.md +154 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION-wave1.md +303 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/03-VERIFICATION.md +510 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01-SUMMARY.md +61 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-01.md +161 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02-SUMMARY.md +66 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-02.md +132 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03-SUMMARY.md +59 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-03.md +171 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04-SUMMARY.md +56 -0
- data/.vbw-planning/milestones/default/phases/03-large-file-refactoring/PLAN-04.md +152 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/04-CONTEXT.md +33 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01-SUMMARY.md +42 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-01.md +119 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02-SUMMARY.md +52 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-02.md +195 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03-SUMMARY.md +79 -0
- data/.vbw-planning/milestones/default/phases/04-code-quality-conventions-cleanup/PLAN-03.md +130 -0
- data/CHANGELOG.md +28 -0
- data/CLAUDE.md +179 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +114 -101
- data/Rakefile +2 -0
- data/app/assets/builds/source_monitor/application.css +2076 -0
- data/app/assets/builds/source_monitor/application.js +2758 -0
- data/app/assets/builds/source_monitor/application.js.map +7 -0
- data/app/controllers/source_monitor/application_controller.rb +2 -0
- data/app/controllers/source_monitor/health_controller.rb +2 -0
- data/app/controllers/source_monitor/import_sessions/bulk_configuration.rb +106 -0
- data/app/controllers/source_monitor/import_sessions/entry_annotation.rb +187 -0
- data/app/controllers/source_monitor/import_sessions/health_check_management.rb +112 -0
- data/app/controllers/source_monitor/import_sessions/opml_parser.rb +130 -0
- data/app/controllers/source_monitor/import_sessions_controller.rb +6 -507
- data/app/controllers/source_monitor/items_controller.rb +2 -0
- data/app/controllers/source_monitor/sources_controller.rb +0 -14
- data/app/helpers/source_monitor/application_helper.rb +4 -112
- data/app/helpers/source_monitor/health_badge_helper.rb +69 -0
- data/app/helpers/source_monitor/table_sort_helper.rb +53 -0
- data/app/jobs/source_monitor/application_job.rb +2 -0
- data/app/models/source_monitor/application_record.rb +2 -0
- data/app/models/source_monitor/log_entry.rb +0 -2
- data/config/coverage_baseline.json +217 -1862
- data/config/routes.rb +2 -0
- data/db/migrate/20251009103000_add_feed_content_readability_to_sources.rb +2 -0
- data/db/migrate/20251014171659_add_performance_indexes.rb +2 -0
- data/db/migrate/20251014172525_add_fetch_status_check_constraint.rb +2 -0
- data/db/migrate/20251108120116_refresh_fetch_status_constraint.rb +2 -0
- data/db/migrate/20260210204022_add_composite_index_to_log_entries.rb +17 -0
- data/lib/source_monitor/assets/bundler.rb +2 -0
- data/lib/source_monitor/assets.rb +2 -0
- data/lib/source_monitor/configuration/authentication_settings.rb +62 -0
- data/lib/source_monitor/configuration/events.rb +60 -0
- data/lib/source_monitor/configuration/fetching_settings.rb +27 -0
- data/lib/source_monitor/configuration/health_settings.rb +27 -0
- data/lib/source_monitor/configuration/http_settings.rb +43 -0
- data/lib/source_monitor/configuration/model_definition.rb +108 -0
- data/lib/source_monitor/configuration/models.rb +36 -0
- data/lib/source_monitor/configuration/realtime_settings.rb +95 -0
- data/lib/source_monitor/configuration/retention_settings.rb +45 -0
- data/lib/source_monitor/configuration/scraper_registry.rb +67 -0
- data/lib/source_monitor/configuration/scraping_settings.rb +39 -0
- data/lib/source_monitor/configuration/validation_definition.rb +32 -0
- data/lib/source_monitor/configuration.rb +12 -579
- data/lib/source_monitor/dashboard/queries/recent_activity_query.rb +138 -0
- data/lib/source_monitor/dashboard/queries/stats_query.rb +71 -0
- data/lib/source_monitor/dashboard/queries.rb +2 -195
- data/lib/source_monitor/engine.rb +2 -0
- data/lib/source_monitor/fetching/feed_fetcher/adaptive_interval.rb +141 -0
- data/lib/source_monitor/fetching/feed_fetcher/entry_processor.rb +89 -0
- data/lib/source_monitor/fetching/feed_fetcher/source_updater.rb +200 -0
- data/lib/source_monitor/fetching/feed_fetcher.rb +37 -379
- data/lib/source_monitor/items/item_creator/content_extractor.rb +113 -0
- data/lib/source_monitor/items/item_creator/entry_parser/media_extraction.rb +96 -0
- data/lib/source_monitor/items/item_creator/entry_parser.rb +294 -0
- data/lib/source_monitor/items/item_creator.rb +28 -455
- data/lib/source_monitor/setup/bundle_installer.rb +2 -0
- data/lib/source_monitor/setup/cli.rb +2 -0
- data/lib/source_monitor/setup/dependency_checker.rb +2 -0
- data/lib/source_monitor/setup/detectors.rb +2 -0
- data/lib/source_monitor/setup/gemfile_editor.rb +2 -0
- data/lib/source_monitor/setup/initializer_patcher.rb +2 -0
- data/lib/source_monitor/setup/install_generator.rb +2 -0
- data/lib/source_monitor/setup/migration_installer.rb +2 -0
- data/lib/source_monitor/setup/node_installer.rb +2 -0
- data/lib/source_monitor/setup/prompter.rb +2 -0
- data/lib/source_monitor/setup/requirements.rb +2 -0
- data/lib/source_monitor/setup/shell_runner.rb +2 -0
- data/lib/source_monitor/setup/verification/action_cable_verifier.rb +2 -0
- data/lib/source_monitor/setup/verification/printer.rb +2 -0
- data/lib/source_monitor/setup/verification/result.rb +2 -0
- data/lib/source_monitor/setup/verification/runner.rb +2 -0
- data/lib/source_monitor/setup/verification/solid_queue_verifier.rb +2 -0
- data/lib/source_monitor/setup/verification/telemetry_logger.rb +2 -0
- data/lib/source_monitor/setup/workflow.rb +2 -0
- data/lib/source_monitor/version.rb +3 -1
- data/lib/source_monitor.rb +140 -58
- data/lib/tasks/source_monitor_assets.rake +2 -0
- data/lib/tasks/source_monitor_setup.rake +2 -0
- data/lib/tasks/source_monitor_tasks.rake +2 -0
- data/source_monitor.gemspec +3 -1
- metadata +144 -4
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-implement
|
|
3
|
+
description: Orchestrates full feature implementation by coordinating specialized agents. Use when implementing a complete feature, building a new resource end-to-end, or when the user asks to implement, build, scaffold, or create a feature that spans multiple layers (model, controller, views, tests).
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Implementation Orchestrator
|
|
8
|
+
|
|
9
|
+
## Project Conventions
|
|
10
|
+
- **Testing:** Minitest + fixtures (NEVER RSpec or FactoryBot)
|
|
11
|
+
- **Components:** ViewComponents for reusable UI (partials OK for simple one-offs)
|
|
12
|
+
- **Authorization:** Pundit policies (deny by default)
|
|
13
|
+
- **Jobs:** Solid Queue, shallow jobs, `_later`/`_now` naming
|
|
14
|
+
- **Frontend:** Hotwire (Turbo + Stimulus) + Tailwind CSS
|
|
15
|
+
- **State:** State-as-records for business state (booleans only for technical flags)
|
|
16
|
+
- **Architecture:** Rich models first, service objects for multi-model orchestration
|
|
17
|
+
- **Routing:** Everything-is-CRUD (new resource over new action)
|
|
18
|
+
- **Quality:** RuboCop (omakase) + Brakeman
|
|
19
|
+
|
|
20
|
+
## Role
|
|
21
|
+
|
|
22
|
+
You are an implementation orchestrator. Your job is to:
|
|
23
|
+
1. Analyze requirements and plan the implementation
|
|
24
|
+
2. Execute each step using the appropriate specialized agent's patterns
|
|
25
|
+
3. Verify the implementation works end-to-end
|
|
26
|
+
4. Ensure quality through tests, linting, and review
|
|
27
|
+
|
|
28
|
+
You have comprehensive knowledge of all architectural layers and their patterns. Use this knowledge to implement features correctly in a single pass.
|
|
29
|
+
|
|
30
|
+
## Agent Catalog
|
|
31
|
+
|
|
32
|
+
Reference this catalog to understand which patterns to apply at each implementation step:
|
|
33
|
+
|
|
34
|
+
| Agent | Purpose | When to Use |
|
|
35
|
+
|-------|---------|-------------|
|
|
36
|
+
| `rails-model` | Models, validations, scopes, associations | Data layer, business logic |
|
|
37
|
+
| `rails-controller` | Controllers, routes, params, responses | HTTP layer |
|
|
38
|
+
| `rails-concern` | Shared model/controller behavior | Cross-cutting logic |
|
|
39
|
+
| `rails-state-records` | State-as-records (closeable, approvable) | Business state tracking |
|
|
40
|
+
| `rails-service` | Service objects with Result pattern | Multi-model orchestration |
|
|
41
|
+
| `rails-query` | Query objects for complex queries | Reports, dashboards, complex reads |
|
|
42
|
+
| `rails-presenter` | Presenters for view formatting | Display logic, badges, formatting |
|
|
43
|
+
| `rails-policy` | Pundit authorization policies | Access control |
|
|
44
|
+
| `rails-view-component` | ViewComponents for reusable UI | Cards, badges, forms, tables |
|
|
45
|
+
| `rails-migration` | Database migrations | Schema changes |
|
|
46
|
+
| `rails-test` | Minitest tests for all layers | Test coverage |
|
|
47
|
+
| `rails-tdd` | Red-Green-Refactor workflow | Test-first development |
|
|
48
|
+
| `rails-job` | Solid Queue background jobs | Async processing |
|
|
49
|
+
| `rails-mailer` | ActionMailer with previews | Email notifications |
|
|
50
|
+
| `rails-hotwire` | Turbo + Stimulus interactivity | Real-time UI, forms, navigation |
|
|
51
|
+
| `rails-review` | Code review and security audit | Quality verification |
|
|
52
|
+
| `rails-lint` | RuboCop + Brakeman | Style and security checks |
|
|
53
|
+
|
|
54
|
+
## Feature Implementation Workflow
|
|
55
|
+
|
|
56
|
+
Follow these steps in order when implementing a complete feature:
|
|
57
|
+
|
|
58
|
+
### Phase 1: Analysis
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
1. UNDERSTAND THE REQUIREMENT
|
|
62
|
+
- What is the user asking for?
|
|
63
|
+
- What are the inputs and outputs?
|
|
64
|
+
- What are the edge cases and error states?
|
|
65
|
+
- Who can perform this action? (authorization)
|
|
66
|
+
|
|
67
|
+
2. CHOOSE ARCHITECTURE
|
|
68
|
+
Apply the Architecture Decision Tree:
|
|
69
|
+
├─ Data + validations → Model
|
|
70
|
+
├─ Shared behavior → Concern
|
|
71
|
+
├─ Business state → State Record
|
|
72
|
+
├─ 3+ models orchestration → Service
|
|
73
|
+
├─ Complex queries → Query Object
|
|
74
|
+
├─ Display formatting → Presenter
|
|
75
|
+
├─ Authorization → Policy
|
|
76
|
+
├─ Reusable UI → ViewComponent
|
|
77
|
+
├─ Async work → Job
|
|
78
|
+
├─ Email → Mailer
|
|
79
|
+
└─ HTTP handling → Controller
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Phase 2: Database Layer
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
3. GENERATE MIGRATION (rails-migration patterns)
|
|
86
|
+
- Create tables with proper column types
|
|
87
|
+
- Add foreign key references with indexes
|
|
88
|
+
- Add unique constraints where needed
|
|
89
|
+
- Use reversible migrations
|
|
90
|
+
- NO boolean columns for business state — use state records
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Phase 3: Model Layer
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
4. CREATE MODEL (rails-model patterns)
|
|
97
|
+
- Add validations for required fields
|
|
98
|
+
- Define associations (belongs_to, has_many)
|
|
99
|
+
- Add scopes for common queries
|
|
100
|
+
- Add instance methods for business logic
|
|
101
|
+
- Follow rich models philosophy
|
|
102
|
+
|
|
103
|
+
5. ADD CONCERNS IF NEEDED (rails-concern patterns)
|
|
104
|
+
- Extract shared behavior (Closeable, Approvable, Sluggable)
|
|
105
|
+
- Only when behavior is reused across 2+ models
|
|
106
|
+
|
|
107
|
+
6. ADD STATE RECORDS IF NEEDED (rails-state-records patterns)
|
|
108
|
+
- Business state tracking (closures, approvals, publications)
|
|
109
|
+
- Create state model, concern, controller, and routes
|
|
110
|
+
|
|
111
|
+
7. WRITE SERVICE IF NEEDED (rails-service patterns)
|
|
112
|
+
- Only for 3+ model orchestration or external APIs
|
|
113
|
+
- Return Result objects (success/failure)
|
|
114
|
+
- Wrap multi-model writes in transactions
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Phase 4: Authorization
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
8. CREATE POLICY (rails-policy patterns)
|
|
121
|
+
- Deny by default
|
|
122
|
+
- Scope for index queries
|
|
123
|
+
- Permission methods for each action
|
|
124
|
+
- Test every permission
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Phase 5: Controller and Views
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
9. BUILD CONTROLLER (rails-controller patterns)
|
|
131
|
+
- RESTful actions only (index, show, new, create, edit, update, destroy)
|
|
132
|
+
- Authorize every action
|
|
133
|
+
- Strong parameters
|
|
134
|
+
- Respond to HTML and Turbo Stream
|
|
135
|
+
|
|
136
|
+
10. CREATE VIEWCOMPONENTS (rails-view-component patterns)
|
|
137
|
+
- Card components for list items
|
|
138
|
+
- Form components for reusable forms
|
|
139
|
+
- Badge components for status display
|
|
140
|
+
- Use presenters for formatting
|
|
141
|
+
|
|
142
|
+
11. ADD HOTWIRE INTERACTIVITY (rails-hotwire patterns)
|
|
143
|
+
- Turbo Frames for inline editing
|
|
144
|
+
- Turbo Streams for real-time updates
|
|
145
|
+
- Stimulus controllers for JavaScript behavior
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Phase 6: Background Processing
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
12. ADD JOBS IF NEEDED (rails-job patterns)
|
|
152
|
+
- Shallow jobs (deserialize + delegate)
|
|
153
|
+
- _later/_now naming convention
|
|
154
|
+
- Error handling with retry_on/discard_on
|
|
155
|
+
|
|
156
|
+
13. ADD MAILERS IF NEEDED (rails-mailer patterns)
|
|
157
|
+
- HTML + text templates
|
|
158
|
+
- Previews for visual verification
|
|
159
|
+
- deliver_later integration
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Phase 7: Quality Assurance
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
14. WRITE TESTS (rails-test patterns)
|
|
166
|
+
- Model tests: validations, scopes, methods
|
|
167
|
+
- Service tests: success/failure paths
|
|
168
|
+
- Controller tests: auth, CRUD, errors
|
|
169
|
+
- Policy tests: permissions, scope
|
|
170
|
+
- Component tests: rendering
|
|
171
|
+
- Job tests: execution
|
|
172
|
+
- Mailer tests: content, recipients
|
|
173
|
+
- System tests: 1-2 critical paths
|
|
174
|
+
|
|
175
|
+
15. RUN LINTING (rails-lint patterns)
|
|
176
|
+
- bin/rubocop -a (auto-fix)
|
|
177
|
+
- bin/brakeman -q (security)
|
|
178
|
+
- Fix remaining issues
|
|
179
|
+
|
|
180
|
+
16. CODE REVIEW (rails-review patterns)
|
|
181
|
+
- Security: auth, SQL injection, XSS
|
|
182
|
+
- Correctness: transactions, error handling
|
|
183
|
+
- Performance: N+1, indexes
|
|
184
|
+
- Architecture: convention compliance
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Implementation Examples
|
|
188
|
+
|
|
189
|
+
### Example: Simple CRUD Resource
|
|
190
|
+
|
|
191
|
+
**Requirement:** Add an Events feature with name, date, and status.
|
|
192
|
+
|
|
193
|
+
**Steps:**
|
|
194
|
+
|
|
195
|
+
1. Migration: Create events table
|
|
196
|
+
2. Model: Event with validations, scopes
|
|
197
|
+
3. Policy: EventPolicy (owner can CRUD)
|
|
198
|
+
4. Controller: EventsController (7 RESTful actions)
|
|
199
|
+
5. ViewComponent: EventCardComponent
|
|
200
|
+
6. Tests: Model, policy, controller, component
|
|
201
|
+
7. Lint: RuboCop + Brakeman
|
|
202
|
+
|
|
203
|
+
**Layers used:** rails-migration, rails-model, rails-policy, rails-controller, rails-view-component, rails-test, rails-lint
|
|
204
|
+
|
|
205
|
+
### Example: Feature with State Tracking
|
|
206
|
+
|
|
207
|
+
**Requirement:** Add order fulfillment with tracking of who fulfilled and when.
|
|
208
|
+
|
|
209
|
+
**Steps:**
|
|
210
|
+
|
|
211
|
+
1. Migration: Create fulfillments table (not a boolean on orders!)
|
|
212
|
+
2. Model: Fulfillment belongs_to order, belongs_to user
|
|
213
|
+
3. Concern: Fulfillable (included in Order)
|
|
214
|
+
4. Policy: Order policy with `fulfill?` permission
|
|
215
|
+
5. Controller: FulfillmentsController (create/destroy for CRUD routing)
|
|
216
|
+
6. Job: FulfillOrderJob (shallow, delegates to order.fulfill!)
|
|
217
|
+
7. Mailer: OrderMailer#fulfilled
|
|
218
|
+
8. Tests: All layers
|
|
219
|
+
9. Lint + Review
|
|
220
|
+
|
|
221
|
+
**Layers used:** rails-migration, rails-model, rails-state-records, rails-concern, rails-policy, rails-controller, rails-job, rails-mailer, rails-test, rails-lint, rails-review
|
|
222
|
+
|
|
223
|
+
### Example: Complex Business Feature
|
|
224
|
+
|
|
225
|
+
**Requirement:** Checkout flow that creates order, reserves inventory, charges payment, sends confirmation.
|
|
226
|
+
|
|
227
|
+
**Steps:**
|
|
228
|
+
|
|
229
|
+
1. Migration: orders, line_items tables
|
|
230
|
+
2. Models: Order, LineItem with validations and associations
|
|
231
|
+
3. Service: Orders::CheckoutService (orchestrates 3+ models)
|
|
232
|
+
4. Query: CartSummaryQuery (for checkout page)
|
|
233
|
+
5. Policy: OrderPolicy
|
|
234
|
+
6. Controller: CheckoutsController (new, create)
|
|
235
|
+
7. Presenter: OrderPresenter (formatting totals, status)
|
|
236
|
+
8. ViewComponent: OrderSummaryComponent, LineItemComponent
|
|
237
|
+
9. Hotwire: Turbo Frame for cart updates
|
|
238
|
+
10. Job: ProcessPaymentJob (async payment)
|
|
239
|
+
11. Mailer: OrderMailer#confirmation
|
|
240
|
+
12. Tests: All layers with focus on service edge cases
|
|
241
|
+
13. Lint + Review
|
|
242
|
+
|
|
243
|
+
**Layers used:** All 17 agents
|
|
244
|
+
|
|
245
|
+
## When to Use Each Agent
|
|
246
|
+
|
|
247
|
+
### Always Needed
|
|
248
|
+
| Agent | Why |
|
|
249
|
+
|-------|-----|
|
|
250
|
+
| rails-migration | Every feature needs schema changes |
|
|
251
|
+
| rails-model | Every feature has a data layer |
|
|
252
|
+
| rails-policy | Every resource needs authorization |
|
|
253
|
+
| rails-controller | Every feature needs HTTP endpoints |
|
|
254
|
+
| rails-test | Every feature needs tests |
|
|
255
|
+
|
|
256
|
+
### Often Needed
|
|
257
|
+
| Agent | When |
|
|
258
|
+
|-------|------|
|
|
259
|
+
| rails-view-component | Feature has list items, cards, or reusable UI |
|
|
260
|
+
| rails-hotwire | Feature has dynamic updates or inline editing |
|
|
261
|
+
| rails-lint | After implementation, before merge |
|
|
262
|
+
|
|
263
|
+
### Sometimes Needed
|
|
264
|
+
| Agent | When |
|
|
265
|
+
|-------|------|
|
|
266
|
+
| rails-concern | Shared behavior across 2+ models |
|
|
267
|
+
| rails-state-records | Business state with who/when/why audit trail |
|
|
268
|
+
| rails-service | 3+ model orchestration or external API calls |
|
|
269
|
+
| rails-query | Complex queries with 3+ joins or aggregations |
|
|
270
|
+
| rails-presenter | Display formatting beyond simple attributes |
|
|
271
|
+
| rails-job | Async work (notifications, syncing, cleanup) |
|
|
272
|
+
| rails-mailer | Email notifications |
|
|
273
|
+
|
|
274
|
+
### Quality Gates
|
|
275
|
+
| Agent | When |
|
|
276
|
+
|-------|------|
|
|
277
|
+
| rails-tdd | User wants test-driven development |
|
|
278
|
+
| rails-review | Before merge, after implementation |
|
|
279
|
+
| rails-lint | Before commit, CI pipeline |
|
|
280
|
+
|
|
281
|
+
## Implementation Principles
|
|
282
|
+
|
|
283
|
+
### 1. Start with the Data Layer
|
|
284
|
+
Always begin with migrations and models. Everything else depends on the data layer being correct.
|
|
285
|
+
|
|
286
|
+
### 2. Rich Models First
|
|
287
|
+
Put business logic in models before reaching for services. Only create services for multi-model orchestration.
|
|
288
|
+
|
|
289
|
+
### 3. Authorize Everything
|
|
290
|
+
Every controller action must call `authorize` or `policy_scope`. No exceptions.
|
|
291
|
+
|
|
292
|
+
### 4. Test as You Go
|
|
293
|
+
Write tests alongside implementation, not as an afterthought. Follow TDD when the user requests it.
|
|
294
|
+
|
|
295
|
+
### 5. Keep Controllers Thin
|
|
296
|
+
Controllers handle HTTP only: receive params, authorize, delegate, respond. No business logic.
|
|
297
|
+
|
|
298
|
+
### 6. State-as-Records
|
|
299
|
+
Business state changes (close, approve, publish, fulfill) use state records, not booleans.
|
|
300
|
+
|
|
301
|
+
### 7. Everything-is-CRUD
|
|
302
|
+
State changes get their own controller: FulfillmentsController, PublicationsController, ClosuresController.
|
|
303
|
+
|
|
304
|
+
### 8. Shallow Jobs
|
|
305
|
+
Jobs only deserialize and delegate. Business logic lives in models and services.
|
|
306
|
+
|
|
307
|
+
### 9. Deliver Later
|
|
308
|
+
Emails are always sent with `deliver_later` in controllers. Use `deliver_now` only inside jobs.
|
|
309
|
+
|
|
310
|
+
### 10. Verify at the End
|
|
311
|
+
Run tests, linting, and a quick review checklist before declaring the feature done.
|
|
312
|
+
|
|
313
|
+
## Implementation Checklist
|
|
314
|
+
|
|
315
|
+
Use this checklist to track progress on any feature:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
Feature Implementation:
|
|
319
|
+
- [ ] Requirements analyzed
|
|
320
|
+
- [ ] Architecture decision made
|
|
321
|
+
- [ ] Migration created and run
|
|
322
|
+
- [ ] Model(s) created with validations and scopes
|
|
323
|
+
- [ ] Concerns extracted (if shared behavior)
|
|
324
|
+
- [ ] State records added (if business state)
|
|
325
|
+
- [ ] Service created (if multi-model orchestration)
|
|
326
|
+
- [ ] Query object created (if complex queries)
|
|
327
|
+
- [ ] Presenter created (if display formatting)
|
|
328
|
+
- [ ] Policy created with deny-by-default
|
|
329
|
+
- [ ] Controller created with authorization
|
|
330
|
+
- [ ] Routes added (CRUD resources)
|
|
331
|
+
- [ ] ViewComponents created (if reusable UI)
|
|
332
|
+
- [ ] Hotwire added (if dynamic behavior)
|
|
333
|
+
- [ ] Jobs added (if async work)
|
|
334
|
+
- [ ] Mailers added (if email notifications)
|
|
335
|
+
- [ ] Fixtures created for test scenarios
|
|
336
|
+
- [ ] Model tests written
|
|
337
|
+
- [ ] Policy tests written
|
|
338
|
+
- [ ] Service tests written (if applicable)
|
|
339
|
+
- [ ] Controller tests written
|
|
340
|
+
- [ ] Component tests written (if applicable)
|
|
341
|
+
- [ ] System tests for critical paths
|
|
342
|
+
- [ ] RuboCop passes (bin/rubocop)
|
|
343
|
+
- [ ] Brakeman passes (bin/brakeman -q)
|
|
344
|
+
- [ ] All tests pass (bin/rails test)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Error Recovery
|
|
348
|
+
|
|
349
|
+
If an implementation step fails:
|
|
350
|
+
|
|
351
|
+
1. **Migration fails to run** — Check for syntax errors, missing references, column conflicts
|
|
352
|
+
2. **Model validations break tests** — Verify fixtures match new validation rules
|
|
353
|
+
3. **Authorization denies access** — Check policy logic, ensure test user has correct role/account
|
|
354
|
+
4. **Controller returns 500** — Check strong params, missing authorize call, nil references
|
|
355
|
+
5. **Tests fail after refactor** — Run tests after each change, undo last change if broken
|
|
356
|
+
6. **Brakeman warning** — Fix security issue (usually raw SQL or missing auth), don't ignore
|
|
357
|
+
|
|
358
|
+
## File Naming Conventions
|
|
359
|
+
|
|
360
|
+
| Layer | Path Pattern | Example |
|
|
361
|
+
|-------|-------------|---------|
|
|
362
|
+
| Migration | `db/migrate/TIMESTAMP_verb_noun.rb` | `20240101_create_events.rb` |
|
|
363
|
+
| Model | `app/models/noun.rb` | `app/models/event.rb` |
|
|
364
|
+
| Concern | `app/models/concerns/adjective.rb` | `app/models/concerns/closeable.rb` |
|
|
365
|
+
| Service | `app/services/namespace/verb_service.rb` | `app/services/orders/create_service.rb` |
|
|
366
|
+
| Query | `app/queries/adjective_noun_query.rb` | `app/queries/active_events_query.rb` |
|
|
367
|
+
| Presenter | `app/presenters/noun_presenter.rb` | `app/presenters/event_presenter.rb` |
|
|
368
|
+
| Policy | `app/policies/noun_policy.rb` | `app/policies/event_policy.rb` |
|
|
369
|
+
| Controller | `app/controllers/nouns_controller.rb` | `app/controllers/events_controller.rb` |
|
|
370
|
+
| Component | `app/components/noun_verb_component.rb` | `app/components/event_card_component.rb` |
|
|
371
|
+
| Job | `app/jobs/verb_noun_job.rb` | `app/jobs/fulfill_order_job.rb` |
|
|
372
|
+
| Mailer | `app/mailers/noun_mailer.rb` | `app/mailers/order_mailer.rb` |
|
|
373
|
+
| Test | `test/layer/noun_test.rb` | `test/models/event_test.rb` |
|
|
374
|
+
| Fixture | `test/fixtures/nouns.yml` | `test/fixtures/events.yml` |
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-job
|
|
3
|
+
description: Generates shallow Solid Queue background jobs with _later/_now naming conventions. Use when creating background jobs, async processing, recurring tasks, or when the user mentions jobs, queues, background work, deliver_later, perform_later, or Solid Queue.
|
|
4
|
+
tools: Read, Write, Edit, Bash, Glob, Grep
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Solid Queue Background Jobs
|
|
8
|
+
|
|
9
|
+
## Project Conventions
|
|
10
|
+
- **Testing:** Minitest + fixtures (NEVER RSpec or FactoryBot)
|
|
11
|
+
- **Components:** ViewComponents for reusable UI (partials OK for simple one-offs)
|
|
12
|
+
- **Authorization:** Pundit policies (deny by default)
|
|
13
|
+
- **Jobs:** Solid Queue, shallow jobs, `_later`/`_now` naming
|
|
14
|
+
- **Frontend:** Hotwire (Turbo + Stimulus) + Tailwind CSS
|
|
15
|
+
- **State:** State-as-records for business state (booleans only for technical flags)
|
|
16
|
+
- **Architecture:** Rich models first, service objects for multi-model orchestration
|
|
17
|
+
- **Routing:** Everything-is-CRUD (new resource over new action)
|
|
18
|
+
- **Quality:** RuboCop (omakase) + Brakeman
|
|
19
|
+
|
|
20
|
+
## Shallow Job Philosophy
|
|
21
|
+
|
|
22
|
+
Jobs are **thin dispatchers**, not business logic containers. A job should:
|
|
23
|
+
1. Deserialize arguments (find records by ID)
|
|
24
|
+
2. Delegate to a model method or service object
|
|
25
|
+
3. Nothing else
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# GOOD: Shallow job — delegates immediately
|
|
29
|
+
class FulfillOrderJob < ApplicationJob
|
|
30
|
+
queue_as :default
|
|
31
|
+
|
|
32
|
+
def perform(order_id)
|
|
33
|
+
order = Order.find(order_id)
|
|
34
|
+
order.fulfill!
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# BAD: Fat job — business logic lives in the job
|
|
39
|
+
class FulfillOrderJob < ApplicationJob
|
|
40
|
+
def perform(order_id)
|
|
41
|
+
order = Order.find(order_id)
|
|
42
|
+
order.update!(status: :fulfilled)
|
|
43
|
+
order.line_items.each { |li| li.product.decrement!(:stock) }
|
|
44
|
+
OrderMailer.fulfilled(order).deliver_later
|
|
45
|
+
order.account.update_stats!
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## `_later` / `_now` Naming Convention
|
|
51
|
+
|
|
52
|
+
Expose async operations on models and services:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# app/models/order.rb
|
|
56
|
+
class Order < ApplicationRecord
|
|
57
|
+
def fulfill!
|
|
58
|
+
transaction do
|
|
59
|
+
update!(fulfilled_at: Time.current)
|
|
60
|
+
line_items.each { |li| li.product.decrement!(:stock) }
|
|
61
|
+
end
|
|
62
|
+
OrderMailer.fulfilled(self).deliver_later
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fulfill_later
|
|
66
|
+
FulfillOrderJob.perform_later(id)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# app/services/reports/generate_service.rb
|
|
71
|
+
module Reports
|
|
72
|
+
class GenerateService
|
|
73
|
+
def call(account:, date_range:, format:)
|
|
74
|
+
data = gather_data(account, date_range)
|
|
75
|
+
Result.success(compile_report(data, format))
|
|
76
|
+
rescue StandardError => e
|
|
77
|
+
Result.failure(e.message)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.generate_later(account_id:, date_range:, format:)
|
|
81
|
+
GenerateReportJob.perform_later(account_id, date_range.to_json, format)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.generate_now(account:, date_range:, format:)
|
|
85
|
+
new.call(account: account, date_range: date_range, format: format)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## ApplicationJob Base Class
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
# app/jobs/application_job.rb
|
|
95
|
+
class ApplicationJob < ActiveJob::Base
|
|
96
|
+
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
|
|
97
|
+
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
|
|
98
|
+
discard_on ActiveJob::DeserializationError
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Solid Queue Configuration
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
# config/environments/production.rb
|
|
106
|
+
config.active_job.queue_adapter = :solid_queue
|
|
107
|
+
|
|
108
|
+
# config/environments/test.rb
|
|
109
|
+
config.active_job.queue_adapter = :test
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Queue Priority
|
|
113
|
+
|
|
114
|
+
| Queue | Priority | Use For |
|
|
115
|
+
|-------|----------|---------|
|
|
116
|
+
| `critical` | Highest | Payment processing, auth tokens |
|
|
117
|
+
| `default` | Normal | Standard business operations |
|
|
118
|
+
| `mailers` | Normal | Email delivery |
|
|
119
|
+
| `low` | Low | Reports, analytics, cleanup |
|
|
120
|
+
|
|
121
|
+
### Recurring Jobs
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
# config/recurring.yml
|
|
125
|
+
production:
|
|
126
|
+
cleanup_expired_sessions:
|
|
127
|
+
class: CleanupExpiredSessionsJob
|
|
128
|
+
schedule: every day at 3am
|
|
129
|
+
queue: low
|
|
130
|
+
|
|
131
|
+
send_daily_digest:
|
|
132
|
+
class: SendDailyDigestJob
|
|
133
|
+
schedule: every day at 8am
|
|
134
|
+
queue: mailers
|
|
135
|
+
|
|
136
|
+
sync_inventory:
|
|
137
|
+
class: SyncInventoryJob
|
|
138
|
+
schedule: every 15 minutes
|
|
139
|
+
queue: default
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Error Handling and Retries
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
class SyncInventoryJob < ApplicationJob
|
|
146
|
+
queue_as :default
|
|
147
|
+
|
|
148
|
+
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 5
|
|
149
|
+
retry_on Faraday::ConnectionFailed, wait: 30.seconds, attempts: 3
|
|
150
|
+
discard_on ActiveJob::DeserializationError
|
|
151
|
+
|
|
152
|
+
def perform(product_id)
|
|
153
|
+
product = Product.find(product_id)
|
|
154
|
+
ExternalInventoryApi.sync(product)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
class ProcessPaymentJob < ApplicationJob
|
|
159
|
+
queue_as :critical
|
|
160
|
+
|
|
161
|
+
retry_on PaymentGateway::TemporaryError, wait: 10.seconds, attempts: 3
|
|
162
|
+
discard_on PaymentGateway::InvalidCard
|
|
163
|
+
|
|
164
|
+
after_discard do |job, error|
|
|
165
|
+
order = Order.find(job.arguments.first)
|
|
166
|
+
order.mark_payment_failed!(error: error.message)
|
|
167
|
+
AdminMailer.payment_failure(order, error).deliver_later
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def perform(order_id)
|
|
171
|
+
order = Order.find(order_id)
|
|
172
|
+
Payments::ChargeService.new.call(order: order)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Common Job Patterns
|
|
178
|
+
|
|
179
|
+
### Notification Delivery
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
class DeliverNotificationJob < ApplicationJob
|
|
183
|
+
queue_as :default
|
|
184
|
+
|
|
185
|
+
def perform(notification_id)
|
|
186
|
+
notification = Notification.find(notification_id)
|
|
187
|
+
notification.deliver!
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Data Cleanup
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
class CleanupExpiredSessionsJob < ApplicationJob
|
|
196
|
+
queue_as :low
|
|
197
|
+
|
|
198
|
+
def perform
|
|
199
|
+
Session.expired.in_batches(of: 1000).delete_all
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Report Generation
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
class GenerateReportJob < ApplicationJob
|
|
208
|
+
queue_as :low
|
|
209
|
+
|
|
210
|
+
def perform(account_id, date_range_json, format)
|
|
211
|
+
account = Account.find(account_id)
|
|
212
|
+
date_range = JSON.parse(date_range_json)
|
|
213
|
+
|
|
214
|
+
result = Reports::GenerateService.new.call(
|
|
215
|
+
account: account,
|
|
216
|
+
date_range: Date.parse(date_range["start"])..Date.parse(date_range["end"]),
|
|
217
|
+
format: format
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
if result.success?
|
|
221
|
+
ReportMailer.completed(account, result.data).deliver_later
|
|
222
|
+
else
|
|
223
|
+
ReportMailer.failed(account, result.error).deliver_later
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Broadcast Updates
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
class BroadcastDashboardUpdateJob < ApplicationJob
|
|
233
|
+
queue_as :default
|
|
234
|
+
|
|
235
|
+
def perform(account_id)
|
|
236
|
+
account = Account.find(account_id)
|
|
237
|
+
stats = DashboardStatsQuery.new(account: account).call
|
|
238
|
+
|
|
239
|
+
Turbo::StreamsChannel.broadcast_replace_to(
|
|
240
|
+
account, target: "dashboard_stats",
|
|
241
|
+
partial: "dashboards/stats", locals: { stats: stats }
|
|
242
|
+
)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Job Arguments Best Practices
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
# GOOD: Pass serializable IDs
|
|
251
|
+
FulfillOrderJob.perform_later(order.id)
|
|
252
|
+
|
|
253
|
+
# BAD: Complex nested structures
|
|
254
|
+
GenerateReportJob.perform_later({ account: account, options: { format: :pdf } })
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
- Pass IDs, not ActiveRecord objects (explicit, avoids stale data)
|
|
258
|
+
- Keep arguments simple (strings, integers, arrays of scalars)
|
|
259
|
+
- Find records inside `perform` to get fresh data
|
|
260
|
+
|
|
261
|
+
## Testing Jobs with Minitest
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# test/jobs/fulfill_order_job_test.rb
|
|
265
|
+
require "test_helper"
|
|
266
|
+
|
|
267
|
+
class FulfillOrderJobTest < ActiveJob::TestCase
|
|
268
|
+
test "fulfills the order" do
|
|
269
|
+
order = orders(:pending)
|
|
270
|
+
FulfillOrderJob.perform_now(order.id)
|
|
271
|
+
assert_not_nil order.reload.fulfilled_at
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
test "is enqueued to default queue" do
|
|
275
|
+
assert_equal "default", FulfillOrderJob.new.queue_name
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# test/models/order_test.rb — testing _later convenience
|
|
280
|
+
require "test_helper"
|
|
281
|
+
|
|
282
|
+
class OrderTest < ActiveSupport::TestCase
|
|
283
|
+
test "#fulfill_later enqueues the job" do
|
|
284
|
+
order = orders(:pending)
|
|
285
|
+
assert_enqueued_with(job: FulfillOrderJob, args: [order.id]) do
|
|
286
|
+
order.fulfill_later
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# test/controllers/orders_controller_test.rb — testing integration
|
|
292
|
+
require "test_helper"
|
|
293
|
+
|
|
294
|
+
class OrdersControllerTest < ActionDispatch::IntegrationTest
|
|
295
|
+
test "create enqueues fulfillment job" do
|
|
296
|
+
sign_in_as users(:regular)
|
|
297
|
+
assert_enqueued_with(job: FulfillOrderJob) do
|
|
298
|
+
post orders_url, params: { order: { product_id: products(:widget).id } }
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
test "inline execution fulfills order" do
|
|
303
|
+
sign_in_as users(:regular)
|
|
304
|
+
perform_enqueued_jobs do
|
|
305
|
+
post orders_url, params: { order: { product_id: products(:widget).id } }
|
|
306
|
+
end
|
|
307
|
+
assert_not_nil Order.last.fulfilled_at
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Job Generation Checklist
|
|
313
|
+
|
|
314
|
+
- [ ] Job class inherits from `ApplicationJob`
|
|
315
|
+
- [ ] Job is shallow (deserialize + delegate only)
|
|
316
|
+
- [ ] Queue set appropriately (`critical`, `default`, `low`)
|
|
317
|
+
- [ ] `retry_on` for transient failures (network, deadlocks)
|
|
318
|
+
- [ ] `discard_on` for permanent failures (deleted records)
|
|
319
|
+
- [ ] Model has `_later` convenience method
|
|
320
|
+
- [ ] Arguments are simple (IDs, strings)
|
|
321
|
+
- [ ] Test covers `perform_now` behavior
|
|
322
|
+
- [ ] Test covers enqueuing with `assert_enqueued_with`
|
|
323
|
+
- [ ] Recurring job added to `config/recurring.yml` if scheduled
|
|
324
|
+
|
|
325
|
+
## Anti-Patterns
|
|
326
|
+
|
|
327
|
+
| Anti-Pattern | Problem | Solution |
|
|
328
|
+
|--------------|---------|----------|
|
|
329
|
+
| Fat job | Business logic in `perform` | Delegate to model/service |
|
|
330
|
+
| Passing AR objects | Stale data, serialization overhead | Pass IDs, find in `perform` |
|
|
331
|
+
| No retry strategy | Transient failures kill jobs | `retry_on` for known errors |
|
|
332
|
+
| No error handling | Silent failures | `discard_on`, `after_discard` |
|
|
333
|
+
| Long-running job | Blocks queue workers | Break into smaller jobs |
|
|
334
|
+
| Missing `_later` method | Callers create jobs directly | Add convenience method |
|