Maquina Generators
Rails generators that write standalone code: passwordless auth, Rack::Attack scanner bans with a Security dashboard, job queues and error tracking.
Rails generators that produce standalone application code. No runtime dependency — generate once, own the code forever. Delete the gem when you’re done.
What Is This?
After rails new, every developer follows the same steps: configure authentication, set up request throttling, wire up a job queue, add error tracking. These steps aren’t gaps in the framework — they’re workflow choices that are repetitive and time-consuming.
Maquina Generators make the post-rails new setup as deterministic as the framework itself. The gem is development-only. Everything it produces lives in your app and is yours to modify.
rails new myapp --css tailwind
bundle add maquina-generators --group development
rails generate maquina:app --auth clave
bin/rails db:migrate
bin/dev
Five commands. Auth, multi-tenancy, roles, job queue, error tracking, request protection and a dashboard of what it refused — all generated, all yours.

Quick Start
1. Create a Rails App
rails new myapp --css tailwind
cd myapp
2. Add the Gem
bundle add maquina-generators --group development
3. Run the App Generator
rails generate maquina:app --auth registration
4. Finish Setup
bin/rails db:migrate
bin/rails credentials:edit
# Add: backstage: { username: admin, password: your_password }
bin/dev
Available Generators
| Generator | Command | Purpose |
|---|---|---|
| App | rails g maquina:app |
Full application setup (orchestrator) |
| Clave | rails g maquina:clave |
Passwordless email-code authentication |
| Registration | rails g maquina:registration |
Password-based auth with accounts and roles |
| Rack Attack | rails g maquina:rack_attack |
Scanner and flood bans, request throttling |
| Security | rails g maquina:security |
Dashboard of every Rack::Attack refusal |
| Solid Queue | rails g maquina:solid_queue |
Background job processing |
| Solid Errors | rails g maquina:solid_errors |
Error tracking dashboard |
| Mission Control | rails g maquina:mission_control_jobs |
Job queue monitoring dashboard |
The App Generator
The orchestrator. Runs after rails new and configures a complete, production-ready application in a single command.
rails g maquina:app --auth clave --prefix /admin --port 3000
What It Does
- Adds gems — brakeman, standard, rails-i18n, maquina-components, aws-sdk-s3
- Creates configs — Procfile.dev, .rubocop.yml, .standard.yml
- Configures environments — letter_opener for dev, APPLICATION_HOST for production
- Installs Rails features — Action Text, Active Storage, Turbo morphing
- Runs auth generator — your choice of clave, registration, or none
- Runs sub-generators — solid_queue, mission_control_jobs, solid_errors, and security (which brings the rack_attack rules)
- Installs Solid adapters — Solid Queue, Solid Cache, Solid Cable, Solid Errors
- Installs Maquina Components — UI library ready to use
- Creates HomeController — with root route
- Sets up multi-database — primary, queue, cache, cable, errors, security
Options
| Option | Default | Description |
|---|---|---|
--auth |
none |
Authentication: none, clave, or registration |
--prefix |
/admin |
URL prefix for ops dashboards |
--port |
3000 |
Development server port |
Generated Database Configuration
development:
primary:
database: storage/development.sqlite3
queue:
database: storage/development_queue.sqlite3
cache:
database: storage/development_cache.sqlite3
cable:
database: storage/development_cable.sqlite3
errors:
database: storage/development_errors.sqlite3
security:
database: storage/development_security.sqlite3
Authentication: Clave (Passwordless)
Complete passwordless authentication using email verification codes. Users receive a 6-digit code via email to sign in — no passwords to manage, no password resets to build.
rails g maquina:clave
What You Get
Models:
Account— multi-tenant container (has_many :users)User— with role enum (member/admin), account association, blocking supportSession— browser session tracking with IP and user agentEmailVerification— verification codes with expiry and attempt trackingCurrent—ActiveSupport::CurrentAttributeswith session, user, and account
Controllers:
SessionsController— email entry for sign-inSession::VerificationsController— code verificationSession::VerificationResendsController— resend with 15-minute cooldownRegistrationsController— account creation (optional)- Registration verification controllers
Additional:
VerificationMailer— HTML + text email templatesAuthenticationCleanupJob— daily cleanup of expired sessions and codesSessionTestHelper—sign_in_as(user)andsign_outfor tests- Full i18n support (English and Spanish)
How It Works
User enters email → receives 6-digit code → enters code → signed in
- Codes expire in 15 minutes
- 15-minute cooldown before resend
- Rate limited: 10 attempts per 3 minutes
- Sessions last 30 days (configurable)
+characters blocked in emails to prevent alias attacks
Multi-Tenancy
Every user belongs to an Account. The first user who creates an account becomes its admin.
# Access anywhere in your app
Current.user # The signed-in user
Current.account # The user's account
Current.user.admin? # Check role
Scoping Queries
class ProjectsController < ApplicationController
def index
@projects = Current.account.projects
end
def create
@project = Current.account.projects.build(project_params)
# ...
end
private
def set_project
@project = Current.account.projects.find(params[:id])
end
end
Options
| Option | Default | Description |
|---|---|---|
--skip-views |
false |
Skip view templates |
--skip-registration |
false |
Skip sign-up flow (sign-in only) |
Authentication: Registration (Password-Based)
Password-based authentication that builds on Rails 8’s built-in rails generate authentication. Adds multi-tenancy with an Account model, user roles, and a registration flow.
rails g maquina:registration
What It Adds to Rails Auth
Rails 8’s authentication generator gives you login but no signup. Registration adds:
Accountmodel withhas_many :users- User gains
belongs_to :accountand role enum (admin/member) Current.accountdelegationRegistrationsController— creates Account + User in a single transaction- Tailwind-styled views
- English and Spanish translations
Generated Models
class Account < ApplicationRecord
has_many :users, dependent: :destroy
validates :name, presence: true
end
class User < ApplicationRecord
has_secure_password
has_many :sessions, dependent: :destroy
belongs_to :account
validates :name, presence: true
enum :role, { member: "member", admin: "admin" }, default: "member"
end
class Current < ActiveSupport::CurrentAttributes
attribute :session
delegate :user, to: :session, allow_nil: true
delegate :account, to: :user, allow_nil: true
end
Registration Flow
class RegistrationsController < ApplicationController
allow_unauthenticated_access
rate_limit to: 10, within: 3.minutes, only: :create
def create
ActiveRecord::Base.transaction do
account = Account.create!(name: params[:account_name])
user = account.users.create!(
name: params[:name],
email_address: params[:email_address],
password: params[:password],
role: :admin
)
end
start_new_session_for user
redirect_to root_path
end
end
Options
| Option | Default | Description |
|---|---|---|
--skip-views |
false |
Skip view templates |
Rack Attack
Request protection with Rack::Attack. Bans vulnerability scanners and addresses that ignore the throttle, instead of refusing one request at a time.
rails g maquina:rack_attack
rails g maquina:rack_attack --login-path /sign_in
Default Protections
| Rule | Trigger | Response |
|---|---|---|
Scanner ban (fail2ban/scanners) |
3 scanner paths in 10 minutes | 403, IP banned for 7 days |
Flood ban (allow2ban/flood) |
600 requests in 5 minutes | 403, IP banned for 1 day |
General throttle (req/ip) |
Over 300 requests in 5 minutes | 429 |
Sign-in throttle (login/ip) |
Over 5 POST /session in 20 seconds |
429 |
| Safelist | 127.0.0.1, ::1 |
Never refused |
A scanner sprays hundreds of guesses in a couple of minutes. Refusing each matching path lets every other guess through to routing, so the third scanner path bans the address for a week. The flood ban is twice the general throttle: an address still sending at that rate has ignored every 429 since it hit the limit.
Scanner paths are PHP files (*.php), WordPress paths (/wp-admin, /wp-login, /xmlrpc.php), sensitive files anywhere in the path (.env, .git, .aws, .ssh, /etc/passwd), backup archives (.zip, .sql, .tar.gz, .bak) and scanner targets (/cgi-bin, /phpmyadmin, /actuator). /rails/active_storage is exempt from the scanner rule and the general throttle, and /assets from the throttle.
Every refusal is logged as an [ATTACK] line with its IP — Rack::Attack answers before Rails, so it is the only trace in the log. To see refusals on a page, add the Security Dashboard.
Tuning
The knobs are constants on Rack::Attack in config/initializers/rack_attack.rb:
SCANNER_MAXRETRY = 3
SCANNER_FINDTIME = 10.minutes
SCANNER_BANTIME = 7.days
GENERAL_LIMIT = 300
GENERAL_PERIOD = 5.minutes
FLOOD_MAXRETRY = GENERAL_LIMIT * 2
FLOOD_BANTIME = 1.day
LOGIN_PATH = "/session"
LOGIN_LIMIT = 5
LOGIN_PERIOD = 20.seconds
Rack::Attack.banned?(ip) answers whether an address is banned right now. Bans and counters live in Rails.cache, so production needs a shared store such as Solid Cache.
Options
| Option | Default | Description |
|---|---|---|
--login-path |
/session |
Path whose POST the sign-in throttle counts |
Solid Queue
Sets up Solid Queue as your Active Job backend with a separate database and Procfile integration.
rails g maquina:solid_queue --database sqlite3
Configuration
# config/solid_queue.yml
default: &default
dispatchers:
- polling_interval: 1
batch_size: 500
workers:
- queues: "*"
threads: 3
polling_interval: 0.1
recurring:
authentication_cleanup:
class: AuthenticationCleanupJob
schedule: every day at 3am
Options
| Option | Default | Description |
|---|---|---|
--database |
sqlite3 |
Database adapter (sqlite3 or postgresql) |
Solid Errors
Error tracking dashboard with custom Tailwind views and HTTP basic auth.
rails g maquina:solid_errors --prefix /admin
What You Get
- Custom Tailwind-styled error views
- HTTP basic auth (credentials-first, ENV fallback)
- Severity badge helpers
- Clipboard and backtrace filter Stimulus controllers
- Engine mounted at
<prefix>/errors - Self-contained mailer templates, so
send_emailsworks without the dashboard’s helpers - Shared admin navigation bar (Overview, Errors, Jobs, Security) and the admin overview at
<prefix>
Authentication
# Checks in order:
# 1. Rails.application.credentials.backstage.username / .password
# 2. ENV["SOLID_ERRORS_USER"] / ENV["SOLID_ERRORS_PASSWORD"]
Set up credentials:
bin/rails credentials:edit
backstage:
username: admin
password: your_secure_password
Options
| Option | Default | Description |
|---|---|---|
--prefix |
required | URL prefix (e.g., /admin) |
--user-env-var |
SOLID_ERRORS_USER |
Custom env var for username |
--password-env-var |
SOLID_ERRORS_PASSWORD |
Custom env var for password |
--copy-views |
true |
Include custom Tailwind views |
Mission Control Jobs
Job queue monitoring dashboard with custom Tailwind views. 41 view files styled to match your application.
rails g maquina:mission_control_jobs --prefix /admin
What You Get
- Full Tailwind-styled dashboard for Solid Queue
- Job status badges, queue views, worker monitoring
- Recurring task management
- Engine mounted at
<prefix>/jobs - Shared admin navigation (Overview, Errors, Jobs, Security) and the admin overview at
<prefix> - HTTP basic auth (same credentials as Solid Errors)
Options
| Option | Default | Description |
|---|---|---|
--prefix |
required | URL prefix (e.g., /admin) |
--user-env-var |
MISSION_CONTROL_JOBS_USER |
Custom env var for username |
--password-env-var |
MISSION_CONTROL_JOBS_PASSWORD |
Custom env var for password |
--copy-views |
true |
Include custom Tailwind views |
Security Dashboard
A backstage page for everything Rack::Attack refused in the last 7 days, next to Solid Errors and Mission Control Jobs.
Each throttled, blocked or banned request is recorded as a Security::AbuseEvent in its own security database and kept for 30 days.
rails g maquina:security --prefix /admin
bin/rails db:prepare
What the Page Shows
The top five of each, for the last 7 days. Times are UTC. No filters and no pagination: it is a briefing, not a log viewer.
| Section | What it answers |
|---|---|
| Stats | Refused requests, throttled, blocked or banned, distinct addresses |
| Throttled addresses | Who went over the general or sign-in limit, and which rules they hit |
| Blocked addresses | Every address a blocklist refused: Banned now, Expired, or Not banned |
| Most requested blocked paths | What the scanners were after |
| Sign-in throttles | Addresses refused on POST /session, kept apart from scanner noise |
| Targeted hosts | Which hostnames the refusals were aimed at |
| By day | Throttled, blocked and banned per day, zeros kept so a spike reads against its neighbours |
| Recent refusals | The last 20, newest first |
Banned now is Rack::Attack’s own answer at render time, so an expired ban flips its badge instead of disappearing. Not banned is an address with a strike or two on the scanner rule — or a slow scanner that stays under three paths in ten minutes, which the page makes visible.
Once an address is banned, every request it sends is refused, its ordinary pages included. The paths table lists only the paths Rack::Attack.scanner_path? recognises, so / and /up never crowd out /.env.
What You Get
maquina:rack_attackrules, unlessconfig/initializers/rack_attack.rbalready definesRack::Attack.banned?config/initializers/rack_attack_events.rb— the subscriber that records each refusal; a failed write never turns a 403 into a 500Security::Record,Security::AbuseEvent(30-day retention) andSecurity::AbuseReport(the page’s figures)db/security_schema.rband asecurity:entry in every environment ofconfig/database.yml- A daily
purge_abuse_eventstask inconfig/recurring.yml Backstage::SecurityController, layout and views at<prefix>/security, and a Security tab in the shared admin navigation
Authentication
Same backstage credentials as Solid Errors and Mission Control Jobs. The page answers 503 until they are set, so it is never left open.
# Checks in order:
# 1. Rails.application.credentials.backstage.username / .password
# 2. ENV["SECURITY_USER"] / ENV["SECURITY_PASSWORD"]
Trying It Locally
Localhost is safelisted, so your own requests are never refused. Rack::Attack trusts X-Forwarded-For from 127.0.0.1, so send one to stand in for another address:
for path in /wp-login.php /.env /backup.zip /; do
curl -s -o /dev/null -w "%{http_code} $path\n" \
-H "X-Forwarded-For: 203.0.113.10" http://localhost:3000$path
done
# 403 /wp-login.php
# 403 /.env
# 403 /backup.zip <- third scanner path: banned for 7 days
# 403 / <- banned, so every path is refused
Development’s :memory_store keeps bans inside the server process: they reset on restart, and a bin/rails runner script cannot see them.
Options
| Option | Default | Description |
|---|---|---|
--prefix |
required | URL prefix (e.g., /admin) |
--login-path |
/session |
Sign-in path for the Rack::Attack throttle |
--user-env-var |
SECURITY_USER |
Custom env var for username |
--password-env-var |
SECURITY_PASSWORD |
Custom env var for password |
--copy-views |
true |
Include the dashboard views |
Architecture Overview
After running maquina:app --auth clave, your project structure looks like this:
app/
controllers/
concerns/
authentication.rb # Session management
sessions_controller.rb # Sign-in
registrations_controller.rb # Sign-up
home_controller.rb # Root page
models/
security/
abuse_event.rb # One Rack::Attack refusal
abuse_report.rb # The Security page's figures
account.rb # Multi-tenant container
user.rb # Roles + auth
current.rb # Request context
session.rb # Browser sessions
email_verification.rb # Verification codes
mailers/
verification_mailer.rb # Email codes
jobs/
authentication_cleanup_job.rb # Daily cleanup
config/
initializers/
rack_attack.rb # Bans and throttles
rack_attack_events.rb # Records each refusal
solid_errors.rb # Error tracking auth
mission_control.rb # Job dashboard auth
solid_queue.yml # Queue configuration
Security Defaults
- Rate limiting on registration and login
- Rack Attack bans scanners for 7 days and floods for 1 day
- All controllers require authentication by default
- Account scoping prevents cross-tenant data access
Ops Dashboards
/admin— overview, with a card per installed dashboard and a slot for your own aggregate counts/admin/errors— error tracking (Solid Errors)/admin/jobs— job queue monitoring (Mission Control Jobs)/admin/security— Rack::Attack refusals
All four share one admin navigation and the backstage credentials. The overview and Security pages answer 503 until those credentials are set.
Role-Based Authorization
Use the role enum to restrict actions:
class ProjectsController < ApplicationController
before_action :require_admin, only: [:destroy]
private
def require_admin
unless Current.user.admin?
redirect_to projects_path, alert: t("flash.general.forbidden")
end
end
end
Roles:
- admin — first user created with account, full access
- member — default role, restricted from destructive actions
Customization
All generated code lives in your app. Common customization points:
| What | Where |
|---|---|
| Redirect after login | app/controllers/concerns/authentication.rb → after_authentication_url |
| Session duration | Change 30.days.from_now in authentication.rb |
| Code expiration | Change 15.minutes.from_now in verification controllers |
| Resend cooldown | EmailVerification::COOLDOWN_MINUTES (default: 15) |
| View styling | Edit view templates directly |
| Email sender | app/mailers/verification_mailer.rb |
| Translations | config/locales/clave.*.yml or registration.*.yml |
| Rack Attack rules | config/initializers/rack_attack.rb — SCANNER_*, FLOOD_*, GENERAL_*, LOGIN_* constants |
| Security retention | Security::AbuseEvent::RETENTION (default: 30 days) |
| Dashboard credentials | bin/rails credentials:edit → backstage: |
| Queue config | config/solid_queue.yml |
Requirements
- Ruby >= 3.2.0
- Rails >= 7.2
- Tailwind CSS (for generated views)
The gem has zero runtime dependencies. Add it to your development group, generate your code, and remove it.
# Gemfile
group :development do
gem "maquina-generators"
end