--- 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
``` ### 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 (``) 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
``` ### 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