Prohlížeč zdrojového kódu
docs/specs/examples.md
# Spec: Examples System
- **Status:** active
- **Created:** 2026-03-05
- **Related code:** `app/examples/`, `app/services/example_entry.rb`, `app/services/example_registry.rb`, `app/controllers/examples_controller.rb`, `app/views/examples/`
## Overview
The examples system lets visitors browse a curated set of Ruby code examples. Each example has metadata (title, description, tags) and optionally a source file displayed with syntax highlighting. Examples are grouped by tags and listed on an index page; clicking one opens a detail page. No database is involved — the filesystem is the single source of truth.
## Behaviour
### Storage
- Each example lives in its own subdirectory under `app/examples/<slug>/`.
- The subdirectory name is the example's slug (used in URLs).
- Every example directory contains an `example.yml` (metadata) and an `example.md` content file. A `.rb` source file is optional.
### Metadata (`example.yml`)
Required fields: `title`.
Optional fields: `source_file`, `description` (defaults to `""`), `tags` (defaults to `[]`), `position` (defaults to `999`), `scenarios`.
- `title` and `description` are in Czech; option values stay in English.
- `position` controls listing order; lower numbers appear first. Examples without a position are sorted last.
- Tags are free-form strings in Czech used for filtering.
- Each scenario must define a `boilerplate_file`: an explicit filename (relative to the example directory) containing the runner snippet shown in the "Vyzkoušet" section.
### Index page (`/examples`)
- Lists all examples as cards showing title, description, and tags.
- When tags exist, a tag filter bar appears above the grid with a "Vše" (all) link and one link per tag.
- Filtering by tag shows only matching examples; the active filter is highlighted.
- If no examples match, a Czech message is shown (different wording when filtering by tag vs. no examples at all).
### Detail page (`/examples/:slug`)
- Displays the example's title, tags, and rich content from `example.md`.
- If `source_file` is present, the `<!-- source -->` placeholder in `example.md` is replaced with the syntax-highlighted source. Using `<!-- source -->` in an example without a `source_file` raises an error.
- Examples without a `source_file` use `example.md` as the sole content driver, with code snippets embedded directly as fenced code blocks in the Markdown. This suits complex, multi-file topics ("case studies") where there is no single file to showcase.
- If the example has scenarios, each `<!-- scenario:N -->` or `<!-- scenarios -->` placeholder is replaced with the scenario card — details covered in the scenario runner spec. Scenarios in a source-file-less example must be fully self-contained.
- Unknown slugs raise a 404.
### Registry
- Examples are loaded at boot into an in-memory singleton (`ExampleRegistry`).
- `ExampleRegistry.all` returns all entries sorted by `position`.
- `ExampleRegistry.find_by_slug(slug)` returns an entry or `nil`.
- `ExampleRegistry.filter_by_tag(tag)` returns matching entries sorted by `position`.
- `ExampleRegistry.tags` returns all unique tags, sorted alphabetically.
- `ExampleRegistry.reload!` rebuilds the singleton (used in tests and development).
## Implementation Notes
- `app/examples/` is excluded from Zeitwerk autoloading (configured in `config/application.rb`) — example `.rb` files are loaded manually by `ScenarioRunner` inside an anonymous `Module`.
- `ExampleRegistry` is a singleton class; the instance is memoized on first access via `ExampleRegistry.instance`.
- `ExampleEntry` is a plain Ruby object (not ActiveRecord); `Scenario` and `Input` are inner Structs.
- The registry reads all `example.yml` files at boot using `Dir[examples_root.join("*", "example.yml")]`.
- `source_file` is optional; `ExampleEntry` stores `nil` when the key is absent in `example.yml`. `ExampleEntry#source_code` returns `nil` in that case.
- Source code is read from disk on each request via `ExampleEntry#source_code`.
- `boilerplate_file` is resolved to an absolute path at parse time in `build_scenarios`; `Scenario#boilerplate_code` reads it on demand via `File.read`.
- `entry_class` and `entry_method` are no longer part of `ExampleEntry` or `Scenario`.
## Tests
- `spec/services/example_registry_spec.rb` — loading, lookup, tag filtering, sorting, reload
- `spec/services/example_entry_spec.rb` — attribute parsing, source file path, default position
## See Also
- [Examples Runner](examples-runner.md) — defines how scenarios within examples are executed interactively.
- [Rich Example Content](example-content.md) — Markdown-driven detail page layout replacing the fixed description/source/scenarios structure.
- [Home Page](home-page.md) — displays a preview of up to six examples from the registry on the landing page.
## Change Log
- 2026-03-05: Initial retrospective spec.
- 2026-03-09: Prospective update — added boilerplate_file per scenario; removed top-level entry_class/entry_method; updated detail page source display behaviour.
- 2026-03-23: Prospective update — `source_file` made optional; examples without it use `example.md` as sole content driver (case studies); `<!-- source -->` silently removed when absent.