13 KiB
13 KiB
| description | applyTo |
|---|---|
| L4D Tools - Early-stage Rails 8.1 app with Solid Queue, Hotwire, Kamal deployment | **/*.rb |
L4D Tools - AI Coding Guide
Project Overview
l4d_tools is a fresh Rails 8.1 application in early development stage. No domain models exist yet—focus is on establishing clean patterns for future features.
Core Stack:
- Framework: Rails 8.1 (modern defaults: modern browser support, Propshaft asset pipeline)
- Database: SQLite with WAL mode (development); production supports Kamal/Docker deployment
- Jobs: Solid Queue (embedded in Puma via
plugin :solid_queuewhenSOLID_QUEUE_IN_PUMA=true) - Frontend: Hotwire (Turbo + Stimulus) + importmap-rails (no build step)
- Testing: Minitest + Capybara with parallel workers
- Deployment: Kamal (Docker/container-based) with private registry at
localhost:5555 - Security: Brakeman, Rubocop-rails-omakase, Bundler-audit
Critical Developer Workflows
Setup & Running
bin/setup # Install gems, prepare SQLite DB, start server on :3000
bin/dev # Start Rails dev server (invokes bin/rails server)
bin/rails console # Interactive REPL with full app context
Testing
bin/rails test # Full test suite (auto-parallelized by CPU cores)
bin/rails test test/models/post_test.rb # Single test file
bin/rails test test/models/post_test.rb:42 # Specific test by line number
Code Quality
bundle exec brakeman # Security scanning; fix ALL warnings before merge
bundle exec rubocop -a # Auto-fix style issues (rails-omakase rules)
bundle exec bundler-audit # Check gem vulnerabilities; update outdated gems
Database (SQLite WAL mode)
bin/rails db:migrate # Apply pending migrations
bin/rails db:rollback # Revert last migration step
bin/rails db:reset # Wipe dev DB and re-run schema + seeds
Deployment (Kamal)
- Config:
config/deploy.yml(servers, registry endpoint, environment variables) - Build: Auto-built by Kamal; Dockerfile uses multi-stage build
- Registry: Private Docker registry at
localhost:5555(configured in deploy.yml) - Secrets: Set
RAILS_MASTER_KEYenv var via Kamal secrets or system env - Job Queue:
SOLID_QUEUE_IN_PUMA=trueembeds queue supervisor in Puma (single-server deployments only)
Architecture & Key Components
Job Processing (Solid Queue - no separate Redis/Sidekiq)
- Store: Database-backed; data lives in Solid Queue tables (queue_schema.rb)
- Location:
app/jobs/- inherit fromApplicationJob < ActiveJob::Base - Configuration:
config/queue.ymldefines:threads: 3- worker threads per processprocesses: JOB_CONCURRENCYenv var - defaults to 1 (scale via env)polling_interval: 0.1- how frequently workers check for jobs
- Enqueue:
MyJob.perform_later(args)for async;MyJob.perform_now(args)for testing/immediate - Puma Integration:
plugin :solid_queue if ENV["SOLID_QUEUE_IN_PUMA"]inconfig/puma.rbruns supervisor in same process (single-server deployments only) - In Tests: Jobs run synchronously (development mode behavior)
- Important: No Sidekiq/Redis—Solid Queue is database-backed and built into Puma
Frontend (Hotwire + Importmap, no build step)
- Turbo: Handles navigation via AJAX (replaces full page loads); auto-loads from
data-turbo=true - Stimulus: Zero-config JavaScript binding via
data-controller="name"→NameControllerinapp/javascript/controllers/name_controller.js - Asset Pipeline: Propshaft (modern successor to Sprockets) + importmap-rails
- CSS: All files in
app/assets/stylesheets/auto-loaded viastylesheet_link_tag :app - JS: Controllers auto-discovered; no webpack/bundler required
- Entry point:
app/javascript/application.jsimports controllers and libraries
- CSS: All files in
- Example Stimulus binding:
// app/javascript/controllers/search_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["input"] search() { console.log(this.inputTarget.value) } }<!-- In view: --> <div data-controller="search"> <input data-search-target="input" data-action="input->search#search"> </div>
Database & Migrations (SQLite WAL)
- Schema:
db/schema.rbauto-generated; commit to git (tracks schema evolution) - Migrations: Generate via
bin/rails generate migration DescriptiveNameHere- Use
changemethod for reversible migrations (preferred) - Always add indexes for foreign keys
- Test rollback:
bin/rails db:rollbackverifies reversibility
- Use
- SQLite Limits: AUTOINCREMENT primary keys, pragma constraints, no ALTER COLUMN
- Models: No domain models exist yet; structure will be pattern-driven once first models are created
Testing Structure
- Unit Tests:
test/models/,test/helpers/,test/controllers/ - Integration Tests:
test/integration/ - System Tests: Full browser tests via Capybara/Selenium
- Parallelization: Enabled by default; workers scale to CPU count
- Fixtures: Auto-loaded and available as lowercase underscore-separated variables (e.g.,
posts(:one))
Project-Specific Guidelines
Early Stage Patterns (No Domain Models Yet)
- Start with migrations: Define database schema first; models follow naturally
- Keep ApplicationRecord/ApplicationJob clean: These are templates for future models/jobs
- Use concerns sparingly: Extract concerns only after 2+ models need the same behavior
- Establish directory structure early: Create
app/services/,app/presenters/if needed for future patterns
Code Quality & Style
- Follow RuboCop-rails-omakase style guide strictly; run
bundle exec rubocop -ato auto-fix - Use snake_case for variables/methods, CamelCase for classes
- Extract reusable business logic into
app/services/to keep models lean - Keep
config/routes.rbsimple; add routes only when adding controller actions - Write tests using fixtures; auto-loaded into test instance variables
- Use
bin/rails testto run full suite with parallel workers
Performance & Background Jobs
- Use Solid Queue
perform_laterfor background work; never block HTTP requests - Set
JOB_CONCURRENCYenv var to scale job processing beyond default (1 process × 3 threads) - Test jobs with
MyJob.perform_nowin test env; they run synchronously - Monitor job failures in
bin/rails console:SolidQueue::Job.where(status: :failed)
Frontend & UI
- Prefer Turbo Frames (
<turbo-frame>) for dynamic UI updates over full page reloads - Always set
data-turbo="false"on forms that should NOT use Turbo (e.g., file uploads, legacy forms) - Stimulus controllers are zero-config; name them kebab-case, file them snake_case
- Test JavaScript with system tests; Capybara + Selenium support Turbo navigation
Secrets & Credentials
- Store all secrets in
Rails.application.credentials(encrypted viamaster.key) - Never commit
config/master.key; setRAILS_MASTER_KEYenv var in production - Use
bin/rails credentials:editto add new secrets - Development uses
.keyfile automatically; test/production use env variable
Debugging
- Debug with
byebugor Rails logger; avoidputsfor production debugging - Use
Rails.logger.info("...")for production-safe logging - Inspect Solid Queue tables directly:
bin/rails console→SolidQueue::Job.last(10)
Integration Points & Data Flows
HTTP Request Lifecycle
- Routing:
config/routes.rb→ Controller action - Parameter Validation: Use
strong_parametersin ApplicationController (already configured) - Business Logic: Execute in service objects or job queues, not controllers
- Response: Render ERB template (with Turbo) or JSON (if API)
- Browser: Turbo replaces page or frame; Stimulus controllers initialize
Database Access Patterns
- Queries: Use model scopes and class methods (not raw SQL)
- Write Operations: Controller delegates to service → model (transaction boundaries)
- Background Jobs: Solid Queue tables store jobs; accessed via ActiveJob interface
- Migrations: Always reversible; schema changes are version-controlled in
db/schema.rb
Job Queue Integration
- Enqueue:
MyJob.perform_later(id)in controller/service - Polling: Solid Queue supervisor (in Puma) polls for jobs every 0.1s
- Execution: Worker thread picks job, calls
perform(id) - Persistence: Results/errors logged in job records; failures retryable
- Monitoring: Check status via
SolidQueue::Jobmodel in console
Asset & Static File Pipeline
- CSS: All files in
app/assets/stylesheets/auto-included viastylesheet_link_tag :app - JS: Controllers auto-discovered from
app/javascript/controllers/; no bundling required - Images: Reference via
image_tag "name"(fingerprinted in production) - PWA: Service worker template available in
app/views/pwa/(commented out in routes) - Template Engine: Use slim for views; prefer partials for reusable components
Job Enqueue (Solid Queue)
# app/jobs/my_job.rb
class MyJob < ApplicationJob
queue_as :default
def perform(arg1, arg2)
# async work here
end
end
# Trigger: MyJob.perform_later(arg1, arg2)
# Test: MyJob.perform_now(arg1, arg2)
Model Scopes & Queries
# app/models/post.rb
class Post < ApplicationRecord
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
def self.active; where(active: true); end
end
# Usage: Post.published.recent
Stimulus Controller
// app/javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["input"]
search() {
console.log(this.inputTarget.value)
}
}
<!-- View: -->
<div data-controller="search">
<input type="text" data-search-target="input" data-action="input->search#search">
</div>
Test Pattern (Minitest)
# test/models/post_test.rb
class PostTest < ActiveSupport::TestCase
fixtures :posts # Auto-loads test/fixtures/posts.yml
test "post is valid" do
assert posts(:one).valid?
end
end
Security & Production Readiness
- Brakeman scans detect security issues; fix all warnings before merge
- Bundler-audit tracks vulnerable gems; update versions regularly
- Strong parameters required for all user input in controllers
- RAILS_MASTER_KEY (from
config/master.key) must be set in Docker via secrets - SQLite adequate for single-server; use PostgreSQL for multi-server scaling
- Content Security Policy configured in
config/initializers/content_security_policy.rb - Never commit
master.keyor.kamal/secrets; set RAILS_MASTER_KEY via environment
Key Directory Structure
app/controllers/- HTTP request handlers; keep thin (validate + delegate to services)app/models/- ActiveRecord models; use scopes for queries, keep business logic minimalapp/views/- ERB templates with Turbo/Stimulus integrationapp/javascript/controllers/- Stimulus controllers for DOM interactivityapp/jobs/- Solid Queue background jobs; use for time-consuming operationsconfig/- Application configuration; routes, database, queue, deployment settingstest/- Test suite mirroring app structure; fixtures auto-loadeddb/migrations/- Version-controlled database schema changes
Common Pitfalls to Avoid
Early-Stage Mistakes
- Don't skip migrations: Always create reversible migrations with
changeblocks (or up/down) - Don't hard-code job names: Reference jobs via constants/classes, not strings
- Don't mix concerns in models: Business logic belongs in services; models are data containers
- Don't create routes without controllers: Routes must map to actual controller actions
Solid Queue Specifics
- No Sidekiq/Redis: Job data is stored in SQLite; don't assume Redis-style operations
- Single-server default:
JOB_CONCURRENCY=1by default; set env var to scale - Test mode synchronous: Jobs run immediately in tests; use
perform_nowexplicitly if needed - Monitor job failures: Failed jobs remain in DB; check
SolidQueue::Job.where(status: :failed)
Hotwire/Frontend Gotchas
- Set data-turbo="false" for file uploads: Turbo doesn't handle multipart forms well
- Use
data-turbo-method="delete"for non-GET actions: Turbo expects method hints - Stimulus controllers auto-initialize: No manual instantiation needed; just use HTML attributes
- CSS conflicts with Turbo: Use scoped styles; avoid global
.activeor.selectedclasses
SQLite Development
- No concurrent migrations: SQLite locks during schema changes; don't run multiple devs simultaneously
- WAL mode enabled: Provides better concurrency; don't disable without reason
- No ALTER COLUMN: Use temporary tables pattern for complex migrations
- Indexes matter: SQLite can't optimize without them; always index foreign keys
Deployment & Secrets
- RAILS_MASTER_KEY required: Set env var before Docker startup; app won't boot without it
- Registry authentication: Docker registry at localhost:5555 must be running for Kamal builds
- Single-server assumption:
SOLID_QUEUE_IN_PUMA=trueonly works with one web server - Database file location: SQLite database is in
storage/directory; ensure volume mounts in Docker