This content originally appeared on DEV Community and was authored by Jan Peterka
What is Phlex?
If you are in the Ruby on Rails land, you might have noticed Phlex.
A little Ruby gem for building HTML and SVG view components.
, as it says on the website.
It was concieved by Joel Drapper as a new approach to view layer in Rails (and other web app frameworks, but I don't have any experience in that department).
Managing view layer in Rails app.
I'm pretty much new kid in Rails department (started with it ~5 years ago), so I don't feel like talking about the whole history of view stack in this framework. But even my experience with it (mostly in my ~15yr old $WORK project, and some new projects I started from scratch using Rails 7-8 ) is bit cumbersome:
- there's the "golden standard" -
erb
templating.- I don't like erb. It's just a lot of writing, and the code us just ugly in my view.
- at $WORK, we use
slim
instead, and I prefer that, so I mostly use that!- but for example when I was creating Rails generator I intend to publish, it makes sense to write it in
erb
, as much more people use that.
- but for example when I was creating Rails generator I intend to publish, it makes sense to write it in
- we also use ViewComponents, which I then used (mostly copy-pasted) in my other projects to save time.
- they are probably super powerful, but I have trouble understanding them. Also, I don't like having two files (
rb
+html.*
) for one thing.
- they are probably super powerful, but I have trouble understanding them. Also, I don't like having two files (
- and of course you have partials and helper
- I 'm currently very suspicous of helpers. They are great until you shove logic into them (which can happen easily).
Also, for a long time, there were no component libraries for Rails! That's currently changing, as I'll soon mention.
As you see, I was not super happy about the stuff I used for view layer, but there wasn't much to do about that.
So, when Phlex came by, I was curious - would this be better for me?
Different, in a good way?
I wasn't sure.
One of the big things that Phlex does differently is that you only write Ruby. No HTML. No erb
or slim
or other templating. It's all ruby code.
Don't get me wrong, I absolutely love Ruby, but at the same time, I'm bit sceptical about approaches that try to "get rid" of some language - I don't for example think you should write Ruby/Python/.. instead of javascript.
With this mix of scepticism and curiosity, when I started new project, I wanted to play with Phlex a bit.
What did help was that I found RubyUI, nice library of components made with Phlex, which did speed up the initial prototype development by a lot.
It took me a bit to get a grasp, and I stumbled multiple time, as I don't fully understand how it all works. Also, I still had basic layouts and used simple_form (I wanted to use superform, which looks amazing, but it didn't support Phlex 2.x at the time I started the project. It should now, so maybe I'll give it a try now!), so there's some mixing of old code with new, which is all doable, just slows one down at first.
But then?
I found out I love it.
I'll show an example of a page I got to rewrite to Phlex on a different project, where I now use classic approach + ViewComponents, and try to describe why Phlex makes sense to me now:
Here's original controller:
# app/controllers/dashboards_controller.rb
class DashboardsController < ApplicationController
def show
@current_events = Current.member.events.current
dashboard = Dashboard.new
@future_events = selected_future_events
@sboodles = []
@unread_announcements = Current.member.unread_announcements[..2]
@pinned_announcements = Current.member.pinned_announcements[..2]
@tasks = Current.member.assigned_tasks.incomplete[..2]
render Views::Dashboard::Show.new dashboard
end
private
def selected_future_events
nearest_performance = Current.member.events.future.includes(:type).find(&:performance?)
nearest_events = Current.member.events.future.limit(3)
if nearest_performance.present? && nearest_events.exclude?(nearest_performance)
(nearest_events[..1] << nearest_performance).compact
else
nearest_events.compact
end
end
end
# app/views/dashboards/show.html.slim
- content_for :title, "nástěnka"
- if @current_events.any?
= heading("právě probíhá")
= render "events/card", event: @current_events.first
- if @future_events.any?
= heading("nejbližší události")
.grid.md:grid-cols-3.gap-4
- @future_events.each do |event|
= render "events/card", event:
- if @sboodles.any?
= heading("aktivní sboodly")
- if @unread_announcements.any?
= heading("nové příspěvky")
.grid.md:grid-cols-3.gap-4
- @unread_announcements.each do |announcement|
= render "announcements/card", announcement:
- if @pinned_announcements.any?
= heading("připnuté příspěvky")
.grid.md:grid-cols-3.gap-4
- @pinned_announcements.each do |announcement|
= render "announcements/card", announcement:
- if @tasks.any?
= heading("nejbližší úkoly")
.grid.md:grid-cols-3.gap-4
- @tasks.each do |task|
= render "tasks/card", task:, editable: false
And here's a new, Phlex, one:
# app/controllers/dashboards_controller.rb
class DashboardsController < ApplicationController
def show
dashboard = Dashboard.new
render Views::Dashboard::Show.new dashboard
end
end
# app/views/dashboard/show.rb
class Views::Dashboard::Show < Views::Base
prop :dashboard, Dashboard, :positional, reader: :private
def view_template
content_for :title, "dashboard"
current_event
future_events
active_sboodles
unread_announcements
pinned_announcements
tasks
end
private
def future_events
return if dashboard.future_events.empty?
Heading "future events"
grid do
dashboard.future_events.each do |event|
EventCard event
end
end
end
...
# there are of course all the methods, I just don't want to list them all
end
Ok, so multiple interesting things are happening here, not all Phlex specific, but Phlex (+ Literal) did definitely push me it the right direction:
Controller got much smaller
I love small controllers. I have a strong belief that the controller method should only do one thing (not counting loading and rendering/redirecting), preferrably on one line of code.
In here, I didn't previously follow this directive. I got lazy. I just kept adding instance variables, because the template will just pick them up. It doesn't cost me anything.
Sidenote: I like how this interface is made explicit in partials using strict locals. But for the "main" view, there's nothing.
Well, with Phlex it does. Because I'm explicitely passing values to method:
1️⃣ on the controller side I have to edit render Views::Dashboard::Show.new dashboard
2️⃣ on the view side I have to specify incoming attributes
If you are confused about what's prop :dashboard, Dashboard, :positional, reader: :private
about, it's from different gem (Literal) by Joel Drapper, which works great with Phlex. I could do the same with
def initialize(dashboard)
@dashboard = dashboard
end
private
attr_reader :dashboard
but Literal Properties makes this much simpler, and I get type checking "for free".
So it made sense to finally make a "presenter" object Dashboard
to pass around, which is what I wanted to do anyway, just didn't have that much reason to.
View can have layers of abstraction
I'm a bit fan of having only one (and the same) layer of abstraction in one place. So I like that my view_template
now describes basic structure of my dashboard. You can look at the code and just know what to expect. Then, if you need to dig deeper, you do, very easily!
All in one place
Very easily indeed, as you have all the code in same place! Using traditional templates, I did have show.html.slim
, and then all the partials.
Now the "partials" are in same place.
These last points show why I did get to like having all in Ruby - you can use all the Ruby tricks and patters and it also leads me to keeping my code clean, much cleaner then I do in templates (where I'm just like "fuck it" and do anything necessary half the time). This is only my personal failing, not any objective problem with templates! But if there's a way to nudge me into writing better code? Yes please!
You can notice multiple more ways this did force me to make the code bit nicer:
grid
is just a very simple shared method:
def grid(cols: 3, &)
div(class: "grid md:grid-cols-#{cols} gap-2 mb-3", &)
end
as I 1️⃣ didn't like to repeat the code, and 2️⃣ also I don't like the look of long list of classes in my code (maybe I shouldn't use Tailwind than I hear you cry? Well, I like it for fast prototyping and changing, and when contained in components and helpers, it doesn't bother me!)
Then I also created component EventCard
, but I already wrote a lot of code, so maybe let's not get into that (it's just - again, in my view - more elegant than using ViewComponent, but not that different).
Ok, let's wrap this up.
I wanted to share my exploration of Phlex as a different way of managing both components and views themselves. You can also choose to use Phlex only for components (which I originally planned to), but I got very much convinced that using it for views makes my code better and myself happier.
Here's a summary of whys:
1️⃣ I enjoy having one Ruby file instead of Ruby + HTML for components
2️⃣ I love how Phlex makes it soo easy to work with "components", and bit cumbersome to work with anything else, that it basically forces me to write everything that way - which gives me reusable and consistent elements and layouts!
3️⃣ I love using private methods for parts of the page (all in one file) instead of partials (that you have to search through).
4️⃣ Literal Properties are super convenient with both components and views
5️⃣ And using them nudges me into very clean and simple interfaces, following patterns that I believe in, but sometimes don't follow fully.
Do you have experience with Phlex? Do you like it? I'd love to hear from you!
This content originally appeared on DEV Community and was authored by Jan Peterka

Jan Peterka | Sciencx (2025-07-26T12:33:09+00:00) Using Phlex helps me be a better programmer. Retrieved from https://www.scien.cx/2025/07/26/using-phlex-helps-me-be-a-better-programmer/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.