DEV Community

Jan Peterka
Jan Peterka

Posted on

Using Phlex helps me be a better programmer

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.
  • 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.
  • 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 
Enter fullscreen mode Exit fullscreen mode
# 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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode
# 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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

At work, we started migrating much of our code to phlex over these last few weeks, and it is about 95% perfect.

I do have some minor nitpicks about it, mostly that I'd like it to be a bit more functional, but overall that's really just a "I would have done things slightly differently", not a "this is wrong".

I'm still very happy that I can finally split code up along the same lines I do in other languages, which was really hard to do with classic rails partials, since they had to be in separate files and defining re-usable bits of code within one document was extremely tedious.

One of the big things that Phlex does differently is that you only write Ruby.

This was a big factor for me too, but in my case, it was a big plus from the start. Ruby code just works with my language server, gives proper stack traces, interacts more easily with other ruby code, etc.

I've never quite liked the separation of templating from programming languages for any kind of logic-intensive rendering. When you really only need some glorified string interpolation for your HTML document, it's fine; but once you start splitting your view code into re-usable chunks, it should just use the same language as your application. For your really critical code, you can always render stuff into an intermediary representation, like caching composed components into one bigger erb template that you can later insert the last bits of data into.

For context: I built my own, somewhat similar library for generating HTML in Lua, and the main difference there is that content gets passed as arguments (arrays allowed :D) and elements are returned. In practice, this adds relatively little overhead but makes it much easier to reason about what goes where.