SecondBrain/convention/phlex-component-design.md

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