The CI you already have
This application has a CI pipeline. It was generated by rails new, it has been running on every pull request since the first commit, and most people who work on a Rails app have never opened it.
Open it. .github/workflows/ci.yml starts like this:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
scan_ruby:
runs-on: ubuntu-latest
Four ideas make up the whole model.
The event: on: says when this file runs. Here, that is every pull request and every push to main. Nothing else triggers it, so a push to a feature branch with no open PR runs nothing.
The job: a named unit that gets its own fresh machine. scan_ruby is one; this file has three.
The runner: runs-on: ubuntu-latest is a virtual machine GitHub creates for that job and destroys when it finishes. Nothing survives between jobs unless you deliberately move it.
The steps: an ordered list inside a job. Each step is either uses: (someone else's action) or run: (a shell command).
flowchart TB
E["event: pull_request"] --> W["workflow: CI"]
W --> J1["job: scan_ruby"]
W --> J2["job: scan_js"]
W --> J3["job: lint"]
J1 --> R1["fresh ubuntu-latest VM"]
J2 --> R2["fresh ubuntu-latest VM"]
J3 --> R3["fresh ubuntu-latest VM"]
Three jobs, three machines, all at the same time. Jobs are parallel by default, which is why this file finishes in about the time of its slowest job rather than the sum of all three.
What the three jobs do
Every job in this file has the same four steps, and only the last one differs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Install libvips
run: sudo apt-get update && sudo apt-get install -y libvips42
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Lint code for consistent style
run: bin/rubocop -f github
scan_rubyrunsbin/brakeman --no-pager: static analysis for Rails security problems.scan_jsrunsbin/importmap audit: known vulnerabilities in pinned JavaScript.lintrunsbin/rubocop -f github: style checks, with-f githubso violations appear as annotations on the diff instead of only in the log.
Two details in the shared steps deserve a name.
ruby-version: .ruby-version reads the version from the file the rest of your tooling already reads. The version is stated once, in this repository 4.0.1, and CI cannot drift from your machine.
bundler-cache: true tells ruby/setup-ruby to run bundle install and cache the result, keyed on Gemfile.lock. Without it, every job on every push reinstalls every gem from scratch. That one line accounts for most of your pipeline's speed.
The gap
Read the file to the end and count the jobs: scan_ruby, scan_js, lint. Now count the ones that run bundle exec rspec.
There are none. This repository has a full spec/ directory (models, requests, services, policies, queries, system specs), and its CI does not run it. A pull request that breaks every test in the suite gets three green checks.
That is common, and Rails' generator is not to blame: the generated file ships a test job for Minitest. When a project moves to RSpec, that job is often edited out and never replaced. The result is a pipeline that verifies style and security and stays quiet about correctness.
Lesson 2 fixes it, and the fix is more interesting than it sounds, because tests need a database and a job's fresh machine does not have one.
What this course covers
- The CI you already have (you are here).
- Adding the job that runs the tests.
- Making it fast.
- What a green check is worth.
- Secrets, permissions, and the token.
- From CI to CD.
- Putting it together: the full pipeline.
Key takeaways
- A workflow is events, jobs, runners and steps;
.github/workflows/ci.ymlin this repository has all four. - Jobs run in parallel on separate, disposable machines and share nothing by default.
ruby-version: .ruby-versionkeeps CI and your machine on one version;bundler-cache: trueis most of the pipeline's speed.- This application's CI scans and lints but never runs its RSpec suite, so a green check today says nothing about correctness.
Keep going with lesson 2
Adding the job that runs the tests
Adds a test job with a PostgreSQL service container, health checks, libpq variables, db:test:prepare and Chrome for system specs.
A free account also gives you the quizzes, your progress and notes, and the discussion.