# Rails Lesson Recipes

> Use this skill whenever creating a new lesson, choosing a lesson pattern, or validating lesson structure. Trigger on: 'create a lesson', 'lesson recipe', 'lesson pattern', 'terminal-only lesson', 'code editing lesson', 'database lesson', 'console lesson', 'lesson sequence', 'multi-step lesson', 'validate lesson', 'check lesson', or asking how to structure a Rails lesson — even without mentioning recipes. Provides five tested blueprints (terminal-only, code-editing, database, full-app, console/IRB) with correct frontmatter, directory layouts, content examples, and a post-creation validation checklist. Do NOT create lessons without this skill — incorrect structure causes silent failures. Do NOT use for frontmatter reference (use tutorial-lesson-config) or file organization (use rails-file-management).

- Skill: `palkan/rails-lesson-recipes` (Agent Skill)
- Install (CLI): `npx skillmds@latest add palkan/rails-lesson-recipes`
- Raw SKILL.md: https://api.skillmd.com/api/skills/palkan/rails-lesson-recipes/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: palkan (https://skillmd.com/u/palkan)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/palkan/rails-lesson-recipes

---


# Rails Lesson Recipes

Ready-to-use patterns for common Rails tutorial lesson types.

## Recipe 1: Terminal-Only Lesson

**Use for:** Running generators, using the console, exploring CLI tools.
**Example:** `rails new`, `rails console`, `rails generate`.

### Directory Structure

```
1-creating-your-first-rails-app/
  content.md
  _files/
    workspace/
      .keep
```

### Frontmatter

```yaml
---
type: lesson
title: Creating your first Rails app
editor: false
custom:
  shell:
    workdir: "/workspace"
---
```

### Key Decisions

- `editor: false` — the user only interacts via terminal
- `previews: false` — inherited from tutorial root (no server running)
- Empty `workspace/.keep` — user creates the app from scratch
- `workdir: "/workspace"` — terminal starts at the workspace root (before app exists)

### Content Pattern

```markdown
# Creating Your First Rails App

Run the following command to generate a new Rails application:

\`\`\`bash
$ rails new store
\`\`\`

:::info
This may take a moment as Rails generates the application structure.
:::

After your new application is created, switch to its directory:

\`\`\`bash
$ cd store
\`\`\`
```

---

## Recipe 2: Code-Editing Lesson (with Preview)

**Use for:** Writing controllers, models, views, routes — any lesson where the user edits code and sees results.
**Example:** Adding a new action, modifying a view, updating routes.

### Directory Structure

```
3-adding-a-controller/
  content.md
  _files/
    .tk-config.json
    workspace/
      app/
        controllers/
          pages_controller.rb       ← starter/skeleton code
      config/
        routes.rb
  _solution/
    workspace/
      app/
        controllers/
          pages_controller.rb       ← completed code
        views/
          pages/
            home.html.erb           ← new file the user creates
      config/
        routes.rb
```

### Frontmatter

```yaml
---
type: lesson
title: Adding a Controller
focus: /workspace/app/controllers/pages_controller.rb
previews: [3000]
mainCommand: ['node scripts/rails.js server', 'Starting Rails server']
custom:
  shell:
    workdir: '/workspace'
---
```

### `.tk-config.json`

```json
{
  "extends": "../../../../../templates/rails-app"
}
```

### Key Decisions

- `focus` opens the file the user will edit
- `previews: [3000]` shows the Rails app in a preview pane
- `mainCommand` starts the Rails server after prepare commands finish
- `_solution/` includes both modified files and new files the user was asked to create
- Inherits `prepareCommands` (npm install) from tutorial root

### Content Pattern

