cover-letter-llm

This is a comprehensive and robust GitHub Actions YAML snippet guide for this Ruby on Rails project.

This snippet will cover:

  1. Running on pushes to main and pull requests targeting main.
  2. Setting up Ruby using a matrix for multiple Ruby versions (good practice).
  3. Setting up services: PostgreSQL (as you specified) and Redis (for Sidekiq).
  4. Caching Bundler gems and Yarn/NPM packages (if you’re using Webpacker/Shakapacker).
  5. Installing dependencies.
  6. Setting up the test database.
  7. Running RuboCop for linting.
  8. Running RSpec tests.

Here’s the .github/workflows/ci.yml snippet:

# .github/workflows/ci.yml

name: Ruby on Rails CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    name: Test (Ruby $)
    runs-on: ubuntu-latest
    # Services to spin up before the job starts
    # The service name (e.g., 'postgres', 'redis') will be the hostname
    # for accessing the service from within the runner.
    services:
      postgres:
        image: postgres:14-alpine # Or your preferred PostgreSQL version
        env:
          POSTGRES_USER: rails_user      # User for CI
          POSTGRES_PASSWORD: rails_password # Password for CI
          POSTGRES_DB: cover_letter_app_test_ci # Distinct DB name for CI
        ports:
          - 5432:5432 # Map container port 5432 to host port 5432
        # Health check to ensure postgres is ready before tests run
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
      redis:
        image: redis:7-alpine # Or your preferred Redis version
        ports:
          - 6379:6379 # Map container port 6379 to host port 6379
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    strategy:
      fail-fast: false # Don't cancel other jobs in the matrix if one fails
      matrix:
        ruby-version: ['3.1', '3.2', '3.3'] # Specify Ruby versions to test against

    # Environment variables available to all steps
    env:
      RAILS_ENV: test
      DATABASE_URL: "postgres://rails_user:rails_password@localhost:5432/cover_letter_app_test_ci"
      REDIS_URL: "redis://localhost:6379/1" # Use a different Redis DB number for tests if needed
      # If your tests make real calls to Google AI API (discouraged for most tests - mock them!)
      # You would need to set this secret in your GitHub repository settings
      # GOOGLE_API_KEY: $

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Ruby $
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: $
          bundler-cache: true # Automatically runs bundle install and caches gems
          working-directory: CoverLetterApp

      # Optional: If you're using Webpacker/Shakapacker or any JS managed by Node.js
      - name: Set up Node.js (if using Webpacker/Shakapacker)
        uses: actions/setup-node@v4
        with:
          node-version: '18' # Or your project's Node.js version
          cache: 'yarn'      # Or 'npm' if you use npm

      - name: Install JavaScript dependencies (if using Webpacker/Shakapacker)
        # Only run if you have a yarn.lock or package-lock.json
        if: |
          (
            (steps.setup-node.outputs.cache-hit != 'true' || steps.setup-node.outputs.cache-hit == '') &&
            (hashFiles('yarn.lock') != '' || hashFiles('package-lock.json') != '')
          )
        run: |
          if [ -f yarn.lock ]; then
            yarn install --frozen-lockfile
          elif [ -f package-lock.json ]; then
            npm ci
          fi

      # Configure database (if your config/database.yml doesn't automatically use DATABASE_URL)
      # A common pattern is to have a config/database.yml.ci file and copy it
      # Or ensure your default config/database.yml can read DATABASE_URL
      # - name: Configure database
      #   run: cp config/database.yml.ci config/database.yml

      - name: Create and migrate database
        run: |
          bundle exec rails db:prepare
          # For some setups, `db:schema:load` might be preferred/faster if db:prepare doesn't work as expected
          # bundle exec rails db:create
          # bundle exec rails db:schema:load

      - name: Run RuboCop
        run: bundle exec rubocop

      - name: Run RSpec tests
        run: bundle exec rspec

