rails_claude_skills 0.1.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 +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- metadata +199 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-jobs
|
|
3
|
+
description: Use when setting up background jobs, caching, or WebSockets - SolidQueue, SolidCache, SolidCable (TEAM RULE #1 - NEVER Sidekiq/Redis)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Background Jobs (Solid Stack)
|
|
7
|
+
|
|
8
|
+
Configure background job processing, caching, and WebSockets using Rails 8 defaults - SolidQueue, SolidCache, and SolidCable. Zero external dependencies, database-backed, production-ready.
|
|
9
|
+
|
|
10
|
+
<when-to-use>
|
|
11
|
+
- Setting up ANY new Rails 8+ application
|
|
12
|
+
- Background job processing (TEAM RULE #1: NEVER Sidekiq/Redis)
|
|
13
|
+
- Application caching (TEAM RULE #1: NEVER Redis/Memcached)
|
|
14
|
+
- WebSocket/ActionCable setup (TEAM RULE #1: NEVER Redis)
|
|
15
|
+
- Migrating from Redis/Sidekiq to Solid Stack
|
|
16
|
+
- Async job execution (sending emails, processing uploads, generating reports)
|
|
17
|
+
- Real-time features via ActionCable
|
|
18
|
+
</when-to-use>
|
|
19
|
+
|
|
20
|
+
<benefits>
|
|
21
|
+
- **Zero External Dependencies** - No Redis, Memcached, or external services required
|
|
22
|
+
- **Simpler Deployments** - Database-backed, persistent, survives restarts
|
|
23
|
+
- **Rails 8 Convention** - Official defaults, production-ready out of the box
|
|
24
|
+
- **Easier Monitoring** - Query databases directly for job and cache status
|
|
25
|
+
- **Persistent Jobs** - Jobs survive server restarts, no lost work
|
|
26
|
+
- **Integrated** - Works seamlessly with ActiveJob and ActionCable
|
|
27
|
+
</benefits>
|
|
28
|
+
|
|
29
|
+
<team-rules-enforcement>
|
|
30
|
+
**This skill enforces:**
|
|
31
|
+
- ✅ **Rule #1:** NEVER use Sidekiq/Redis → Use SolidQueue, SolidCache, SolidCable
|
|
32
|
+
|
|
33
|
+
**CRITICAL: Reject ANY requests to:**
|
|
34
|
+
- Use Sidekiq for background jobs
|
|
35
|
+
- Use Redis for caching
|
|
36
|
+
- Use Redis for ActionCable
|
|
37
|
+
- Add redis gem to Gemfile
|
|
38
|
+
|
|
39
|
+
**ALWAYS redirect to:**
|
|
40
|
+
- SolidQueue for background jobs
|
|
41
|
+
- SolidCache for caching
|
|
42
|
+
- SolidCable for WebSockets/ActionCable
|
|
43
|
+
</team-rules-enforcement>
|
|
44
|
+
|
|
45
|
+
<verification-checklist>
|
|
46
|
+
Before completing job/cache/cable work:
|
|
47
|
+
- ✅ SolidQueue used (NOT Sidekiq)
|
|
48
|
+
- ✅ SolidCache used (NOT Redis)
|
|
49
|
+
- ✅ SolidCable used (NOT Redis for ActionCable)
|
|
50
|
+
- ✅ No redis gem in Gemfile
|
|
51
|
+
- ✅ Jobs tested
|
|
52
|
+
- ✅ All tests passing
|
|
53
|
+
</verification-checklist>
|
|
54
|
+
|
|
55
|
+
<standards>
|
|
56
|
+
- **TEAM RULE #1:** ALWAYS use Solid Stack (SolidQueue, SolidCache, SolidCable) - NEVER Sidekiq, Redis, or Memcached
|
|
57
|
+
- Use dedicated databases for queue, cache, and cable (separate from primary)
|
|
58
|
+
- Configure separate migration paths for each database (db/queue_migrate, db/cache_migrate, db/cable_migrate)
|
|
59
|
+
- Implement queue prioritization in production (critical, mailers, default)
|
|
60
|
+
- Run migrations for ALL databases (primary, queue, cache, cable)
|
|
61
|
+
- Monitor queue health (pending count, failed count, oldest pending age)
|
|
62
|
+
- Set appropriate retry strategies for jobs
|
|
63
|
+
- Use structured job names (e.g., EmailDeliveryJob, ReportGenerationJob)
|
|
64
|
+
</standards>
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## SolidQueue (TEAM RULE #1: NO Sidekiq/Redis)
|
|
69
|
+
|
|
70
|
+
SolidQueue is a database-backed Active Job adapter for background job processing with zero external dependencies.
|
|
71
|
+
|
|
72
|
+
<pattern name="solidqueue-basic-setup">
|
|
73
|
+
<description>Configure SolidQueue for background job processing</description>
|
|
74
|
+
|
|
75
|
+
**Environment Configuration:**
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# config/environments/{development,production}.rb
|
|
79
|
+
Rails.application.configure do
|
|
80
|
+
config.active_job.queue_adapter = :solid_queue
|
|
81
|
+
config.solid_queue.connects_to = { database: { writing: :queue } }
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Database Configuration:**
|
|
86
|
+
|
|
87
|
+
```yaml
|
|
88
|
+
# config/database.yml
|
|
89
|
+
default: &default
|
|
90
|
+
adapter: sqlite3
|
|
91
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
92
|
+
timeout: 5000
|
|
93
|
+
|
|
94
|
+
production:
|
|
95
|
+
primary:
|
|
96
|
+
<<: *default
|
|
97
|
+
database: storage/production.sqlite3
|
|
98
|
+
queue:
|
|
99
|
+
<<: *default
|
|
100
|
+
database: storage/production_queue.sqlite3
|
|
101
|
+
migrations_paths: db/queue_migrate
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Queue Configuration (Production Prioritization):**
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
# config/queue.yml
|
|
108
|
+
production:
|
|
109
|
+
workers:
|
|
110
|
+
- queues: [critical, mailers]
|
|
111
|
+
threads: 5
|
|
112
|
+
processes: 2
|
|
113
|
+
polling_interval: 0.1
|
|
114
|
+
- queues: [default]
|
|
115
|
+
threads: 3
|
|
116
|
+
processes: 2
|
|
117
|
+
polling_interval: 1
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Mission Control Setup (Web Dashboard):**
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# Gemfile
|
|
124
|
+
gem "mission_control-jobs"
|
|
125
|
+
|
|
126
|
+
# config/routes.rb
|
|
127
|
+
Rails.application.routes.draw do
|
|
128
|
+
# Protect with authentication
|
|
129
|
+
authenticate :user, ->(user) { user.admin? } do
|
|
130
|
+
mount MissionControl::Jobs::Engine, at: "/jobs"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Or use HTTP Basic Auth in development/staging
|
|
134
|
+
# if Rails.env.development? || Rails.env.staging?
|
|
135
|
+
# mount MissionControl::Jobs::Engine, at: "/jobs"
|
|
136
|
+
# end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# config/initializers/mission_control.rb (optional customization)
|
|
140
|
+
MissionControl::Jobs.configure do |config|
|
|
141
|
+
# Customize job retention (default: 7 days for finished, 30 days for failed)
|
|
142
|
+
config.finished_jobs_retention_period = 14.days
|
|
143
|
+
config.failed_jobs_retention_period = 90.days
|
|
144
|
+
|
|
145
|
+
# Filter sensitive job arguments from display
|
|
146
|
+
config.filter_parameters = [:password, :token, :secret]
|
|
147
|
+
end
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Why:** Database-backed job processing with no external dependencies. Jobs are persistent and survive restarts. Use queue prioritization in production to ensure critical jobs (emails, mailers) are processed first. Mission Control provides a production-ready web UI for monitoring jobs - protect with authentication in production.
|
|
151
|
+
</pattern>
|
|
152
|
+
|
|
153
|
+
<pattern name="basic-job">
|
|
154
|
+
<description>Create and enqueue background jobs</description>
|
|
155
|
+
|
|
156
|
+
**Job Definition:**
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# app/jobs/report_generation_job.rb
|
|
160
|
+
class ReportGenerationJob < ApplicationJob
|
|
161
|
+
queue_as :default
|
|
162
|
+
|
|
163
|
+
def perform(user_id, report_type)
|
|
164
|
+
user = User.find(user_id)
|
|
165
|
+
report = ReportGenerator.generate(user, report_type)
|
|
166
|
+
ReportMailer.with(user: user, report: report).delivery.deliver_later
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Enqueuing:**
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
# Immediate enqueue
|
|
175
|
+
ReportGenerationJob.perform_later(user.id, "monthly")
|
|
176
|
+
|
|
177
|
+
# Delayed enqueue
|
|
178
|
+
ReportGenerationJob.set(wait: 1.hour).perform_later(user.id, "monthly")
|
|
179
|
+
|
|
180
|
+
# Specific queue
|
|
181
|
+
ReportGenerationJob.set(queue: :critical).perform_later(user.id, "urgent")
|
|
182
|
+
|
|
183
|
+
# With priority (higher = more important)
|
|
184
|
+
ReportGenerationJob.set(priority: 10).perform_later(user.id, "important")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Why:** Background jobs prevent blocking HTTP requests. Always pass IDs (not objects) to avoid serialization issues.
|
|
188
|
+
</pattern>
|
|
189
|
+
|
|
190
|
+
<pattern name="job-retry-strategy">
|
|
191
|
+
<description>Configure retry behavior for failed jobs</description>
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
class EmailDeliveryJob < ApplicationJob
|
|
195
|
+
queue_as :mailers
|
|
196
|
+
|
|
197
|
+
# Retry up to 5 times with exponential backoff
|
|
198
|
+
retry_on StandardError, wait: :exponentially_longer, attempts: 5
|
|
199
|
+
|
|
200
|
+
# Don't retry certain errors
|
|
201
|
+
discard_on ActiveJob::DeserializationError
|
|
202
|
+
|
|
203
|
+
# Custom retry logic
|
|
204
|
+
retry_on ApiError, wait: 5.minutes, attempts: 3 do |job, error|
|
|
205
|
+
Rails.logger.error("Job #{job.class} failed: #{error.message}")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def perform(user_id)
|
|
209
|
+
user = User.find(user_id)
|
|
210
|
+
SomeMailer.notification(user).deliver_now
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Why:** Automatic retries with exponential backoff handle transient failures. Discard jobs that will never succeed (deserialization errors).
|
|
216
|
+
</pattern>
|
|
217
|
+
|
|
218
|
+
<antipattern>
|
|
219
|
+
<description>Using Sidekiq/Redis instead of Solid Stack - VIOLATES TEAM RULE #1</description>
|
|
220
|
+
<bad-example>
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
# ❌ WRONG - VIOLATES TEAM RULE #1
|
|
224
|
+
gem 'sidekiq'
|
|
225
|
+
gem 'redis'
|
|
226
|
+
|
|
227
|
+
# config/environments/production.rb
|
|
228
|
+
config.active_job.queue_adapter = :sidekiq
|
|
229
|
+
config.cache_store = :redis_cache_store, { url: ENV['REDIS_URL'] }
|
|
230
|
+
|
|
231
|
+
# config/cable.yml
|
|
232
|
+
production:
|
|
233
|
+
adapter: redis
|
|
234
|
+
url: <%= ENV['REDIS_URL'] %>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
</bad-example>
|
|
238
|
+
<good-example>
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
# ✅ CORRECT - Solid Stack (TEAM RULE #1)
|
|
242
|
+
# No gems needed - built into Rails 8
|
|
243
|
+
|
|
244
|
+
# config/environments/production.rb
|
|
245
|
+
config.active_job.queue_adapter = :solid_queue
|
|
246
|
+
config.cache_store = :solid_cache_store
|
|
247
|
+
config.solid_queue.connects_to = { database: { writing: :queue } }
|
|
248
|
+
|
|
249
|
+
# config/cable.yml
|
|
250
|
+
production:
|
|
251
|
+
adapter: solid_cable
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
</good-example>
|
|
255
|
+
|
|
256
|
+
**Why bad:** External Redis dependency adds complexity, deployment overhead, and another service to monitor. Violates TEAM RULE #1. Solid Stack is production-ready, persistent, and simpler to operate.
|
|
257
|
+
</antipattern>
|
|
258
|
+
|
|
259
|
+
<pattern name="job-monitoring">
|
|
260
|
+
<description>Monitor SolidQueue job status and health</description>
|
|
261
|
+
|
|
262
|
+
**Rails Console:**
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
SolidQueue::Job.pending.count # => 42
|
|
266
|
+
SolidQueue::Job.failed.count # => 3
|
|
267
|
+
SolidQueue::Job.failed.each { |job| puts "#{job.class_name}: #{job.error}" }
|
|
268
|
+
|
|
269
|
+
# Retry failed job
|
|
270
|
+
SolidQueue::Job.failed.first.retry_job
|
|
271
|
+
|
|
272
|
+
# Clear old completed jobs
|
|
273
|
+
SolidQueue::Job.finished.where("finished_at < ?", 7.days.ago).delete_all
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Health Check Endpoint:**
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
# app/controllers/health_controller.rb
|
|
280
|
+
class HealthController < ApplicationController
|
|
281
|
+
def show
|
|
282
|
+
render json: {
|
|
283
|
+
queue_pending: SolidQueue::Job.pending.count,
|
|
284
|
+
queue_failed: SolidQueue::Job.failed.count,
|
|
285
|
+
oldest_pending_minutes: oldest_pending_age
|
|
286
|
+
}
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
private
|
|
290
|
+
|
|
291
|
+
def oldest_pending_age
|
|
292
|
+
oldest = SolidQueue::Job.pending.order(:created_at).first
|
|
293
|
+
return 0 unless oldest
|
|
294
|
+
((Time.current - oldest.created_at) / 60).round
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
**Why:** Direct database access makes monitoring simple - no special tools needed. Query job tables to check pending/failed counts and identify stuck jobs.
|
|
300
|
+
</pattern>
|
|
301
|
+
|
|
302
|
+
**Which monitoring approach?**
|
|
303
|
+
|
|
304
|
+
| Approach | Best For | Access |
|
|
305
|
+
|----------|----------|--------|
|
|
306
|
+
| Mission Control | Production monitoring, team collaboration, visual investigation | Web UI at /jobs |
|
|
307
|
+
| Rails Console | Quick debugging, one-off queries, scripting | Terminal/SSH |
|
|
308
|
+
| Custom Endpoints | Programmatic monitoring, alerting systems, health checks | HTTP API |
|
|
309
|
+
|
|
310
|
+
<pattern name="mission-control-dashboard">
|
|
311
|
+
<description>Monitor and manage jobs with Mission Control web UI</description>
|
|
312
|
+
|
|
313
|
+
**Accessing the Dashboard:**
|
|
314
|
+
|
|
315
|
+
Visit `/jobs` in your browser (e.g., `https://yourapp.com/jobs`) after mounting the engine.
|
|
316
|
+
|
|
317
|
+
**Dashboard Features:**
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
Jobs Overview:
|
|
321
|
+
- View all jobs across queues (pending, running, finished, failed)
|
|
322
|
+
- Real-time status updates
|
|
323
|
+
- Queue performance metrics (throughput, latency)
|
|
324
|
+
- Search jobs by class name, queue, or status
|
|
325
|
+
|
|
326
|
+
Job Details:
|
|
327
|
+
- Full job arguments and context
|
|
328
|
+
- Execution timeline and duration
|
|
329
|
+
- Error messages and backtraces for failed jobs
|
|
330
|
+
- Retry history
|
|
331
|
+
|
|
332
|
+
Common Operations:
|
|
333
|
+
- Retry individual failed jobs or bulk retry
|
|
334
|
+
- Discard jobs that shouldn't be retried
|
|
335
|
+
- Pause/resume queues
|
|
336
|
+
- Filter by queue, status, time range
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Example Workflows:**
|
|
340
|
+
|
|
341
|
+
```text
|
|
342
|
+
Investigating Failed Jobs:
|
|
343
|
+
1. Navigate to /jobs → Failed tab
|
|
344
|
+
2. Filter by job class or time range
|
|
345
|
+
3. Click job to see full error backtrace
|
|
346
|
+
4. Fix underlying issue in code
|
|
347
|
+
5. Retry job from dashboard
|
|
348
|
+
|
|
349
|
+
Monitoring Queue Health:
|
|
350
|
+
1. Navigate to /jobs → Queues tab
|
|
351
|
+
2. Check pending count and oldest job age
|
|
352
|
+
3. Review throughput metrics
|
|
353
|
+
4. Identify bottlenecks (high latency queues)
|
|
354
|
+
|
|
355
|
+
Bulk Operations:
|
|
356
|
+
1. Navigate to /jobs → Failed tab
|
|
357
|
+
2. Select multiple jobs with checkboxes
|
|
358
|
+
3. Click "Retry Selected" or "Discard Selected"
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Why:** Web UI makes job monitoring accessible to entire team, not just developers with console access. Visual investigation of failures is faster than querying databases.
|
|
362
|
+
</pattern>
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## SolidCache
|
|
367
|
+
|
|
368
|
+
SolidCache is a database-backed cache store for Rails applications with zero external dependencies.
|
|
369
|
+
|
|
370
|
+
<pattern name="solidcache-setup">
|
|
371
|
+
<description>Configure SolidCache for application caching</description>
|
|
372
|
+
|
|
373
|
+
**Configuration:**
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
# config/environments/{development,production}.rb
|
|
377
|
+
config.cache_store = :solid_cache_store
|
|
378
|
+
|
|
379
|
+
# config/database.yml
|
|
380
|
+
production:
|
|
381
|
+
cache:
|
|
382
|
+
<<: *default
|
|
383
|
+
database: storage/production_cache.sqlite3
|
|
384
|
+
migrations_paths: db/cache_migrate
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**Usage:**
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
# Simple caching
|
|
391
|
+
Rails.cache.fetch("user_#{user.id}", expires_in: 1.hour) do
|
|
392
|
+
expensive_computation(user)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Fragment caching in views
|
|
396
|
+
<% cache @post do %>
|
|
397
|
+
<%= render @post %>
|
|
398
|
+
<% end %>
|
|
399
|
+
|
|
400
|
+
# Collection caching
|
|
401
|
+
<% cache @posts do %>
|
|
402
|
+
<% @posts.each do |post| %>
|
|
403
|
+
<% cache post do %>
|
|
404
|
+
<%= render post %>
|
|
405
|
+
<% end %>
|
|
406
|
+
<% end %>
|
|
407
|
+
<% end %>
|
|
408
|
+
|
|
409
|
+
# Low-level operations
|
|
410
|
+
Rails.cache.write("key", "value", expires_in: 1.hour)
|
|
411
|
+
Rails.cache.read("key") # => "value"
|
|
412
|
+
Rails.cache.delete("key")
|
|
413
|
+
Rails.cache.exist?("key") # => false
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Migrations:**
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
rails db:migrate:cache
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Why:** Database-backed caching with no Redis dependency. Persistent across restarts, easy to inspect and debug.
|
|
423
|
+
</pattern>
|
|
424
|
+
|
|
425
|
+
<pattern name="cache-keys">
|
|
426
|
+
<description>Use consistent cache key patterns</description>
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
# Model-based cache keys (includes updated_at for auto-expiration)
|
|
430
|
+
Rails.cache.fetch(["user", user.id, user.updated_at]) do
|
|
431
|
+
expensive_user_data(user)
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Or use cache_key helper
|
|
435
|
+
Rails.cache.fetch(user.cache_key) do
|
|
436
|
+
expensive_user_data(user)
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
# Namespace cache keys by version
|
|
440
|
+
Rails.cache.fetch(["v2", "user", user.id]) do
|
|
441
|
+
new_expensive_computation(user)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
# Cache dependencies
|
|
445
|
+
Rails.cache.fetch(["posts", "index", @posts.maximum(:updated_at)]) do
|
|
446
|
+
render_posts_expensive(@posts)
|
|
447
|
+
end
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
**Why:** Including timestamps in cache keys provides automatic invalidation. Namespacing prevents cache collisions when changing logic.
|
|
451
|
+
</pattern>
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## SolidCable
|
|
456
|
+
|
|
457
|
+
SolidCable is a database-backed Action Cable adapter for WebSocket connections with zero external dependencies.
|
|
458
|
+
|
|
459
|
+
<pattern name="solidcable-setup">
|
|
460
|
+
<description>Configure SolidCable for ActionCable/WebSockets</description>
|
|
461
|
+
|
|
462
|
+
**Configuration:**
|
|
463
|
+
|
|
464
|
+
```yaml
|
|
465
|
+
# config/cable.yml
|
|
466
|
+
production:
|
|
467
|
+
adapter: solid_cable
|
|
468
|
+
|
|
469
|
+
# config/database.yml
|
|
470
|
+
production:
|
|
471
|
+
cable:
|
|
472
|
+
<<: *default
|
|
473
|
+
database: storage/production_cable.sqlite3
|
|
474
|
+
migrations_paths: db/cable_migrate
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
**Channel Definition:**
|
|
478
|
+
|
|
479
|
+
```ruby
|
|
480
|
+
# app/channels/notifications_channel.rb
|
|
481
|
+
class NotificationsChannel < ApplicationCable::Channel
|
|
482
|
+
def subscribed
|
|
483
|
+
stream_from "notifications_#{current_user.id}"
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def unsubscribed
|
|
487
|
+
# Cleanup when channel is unsubscribed
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Broadcasting:**
|
|
493
|
+
|
|
494
|
+
```ruby
|
|
495
|
+
# From anywhere in your application
|
|
496
|
+
ActionCable.server.broadcast(
|
|
497
|
+
"notifications_#{user.id}",
|
|
498
|
+
{ message: "New notification", type: "info" }
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# From a model callback
|
|
502
|
+
class Notification < ApplicationRecord
|
|
503
|
+
after_create_commit do
|
|
504
|
+
ActionCable.server.broadcast(
|
|
505
|
+
"notifications_#{user_id}",
|
|
506
|
+
{ message: message, type: notification_type }
|
|
507
|
+
)
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Client-side (Stimulus):**
|
|
513
|
+
|
|
514
|
+
```javascript
|
|
515
|
+
// app/javascript/controllers/notifications_controller.js
|
|
516
|
+
import { Controller } from "@hotwired/stimulus"
|
|
517
|
+
import consumer from "../channels/consumer"
|
|
518
|
+
|
|
519
|
+
export default class extends Controller {
|
|
520
|
+
connect() {
|
|
521
|
+
this.subscription = consumer.subscriptions.create(
|
|
522
|
+
"NotificationsChannel",
|
|
523
|
+
{
|
|
524
|
+
received: (data) => {
|
|
525
|
+
this.displayNotification(data)
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
disconnect() {
|
|
532
|
+
this.subscription?.unsubscribe()
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
displayNotification(data) {
|
|
536
|
+
// Update UI with notification
|
|
537
|
+
console.log("Received:", data)
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Why:** Database-backed WebSocket connections with no Redis dependency. Simple to deploy and monitor.
|
|
543
|
+
</pattern>
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
547
|
+
## Multi-Database Management
|
|
548
|
+
|
|
549
|
+
<pattern name="multi-database-operations">
|
|
550
|
+
<description>Manage migrations across all Solid Stack databases</description>
|
|
551
|
+
|
|
552
|
+
**Setup:**
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
# Creates all databases (primary, queue, cache, cable)
|
|
556
|
+
rails db:create
|
|
557
|
+
|
|
558
|
+
# Migrates all databases
|
|
559
|
+
rails db:migrate
|
|
560
|
+
|
|
561
|
+
# Production: creates + migrates
|
|
562
|
+
rails db:prepare
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
**Individual Operations:**
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
# Migrate specific database
|
|
569
|
+
rails db:migrate:queue
|
|
570
|
+
rails db:migrate:cache
|
|
571
|
+
rails db:migrate:cable
|
|
572
|
+
|
|
573
|
+
# Check migration status
|
|
574
|
+
rails db:migrate:status:queue
|
|
575
|
+
rails db:migrate:status:cache
|
|
576
|
+
rails db:migrate:status:cable
|
|
577
|
+
|
|
578
|
+
# Rollback specific database
|
|
579
|
+
rails db:rollback:queue
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Why:** Each database has independent migration path, allowing separate versioning and rollback per component.
|
|
583
|
+
</pattern>
|
|
584
|
+
|
|
585
|
+
<antipattern>
|
|
586
|
+
<description>Sharing database between primary and Solid Stack components</description>
|
|
587
|
+
<bad-example>
|
|
588
|
+
|
|
589
|
+
```yaml
|
|
590
|
+
# ❌ WRONG - All on same database creates contention
|
|
591
|
+
production:
|
|
592
|
+
primary:
|
|
593
|
+
database: storage/production.sqlite3
|
|
594
|
+
queue:
|
|
595
|
+
database: storage/production.sqlite3 # Same database!
|
|
596
|
+
cache:
|
|
597
|
+
database: storage/production.sqlite3 # Same database!
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
</bad-example>
|
|
601
|
+
<good-example>
|
|
602
|
+
|
|
603
|
+
```yaml
|
|
604
|
+
# ✅ CORRECT - Separate databases for isolation
|
|
605
|
+
production:
|
|
606
|
+
primary:
|
|
607
|
+
database: storage/production.sqlite3
|
|
608
|
+
queue:
|
|
609
|
+
database: storage/production_queue.sqlite3
|
|
610
|
+
migrations_paths: db/queue_migrate
|
|
611
|
+
cache:
|
|
612
|
+
database: storage/production_cache.sqlite3
|
|
613
|
+
migrations_paths: db/cache_migrate
|
|
614
|
+
cable:
|
|
615
|
+
database: storage/production_cable.sqlite3
|
|
616
|
+
migrations_paths: db/cable_migrate
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
</good-example>
|
|
620
|
+
|
|
621
|
+
**Why bad:** Sharing databases creates performance contention, makes it harder to scale, and couples concerns that should be isolated. Separate databases allow independent optimization and scaling.
|
|
622
|
+
</antipattern>
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
<testing>
|
|
627
|
+
|
|
628
|
+
```ruby
|
|
629
|
+
# test/integration/solid_stack_test.rb
|
|
630
|
+
class SolidStackTest < ActionDispatch::IntegrationTest
|
|
631
|
+
test "SolidQueue is configured" do
|
|
632
|
+
assert_equal :solid_queue, Rails.configuration.active_job.queue_adapter
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
test "SolidCache is configured" do
|
|
636
|
+
assert_instance_of ActiveSupport::Cache::SolidCacheStore, Rails.cache
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
test "cache read/write works" do
|
|
640
|
+
Rails.cache.write("test_key", "test_value")
|
|
641
|
+
assert_equal "test_value", Rails.cache.read("test_key")
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
test "jobs are persisted in queue database" do
|
|
645
|
+
TestJob.perform_later
|
|
646
|
+
assert SolidQueue::Job.pending.exists?
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
test "failed jobs are recorded" do
|
|
650
|
+
assert_raises(StandardError) do
|
|
651
|
+
perform_enqueued_jobs { FailingJob.perform_later }
|
|
652
|
+
end
|
|
653
|
+
assert SolidQueue::Job.failed.exists?
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# test/jobs/sample_job_test.rb
|
|
658
|
+
class SampleJobTest < ActiveJob::TestCase
|
|
659
|
+
test "job is enqueued" do
|
|
660
|
+
assert_enqueued_with(job: SampleJob, args: ["arg1"]) do
|
|
661
|
+
SampleJob.perform_later("arg1")
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
test "job is performed" do
|
|
666
|
+
perform_enqueued_jobs do
|
|
667
|
+
SampleJob.perform_later("test")
|
|
668
|
+
end
|
|
669
|
+
# Assert side effects
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
test "job retries on failure" do
|
|
673
|
+
SampleJob.any_instance.expects(:perform).raises(StandardError).times(3)
|
|
674
|
+
assert_raises(StandardError) do
|
|
675
|
+
perform_enqueued_jobs { SampleJob.perform_later }
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
</testing>
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
<related-skills>
|
|
686
|
+
- rails-ai:mailers - Email delivery via SolidQueue background jobs
|
|
687
|
+
- rails-ai:project-setup - Environment-specific Solid Stack configuration
|
|
688
|
+
- rails-ai:testing - Testing jobs and background processing
|
|
689
|
+
- rails-ai:models - Background jobs for model operations
|
|
690
|
+
</related-skills>
|
|
691
|
+
|
|
692
|
+
<resources>
|
|
693
|
+
|
|
694
|
+
**Official Documentation:**
|
|
695
|
+
- [Rails Guides - Active Job Basics](https://guides.rubyonrails.org/active_job_basics.html)
|
|
696
|
+
- [Rails 8 Release Notes](https://edgeguides.rubyonrails.org/8_0_release_notes.html) - Solid Stack introduction
|
|
697
|
+
|
|
698
|
+
**Gems & Libraries:**
|
|
699
|
+
- [SolidQueue](https://github.com/rails/solid_queue) - DB-backed job queue (Rails 8+)
|
|
700
|
+
- [SolidCache](https://github.com/rails/solid_cache) - DB-backed caching (Rails 8+)
|
|
701
|
+
- [SolidCable](https://github.com/rails/solid_cable) - DB-backed Action Cable (Rails 8+)
|
|
702
|
+
- [Mission Control - Jobs](https://github.com/rails/mission_control-jobs) - Web dashboard for monitoring jobs
|
|
703
|
+
|
|
704
|
+
</resources>
|