291 lines
13 KiB
Markdown
291 lines
13 KiB
Markdown
---
|
||
description: 'L4D Tools - Early-stage Rails 8.1 app with Solid Queue, Hotwire, Kamal deployment'
|
||
applyTo: '**/*.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_queue` when `SOLID_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
|
||
```bash
|
||
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
|
||
```bash
|
||
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
|
||
```bash
|
||
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)
|
||
```bash
|
||
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_KEY` env var via Kamal secrets or system env
|
||
- **Job Queue**: `SOLID_QUEUE_IN_PUMA=true` embeds 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 from `ApplicationJob < ActiveJob::Base`
|
||
- **Configuration**: `config/queue.yml` defines:
|
||
- `threads: 3` - worker threads per process
|
||
- `processes: JOB_CONCURRENCY` env 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"]` in `config/puma.rb` runs 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"` → `NameController` in `app/javascript/controllers/name_controller.js`
|
||
- **Asset Pipeline**: Propshaft (modern successor to Sprockets) + importmap-rails
|
||
- CSS: All files in `app/assets/stylesheets/` auto-loaded via `stylesheet_link_tag :app`
|
||
- JS: Controllers auto-discovered; no webpack/bundler required
|
||
- Entry point: `app/javascript/application.js` imports controllers and libraries
|
||
- **Example Stimulus binding**:
|
||
```javascript
|
||
// 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) }
|
||
}
|
||
```
|
||
```erb
|
||
<!-- In view: -->
|
||
<div data-controller="search">
|
||
<input data-search-target="input" data-action="input->search#search">
|
||
</div>
|
||
```
|
||
|
||
### Database & Migrations (SQLite WAL)
|
||
- **Schema**: `db/schema.rb` auto-generated; commit to git (tracks schema evolution)
|
||
- **Migrations**: Generate via `bin/rails generate migration DescriptiveNameHere`
|
||
- Use `change` method for reversible migrations (preferred)
|
||
- Always add indexes for foreign keys
|
||
- Test rollback: `bin/rails db:rollback` verifies reversibility
|
||
- **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 -a` to 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.rb` simple; add routes only when adding controller actions
|
||
- Write tests using fixtures; auto-loaded into test instance variables
|
||
- Use `bin/rails test` to run full suite with parallel workers
|
||
|
||
### Performance & Background Jobs
|
||
- Use Solid Queue `perform_later` for background work; never block HTTP requests
|
||
- Set `JOB_CONCURRENCY` env var to scale job processing beyond default (1 process × 3 threads)
|
||
- Test jobs with `MyJob.perform_now` in 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 via `master.key`)
|
||
- Never commit `config/master.key`; set `RAILS_MASTER_KEY` env var in production
|
||
- Use `bin/rails credentials:edit` to add new secrets
|
||
- Development uses `.key` file automatically; test/production use env variable
|
||
|
||
### Debugging
|
||
- Debug with `byebug` or Rails logger; avoid `puts` for 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
|
||
1. **Routing**: `config/routes.rb` → Controller action
|
||
2. **Parameter Validation**: Use `strong_parameters` in ApplicationController (already configured)
|
||
3. **Business Logic**: Execute in service objects or job queues, not controllers
|
||
4. **Response**: Render ERB template (with Turbo) or JSON (if API)
|
||
5. **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
|
||
1. **Enqueue**: `MyJob.perform_later(id)` in controller/service
|
||
2. **Polling**: Solid Queue supervisor (in Puma) polls for jobs every 0.1s
|
||
3. **Execution**: Worker thread picks job, calls `perform(id)`
|
||
4. **Persistence**: Results/errors logged in job records; failures retryable
|
||
5. **Monitoring**: Check status via `SolidQueue::Job` model in console
|
||
|
||
### Asset & Static File Pipeline
|
||
- **CSS**: All files in `app/assets/stylesheets/` auto-included via `stylesheet_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)
|
||
```ruby
|
||
# 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
|
||
```ruby
|
||
# 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
|
||
```javascript
|
||
// 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)
|
||
}
|
||
}
|
||
```
|
||
|
||
```erb
|
||
<!-- View: -->
|
||
<div data-controller="search">
|
||
<input type="text" data-search-target="input" data-action="input->search#search">
|
||
</div>
|
||
```
|
||
|
||
### Test Pattern (Minitest)
|
||
```ruby
|
||
# 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.key` or `.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 minimal
|
||
- `app/views/` - ERB templates with Turbo/Stimulus integration
|
||
- `app/javascript/controllers/` - Stimulus controllers for DOM interactivity
|
||
- `app/jobs/` - Solid Queue background jobs; use for time-consuming operations
|
||
- `config/` - Application configuration; routes, database, queue, deployment settings
|
||
- `test/` - Test suite mirroring app structure; fixtures auto-loaded
|
||
- `db/migrations/` - Version-controlled database schema changes
|
||
|
||
## Common Pitfalls to Avoid
|
||
|
||
### Early-Stage Mistakes
|
||
- **Don't skip migrations**: Always create reversible migrations with `change` blocks (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=1` by default; set env var to scale
|
||
- **Test mode synchronous**: Jobs run immediately in tests; use `perform_now` explicitly 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 `.active` or `.selected` classes
|
||
|
||
### 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=true` only works with one web server
|
||
- **Database file location**: SQLite database is in `storage/` directory; ensure volume mounts in Docker
|