Rails Quality Assurance
Opinionated quality assurance kit for Ruby on Rails applications.
rails-quality-assurance provides an out-of-the-box, comprehensive quality control setup designed to work with zero-configuration defaults. It integrates the standard quality control suite used across Develoz Rails applications.
What's Included
1. Style & Linting
-
RuboCop: Pre-configured
rubocop.ymltargeting Ruby 4.0 with all standard plugins loaded (rubocop-rails,rubocop-rspec,rubocop-rspec_rails,rubocop-performance,rubocop-capybara,rubocop-factory_bot,rubocop-rubycw,rubocop-migration). -
Reek: Code smell detection with sensible Rails defaults (
config/reek.yml). - Flay: Structural code duplication analysis.
-
Frontend QA (JS/TS/CSS): delegated to the
@develoz/quality-assuranceCLI. Biome lints and formats, Stylelint covers CSS/Tailwind, and the CLI adds code-smell, duplication, type-check, dead-code, architecture-boundary and dependency-audit gates. Tasks prefer the app's installed copy (node_modules/.bin/qa) and fall back tonpx, so no app-side install is required. Runrails g rails_quality_assurance:jsto pin it inpackage.json.
2. Security
- Brakeman: Static analysis security vulnerability scanner.
- Bundler Audit: Vulnerability scanner for gem dependencies.
-
Importmap Audit: Vulnerability scanner for pinned importmap packages. Runs
only when the app has
config/importmap.rb, using the appbin/importmapbinstub when present and a dependency-lightimportmap-railsinvocation otherwise. -
JavaScript Audit: Vulnerability scanner for npm packages, run through the
JS quality-assurance CLI (
qa audit). Supports npm, yarn, pnpm and bun, fails on high and critical advisories, and audits production dependencies only. Dev-only tooling (linters, test runners) never ships, and its transitive advisories, some with no available fix, would otherwise block CI and deploys.
3. Testing & Coverage
- RSpec: Shared helpers and configuration.
-
Playwright + Capybara: Remote Chromium headless driver support with auto port assignment for parallel workers and HTML error state dumps on failure (
require 'rails_quality_assurance/playwright'). -
Parallel Tests:
rake spec:paralleltask that matches the CPU count by default, isolates system specs into a dedicated worker, and serializes runs behind a cross-process lock. -
SimpleCov: Line and branch coverage threshold enforcement (100% by
default, configurable via
.simplecovor environment), LCOV reporting, and parallel process reporting (require 'rails_quality_assurance/simplecov').
4. Continuous Integration Harness
-
RailsQualityAssurance::CI: Built-in pipeline runner modeled after Rails CI harnesses. -
CLI / Executable: Run with
bundle exec rails-qaor define your pipeline inconfig/ci.rb. -
Pre-commit hook:
rails_quality_assurance:pre_commitinstalls a tracked.githooks/pre-commit(RuboCop, Reek, RSpec) that works alongside shared hook managers without disabling their other hooks.
Installation
Add to your Gemfile:
group :development, :test do
gem 'rails-quality-assurance', require: false
endThen run:
bundle installZero-Config Usage
1. RuboCop
In your .rubocop.yml:
inherit_gem:
rails-quality-assurance: rubocop.yml
# Only your application-specific overrides are needed here2. Single Require for Testing & Coverage (spec/spec_helper.rb)
Add a single line at the very top of spec/spec_helper.rb:
require 'rails_quality_assurance/all'That single require sets up:
- SimpleCov: Starts immediately before application code loads, enforcing line & branch coverage thresholds (100% by default), parallel process aggregation, and LCOV output.
- RSpec: Configures mock verification of partial doubles, expectations, and metadata inheritance.
-
Playwright + Capybara System Tests: Registers the
:remote_playwrightheadless driver, defaults system specs to:rack_test, escalates to Playwright forjs: true, dynamically allocates ports forparallel_testsworkers, and dumps HTML failure snapshots intotmp/capybara.
(If you prefer to load them individually, require 'rails_quality_assurance/simplecov' and require 'rails_quality_assurance/playwright' remain available.)
Configuring SimpleCov (.simplecov)
rails_quality_assurance requires SimpleCov and then calls
SimpleCov.start 'rails'. SimpleCov automatically loads a .simplecov file
from the project root while require 'simplecov' runs, so anything configured
there is applied before coverage starts:
# .simplecov
SimpleCov.configure do
enable_coverage :branch # required before branch thresholds
minimum_coverage line: 95, branch: 90
maximum_coverage_drop line: 5, branch: 2
cover_views # measure ActionView templates (SimpleCov 1.2+)
track_tests # record which test covered each line
group 'Components', 'app/components'
skip 'app/views/pwa'
endSimpleCov requires branch coverage to be enabled before any branch threshold is
declared, so call enable_coverage :branch before setting minimum_coverage
or maximum_coverage_drop for branches. The gem enables it too, but only after
.simplecov has loaded.
Use SimpleCov's native DSL for any option the gem does not wrap — cover,
track_tests, group, baseline_file, and so on. Because SimpleCov reads the
file itself, a new SimpleCov option is available to a project without a new
rails-quality-assurance release.
.simplecov is loaded with plain load, so bare method calls resolve against
main. Wrap them in SimpleCov.configure (which evaluates the block in
SimpleCov's context, as above) or prefix each call with SimpleCov.. Keep it
to configuration only: SimpleCov.start belongs in spec/spec_helper.rb, and
SimpleCov 1.3 deprecates calling it from .simplecov.
Coverage thresholds
The gem defaults to 100% line and branch minimum coverage with no maximum drop. Override any of them per run with environment variables:
| Variable | Purpose |
|---|---|
MINIMUM_LINE_COVERAGE |
Minimum line coverage (0-100) |
MINIMUM_BRANCH_COVERAGE |
Minimum branch coverage (0-100) |
MAXIMUM_COVERAGE_DROP |
Maximum line coverage drop (0-100) |
MAXIMUM_COVERAGE_DROP_BRANCH |
Maximum branch coverage drop (0-100) |
Values are resolved in this order, highest first:
- Environment variables
-
.simplecovconfiguration - Gem defaults
So a project can keep a committed baseline in .simplecov and still relax or
tighten a single criterion for a run:
MINIMUM_LINE_COVERAGE=90 bin/rails spec:parallelSet a threshold to 0 to disable it. Invalid values abort the run with the
offending variable named.
3. Rails Test Setup (spec/rails_helper.rb)
After the Rails environment boots, one require applies the shared Rails test configuration:
require 'spec_helper'
require_relative '../config/environment'
require 'rails_quality_assurance/rails_helper'That sets up, without any boilerplate in the app:
-
ActiveRecord::Migration.maintain_test_schema!(aborts on pending migrations) -
use_transactional_fixtures, fixture paths, andinfer_spec_type_from_file_location! filter_rails_from_backtrace!- FactoryBot syntax methods and ActiveSupport time helpers
- WebMock network lockdown with a loopback + Playwright allowlist
-
I18n.locale = :enbefore each example;Faker::UniqueGeneratorreset after each - CSRF forgery protection toggled on around system specs
Application-specific setup (auth helpers, custom matchers, gateway stubs) stays in the app's own rails_helper.rb.
4. Continuous Integration: bin/ci
Run the generator to install bin/ci and config/ci.rb:
bin/rails generate rails_quality_assurance:ciThis generates bin/ci (executable) and config/ci.rb following Develoz standards.
Run CI anytime on the host:
bin/ciOr in Docker-based setups:
bin/run bin/ci5. Pre-commit hook
Run the generator to install a project-owned hook that runs RuboCop, Reek, and RSpec before each commit:
bin/rails generate rails_quality_assurance:pre_commitIt writes an executable .githooks/pre-commit with the checks and wires it into
the repository:
- If
.git/hooks/pre-commitis free, it installs a small launcher that forwards to.githooks/pre-commit. - If a shared hook manager already owns
.git/hooks/pre-commit(and chains to.githooks/pre-commitwhen present), the generator leaves it untouched. The project checks run through the chain and the manager's other hooks, such asprepare-commit-msg, keep working.
No core.hooksPath is set, so hooks Git resolves through .git/hooks are
unaffected. If core.hooksPath is already set (for example by Husky), the
generator aborts instead of installing a hook that would never run.
Each check runs only when relevant files are staged (Ruby files for RuboCop and
Reek, spec files for RSpec). Bypass a commit with git commit --no-verify.
Reek is resolved per project shape: bin/rails qa:reek in an app,
bundle exec rake -f spec/dummy/Rakefile qa:reek in a gem or engine with a
dummy app, and bundle exec reek in a plain gem with a .reek.yml.
In container-based setups the checks must run inside the container, not on the
host. Pass --runner to prefix every generated command:
bin/rails generate rails_quality_assurance:pre_commit --runner bin/runThe hook then runs bin/run bundle exec rubocop and so on. Rerunning the
generator regenerates .githooks/pre-commit, so local edits to the file are
overwritten — configure the generator instead.
6. Rake Tasks (Optional)
The gem also provides tasks if you prefer rake:
bin/rails qa:lint # Runs RuboCop, Reek, Flay, Brakeman, bundler-audit, importmap-audit, JS audit
bin/rails qa:audit # Runs bundler-audit
bin/rails qa:audit:importmap # Runs the importmap audit when config/importmap.rb exists
bin/rails qa:audit:npm # Runs the JS dependency audit through the QA CLI
bin/rails qa:frontend # Runs Biome, Stylelint, smells and duplication
bin/rails qa:lint:typecheck # Runs tsc when the app has TypeScript
bin/rails qa:lint:deadcode # Runs knip when a knip config exists
bin/rails qa:lint:boundaries # Runs dependency-cruiser when a rules file exists
bin/rails spec:parallel # Runs RSpec in parallel with system specs isolatedspec:parallel reads these environment variables:
| Variable | Default | Purpose |
|---|---|---|
PARALLEL_TEST_PROCESSORS |
Number of CPUs | Total worker count |
ISOLATE_SPEC_TASKS |
1 (or 0 on a single CPU) |
Workers reserved for spec/system/
|
PARALLEL_SPEC_OPTIONS |
- | Extra options passed to RSpec |
Isolation workers are carved out of the total. On a 6-CPU machine the default
run uses 6 workers: 5 for the regular suite and 1 isolated worker for
spec/system/. Set ISOLATE_SPEC_TASKS=0 to skip isolation entirely; system
specs then run as ordinary specs.
spec:parallel takes an exclusive flock on tmp/qa/spec_parallel.lock for
the whole run. Starting a second run while one is in progress aborts
immediately with the holder's command, PID, and elapsed time instead of
doubling the worker count. The kernel releases the lock when the holder exits,
including on SIGKILL, so a killed run never leaves a stale lock behind; spawned
workers inherit the descriptor, so the lock stays held while any of them is
still alive.
Development
bundle install
bundle exec rspec
bundle exec rubocopLicense
Released under the MIT License.