55 lines
2.9 KiB
Markdown
55 lines
2.9 KiB
Markdown
---
|
|
type: convention
|
|
title: Phlex Component Design
|
|
summary: Rules for building Rails UI components in Phlex (pure-Ruby templating) instead of ERB/ViewComponent — when to use it, how to structure and test a component, and the common lifecycle-hook mistakes.
|
|
tags:
|
|
- type/convention
|
|
- domain/software-design
|
|
- tool/phlex
|
|
- tool/rails
|
|
scope: global
|
|
last_updated: 2026-07-13
|
|
date: 2026-07-13
|
|
related:
|
|
- sandi-metz-code-philosophy
|
|
- tdd-methodology
|
|
source: hyperthrive_dev/conventions/phlex.md
|
|
---
|
|
|
|
# Phlex Component Design
|
|
|
|
## Purpose
|
|
Governs when to reach for Phlex over ViewComponent/ERB, and how to structure a Phlex component so it stays testable and safe by default.
|
|
|
|
## Core Principles
|
|
|
|
**1. Every component is a plain Ruby class with no template file.**
|
|
Phlex trades template-language flexibility for structural safety (XSS protection by default) and full unit-testability — treat a component like any other Ruby object with explicit dependencies.
|
|
|
|
**2. Explicit dependencies, injected via `initialize`.**
|
|
Pass all data a component needs through its constructor rather than reaching into globals or helpers. This is what makes a component testable in isolation.
|
|
|
|
**3. Sandi Metz limits apply.** Classes < 100 lines, methods < 5 lines, `initialize` params < 4 — a component doing more than one UI concern should be split. See [[sandi-metz-code-philosophy]].
|
|
|
|
**4. Coverage floor: ≥ 90%, covering init/render/conditional-branch/edge cases.**
|
|
A component's tests are the spec for its behavior; untested branches are unverified behavior in production markup.
|
|
|
|
## Patterns
|
|
|
|
**Choosing Phlex vs ViewComponent.** Use Phlex for anything new: components with slots/composition, or anything needing unit tests. Keep ViewComponent only for legacy `.html.erb` templates already in that form; don't introduce it for new work. One-off, never-reused markup doesn't need a component at all — inline it and extract only once duplicated.
|
|
|
|
**Test shape.** Cover four categories per component: initialization (params, defaults, edge cases), rendering (HTML structure/classes/text), conditional logic (every branch), and edge cases (nil/empty/boundary values).
|
|
|
|
## Anti-Patterns
|
|
|
|
- **Defining `template` instead of `view_template`** → wrong lifecycle hook name, silently no-ops
|
|
- **`include ActionView::Helpers::*` directly** → use the `Phlex::Rails::Helpers::*` adapters instead
|
|
- **Overriding a lifecycle hook (e.g. `before_render`) without calling `super`** → breaks the parent's setup silently
|
|
- **Building markup inside `initialize`** → markup belongs only in `view_template`; `initialize` is for storing dependencies
|
|
- **`render raw(user_input)`** → reintroduces XSS; wrap trusted HTML in `safe()` instead, never wrap untrusted input
|
|
|
|
## Related
|
|
|
|
- [[sandi-metz-code-philosophy]] — the size/responsibility limits Phlex components must obey
|
|
- [[tdd-methodology]] — the red-green-refactor loop these components are built with
|