Explanation and Key Points:

  1. name: Ruby on Rails CI: The name of your workflow.
  2. on:: Defines triggers. This runs on pushes to main and pull requests targeting main.
  3. jobs:: Defines one or more jobs. We have one job named test.
  4. runs-on: ubuntu-latest: Specifies the runner environment.
  5. services::
    • postgres: Sets up a PostgreSQL container.
      • image: Specifies the Docker image.
      • env: Sets environment variables inside the PostgreSQL container (like user, password, db name).
      • ports: Maps the container’s port 5432 to the runner’s port 5432. This means your Rails app in the runner can connect to localhost:5432.
      • options --health-cmd: A crucial check to ensure PostgreSQL is actually ready to accept connections before proceeding.
    • redis: Sets up a Redis container, similar to PostgreSQL.
  6. strategy: matrix::
    • Allows you to run the same job with different configurations. Here, we test against multiple ruby-versions.
    • fail-fast: false: Ensures that if one Ruby version fails, the tests for other Ruby versions still run.
  7. env:: Sets environment variables for the job steps.
    • RAILS_ENV: test: Standard for running tests.
    • DATABASE_URL: Your Rails app will use this to connect to the PostgreSQL service. The hostname is localhost because the service container’s port is mapped to the runner’s localhost.
    • REDIS_URL: For Sidekiq to connect to the Redis service.
    • GOOGLE_API_KEY: Important: For most of your tests (unit, integration), you should mock calls to external APIs like Google Generative AI. This makes tests faster, more reliable, and avoids API costs during testing. If you have specific end-to-end tests that must hit the real API in CI (use sparingly), you’d store the API key as a secret in your GitHub repository (Settings > Secrets and variables > Actions > New repository secret) and reference it here.
  8. steps:: The actual commands to run.
    • actions/checkout@v4: Checks out your repository code.
    • ruby/setup-ruby@v1: Sets up the specified Ruby version and uses bundler-cache: true to automatically install gems from your Gemfile.lock and cache them for faster subsequent runs.
    • Node.js Setup (Optional): If you use Webpacker or Shakapacker (as mentioned in your initial setup with the webpacker gem), you’ll need Node.js and to install JS dependencies. This step is conditional.
    • Database Setup:
      • The DATABASE_URL environment variable should ideally be picked up by your config/database.yml. If your config/database.yml is set up to read ENV['DATABASE_URL'] for the test environment, you might not need to copy a CI-specific database.yml file.
      • bundle exec rails db:prepare: This command is often sufficient. It creates the database if it doesn’t exist, and loads the schema. If it gives you trouble, you can fall back to the more explicit db:create and db:schema:load. Using db:schema:load is generally faster in CI than db:migrate as it loads the structure directly from db/schema.rb.
    • Run RuboCop: Runs your linter.
    • Run RSpec tests: Executes your test suite.

Before using this:

  1. GitHub Secrets: If you do need GOOGLE_API_KEY for any CI tests, add it as a secret in your GitHub repository settings.
  2. config/database.yml: Ensure your config/database.yml for the test environment can use the DATABASE_URL environment variable. A typical setup looks like this:

    # config/database.yml
    default: &default
      adapter: postgresql
      encoding: unicode
      pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
    
    development:
      <<: *default
      database: CoverLetterApp_development
      # ... your local dev settings
    
    test:
      <<: *default
      # This will be overridden by DATABASE_URL if present
      database: CoverLetterApp_test 
      # For CI, DATABASE_URL will be:
      # url: <%= ENV['DATABASE_URL'] %>
    

    Rails 6+ usually handles DATABASE_URL automatically if it’s present, so your existing test block might just need:

    test:
      <<: *default
      database: CoverLetterApp_test_ci # This will be overridden by DATABASE_URL
    

    The key is that ENV['DATABASE_URL'] takes precedence.

  3. Node.js/Yarn/NPM: If you’re not using Webpacker/Shakapacker or have no JavaScript dependencies managed this way, you can remove the “Set up Node.js” and “Install JavaScript dependencies” steps. Modern Rails apps often use import maps or Propshaft, which might not require this. Since your initial setup mentioned webpacker gem, I included it.