```markdown
# Adding a Controller

Open `app/controllers/pages_controller.rb` in the editor. Add a `home` action:

\`\`\`ruby title="app/controllers/pages_controller.rb" ins={2-4}
class PagesController < ApplicationController
  def home
    @message = "Welcome to our store!"
  end
end
\`\`\`

Now create the view at `app/views/pages/home.html.erb`:

\`\`\`erb title="app/views/pages/home.html.erb"
<h1><%= @message %></h1>
\`\`\`

:::tip
Click **Solve** to see the completed code if you get stuck.
:::
```

---

## Recipe 3: Database Lesson

**Use for:** Migrations, models, seeds, ActiveRecord queries.
**Example:** Creating a model, running migrations, seeding data.

### Directory Structure

```
1-creating-a-model/
  content.md
  _files/
    .tk-config.json
    workspace/
      db/
        migrate/
          20240101000000_create_products.rb
        seeds.rb
      app/
        models/
          product.rb
```

### Frontmatter

```yaml
---
type: lesson
title: Creating a Product Model
focus: /workspace/app/models/product.rb
previews: [3000]
mainCommand: ['node scripts/rails.js server', 'Starting Rails server']
prepareCommands:
  - ['npm install', 'Preparing Ruby runtime']
  - ['node scripts/rails.js db:prepare', 'Prepare development database']
terminalBlockingPrepareCommandsCount: 2
custom:
  shell:
    workdir: '/workspace'
---
```

### Key Decisions

- `prepareCommands` includes `db:prepare` — this runs migrations and seeds **before** the lesson becomes interactive
- Migration files must have timestamps in filenames (Rails convention)
- Seeds provide starting data so the user sees something immediately
- Override `prepareCommands` at the lesson level (doesn't inherit the tutorial root's single command)

### Content Pattern

```markdown
# Creating a Product Model

Your Product model is defined in `app/models/product.rb`. Let's add validations:

\`\`\`ruby title="app/models/product.rb"
class Product < ApplicationRecord
  validates :name, presence: true
  validates :price, numericality: { greater_than: 0 }
end
\`\`\`

The migration has already been run. Try it in the console:

\`\`\`bash
$ bin/rails console
\`\`\`

\`\`\`irb
store(dev)> Product.create(name: "Widget", price: 9.99)
store(dev)> Product.all
\`\`\`
```

---

## Recipe 4: Full-App Lesson (Pre-Built State)

**Use for:** Teaching concepts that require a fully scaffolded app — CRUD, associations, authentication.
**Example:** CRUD operations on an existing scaffold.

### Directory Structure

```
2-crud-operations/
  content.md
  _files/
    .tk-config.json
    workspace/
      .keep
```

### Frontmatter

```yaml
---
type: lesson
title: CRUD Operations
focus: /workspace/app/controllers/products_controller.rb
previews: [3000]
mainCommand: ['node scripts/rails.js server', 'Starting Rails server']
prepareCommands:
  - ['npm install', 'Preparing Ruby runtime']
  - ['node scripts/rails.js db:prepare', 'Prepare development database']
terminalBlockingPrepareCommandsCount: 2
custom:
  shell:
    workdir: '/workspace'
---
```

### `.tk-config.json`

```json
{
  "extends": "../../../../../templates/crud-products"
}
```

### Key Decisions

- Uses `crud-products` template which provides a complete scaffolded app
- `_files/` only contains `.tk-config.json` and a `.keep` — the template provides everything
- `focus` points to the file being discussed in the lesson
- No `_solution/` needed — this is an exploration/explanation lesson, not a coding exercise

---

## Recipe 5: Console/IRB Lesson

**Use for:** Interactive Ruby exploration, testing models, querying the database.

### Directory Structure

```
2-rails-console/
  content.md
  _files/
    .tk-config.json
    workspace/
      .keep
```

### Frontmatter

```yaml
---
type: lesson
title: Rails Console
custom:
  shell:
    workdir: "/workspace"
---
```

### Key Decisions

- No `editor`, no `previews`, no `mainCommand` — user works entirely in the terminal
- Inherits `editor: true` by default but doesn't need to set `editor: false` since the user can still browse files
- Uses a pre-built template via `.tk-config.json` so there's an app to explore

