2.9 KiB
| type | title | summary | tags | scope | last_updated | date | related | source | ||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| convention | Phlex Component Design | 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. |
|
global | 2026-07-13 | 2026-07-13 |
|
hyperthrive_dev |
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
templateinstead ofview_template→ wrong lifecycle hook name, silently no-ops include ActionView::Helpers::*directly → use thePhlex::Rails::Helpers::*adapters instead- Overriding a lifecycle hook (e.g.
before_render) without callingsuper→ breaks the parent's setup silently - Building markup inside
initialize→ markup belongs only inview_template;initializeis for storing dependencies render raw(user_input)→ reintroduces XSS; wrap trusted HTML insafe()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