---

## Building Lesson Sequences

### Progressive State Pattern

For a series of lessons that build on each other, use **template layering**:

```
src/templates/
  default/                  ← Base WASM runtime
  rails-app/                ← Empty Rails app (extends default)
  blog-base/                ← Blog app with Post model (extends rails-app)
  blog-with-comments/       ← Blog app + Comment model (extends blog-base)

src/content/tutorial/
  1-blog/
    meta.md                 ← type: part
    1-setup/
      content.md            ← Uses rails-app template
      _files/.tk-config.json → ../../../../../templates/rails-app
    2-posts/
      content.md            ← Uses blog-base template
      _files/.tk-config.json → ../../../../../templates/blog-base
    3-comments/
      content.md            ← Uses blog-with-comments template
      _files/.tk-config.json → ../../../../../templates/blog-with-comments
```

Each template captures the **expected state** at the start of that lesson. This way:
- Users can jump to any lesson without completing previous ones
- Each lesson starts from a known-good state
- Template inheritance keeps file duplication minimal

### When to Create a New Template vs. Use `_files/`

| Create a Template | Use `_files/` |
|-------------------|---------------|
| State is reused by 3+ lessons | Only this lesson needs these files |
| State is complex (many files) | Just 1-3 files differ from the template |
| You want users to jump to this lesson directly | Sequential lessons where order matters |

### Lesson-to-Lesson Expectations

**Always assume each lesson starts fresh.** The WASM runtime reinstalls on lesson navigation. Design each lesson to be self-contained:

1. Use `prepareCommands` for setup (npm install, db:prepare) and `terminalBlockingPrepareCommandsCount: <number of commands>` to ensure terminal is not used before the prepare commands finish.
2. Use templates or `_files/` for starting code state
3. Don't rely on user actions from a previous lesson

## Post-Creation Checklist

After creating a lesson, verify these structural requirements. Violations cause silent failures at runtime.

### Required

- [ ] `content.md` exists in the lesson directory and has `type: lesson` in frontmatter
- [ ] `content.md` has a `title` in frontmatter
- [ ] All file paths in `_files/` start with `workspace/` (not `/workspace/`)
- [ ] All file paths in `_solution/` start with `workspace/` (not `/workspace/`)
- [ ] If `.tk-config.json` exists in `_files/`, its `extends` path resolves to an existing template directory under `src/templates/`
- [ ] `_solution/` mirrors the `_files/` directory structure (paths must match for "Solve" to replace correctly)
- [ ] `custom.shell.workdir` is set on the lesson itself (it does NOT inherit from parent `meta.md`)

### If previews are enabled

- [ ] `previews` is set (e.g., `previews: [3000]`)
- [ ] `mainCommand` is set to start the server (e.g., `['node scripts/rails.js server', 'Starting Rails server']`)
- [ ] `prepareCommands` includes `npm install` (either inherited from tutorial root or set explicitly)

### If database is used

- [ ] `prepareCommands` includes **both** `npm install` and `node scripts/rails.js db:prepare` (remember: arrays replace, not merge)
- [ ] Migration files have timestamp prefixes in filenames
- [ ] Seeds file exists if the lesson needs starting data

### If focus is set

- [ ] `focus` path is absolute from WebContainer root (e.g., `/workspace/app/...`)
- [ ] The file at `focus` path exists in `_files/` or in the referenced template
- [ ] `editor` is not set to `false` (focus is silently ignored when editor is hidden)

### Path verification

- [ ] `.tk-config.json` `extends` uses correct `../` count: 4 + number of intermediate levels above the lesson (typically 5 for `tutorial/part/lesson/_files/`)
- [ ] `focus` paths use `/workspace/...` (absolute), while `_files/` paths use `workspace/...` (relative, no leading slash)
- [ ] `scope` paths use `/workspace/...` (absolute)

