Seb Wilgosz recently published an article Phlex with Hanami on Hanami Mastery. It made the latest Ruby Weekly issue as the first item and generally was quite well received. I write this post as an addendum to Seb's article, giving my perspective on using Phlex in Ruby applications.
Important note: you should probably read Seb's article first, as I don't do an introduction to Phlex here.
Establishing credentials
Who am I to talk? I have been using Phlex in my toy project, writing a forum engine in Hanami. I also added it to some pages in one of my oldest Rails projects, serving a small community in production for 10+ years (source code not available). It's not too much, but more than just playing around with hello worlds.
Phlex – a missing view layer we always needed
The most important characteristic of Phlex in my opinion is that, at least for Rails, it's a view layer we never had but always needed. Rails, despite stating that it's MVC, does not really offer much on the "V" front. It feels much more like a model-template-controller, where templates are dumb and typical responsibilities of a view are shoved into a model, controller or a separate kind of object (like form objects). Oh, and there are helpers that do not fit anywhere in this model.
Let's take a quick example. The requirement is:
User has an optional avatar attached. In the user profile view we display that avatar. If the avatar is not present, we use a placeholder image. However, if it's a VIP user, the placeholder image is different than the one for a regular user.
With "traditional Rails" I wouldn't be surprised if I saw it added as avatar_url
method of a User
model. The alternative is to put the logic directly in the ERB with if
s and else
s, or to use a helper method. The first issue here is that Rails does not really offer an answer on where to put that kind of logic. The second - that all these options are flawed in some way:
- In a model – this is a purely presentational thing, it simply does not belong in the model
- In an ERB file – it makes the template less readable, the logic is not reusable (unless you use partials) and perhaps more importantly: it's really hard to test, as Rails basically offers only testing views by executing the whole controller action
- In a helper – it is probably the right place, but many people will reject this as "not OOP enough"
How would that look in Phlex? (a proper view layer)
class UserProfile < Phlex::HTML
def initialize(user)
@user = user
end
def template
div(class: "user-profile") do
img(src: avatar_url)
end
end
private
def avatar_url
if @user.has_avatar?
@user.avatar_url(:small)
elsif @user.vip?
"/path/to/vip_avatar_placeholder.png"
else
"/path/to/avatar_placeholder.png"
end
end
end
That's it. this is just a Ruby object with a state (user is available via an instance variable) where we can define all kinds of helper methods we want. It's all collocated together, you don't need to do file-jumping in search of a definition of user_avatar_url
helper method. It's also testable.
Phlex is Ruby - it's extremely testable
To test the code above you don't need to instrument the whole request machinery. You don't even need a user persisted in the database, you also don't need to set up a session. It's a simple test, testing exactly what you need to test. It's also extremely fast to run.
require "phlex/testing/view_helper"
class TestHello < Minitest::Test
include Phlex::Testing::Nokogiri::FragmentHelper
def test_user_with_avatar
user = build_user(avatar: "some_file.png")
output = render UserProfile.new(user)
assert_equal "/path/to/avatars/#{user.id}/avatar.png", output.css("img").attr("src")
end
def test_user_without_avatar_but_vip
user = build_user(avatar: nil, vip: true)
output = render UserProfile.new(user)
assert_equal "/path/to/vip_avatar_placeholder.png", output.css("img").attr("src")
end
def test_user_without_avatar
user = build_user(avatar: nil, vip: false)
output = render UserProfile.new(user)
assert_equal "/path/to/avatar_placeholder.png", output.css("img").attr("src")
end
end
Phlex is Ruby - you can do Ruby things with it
Phlex views being pure Ruby classes mean that you can do everything what you could do with any other Ruby class. I, for example, use a mixin to include common typography helpers in a Phlex view.
class ArticlePartial < Phlex::HTML
include Ui::Typography
def initialize(article)
@article = article
end
def template
article(class: "article") do
heading1(@article.title)
subtitle(@article.motto)
div(class: "content") { @article.content }
end
end
end
In this example both heading1
and subtitle
are just methods in Ui::Typography
module.
But that's not it. If you really want it for some reason, you can do really fancy Ruby things, like this:
class Page < Phlex::HTML
def template
emph = [:i, :b, :u].sample
div do
plain "this text will have "
public_send(emph) { "emphasis"}
plain " of some kind"
end
end
end
On each render, the word "emphasis" will randomly be underlined, bold or in italics. Because why not?
Phlex is Ruby - you can package it
If you have a set of components, such as Card
, DataTable
, PaginationControls
etc., that just accept simple primitive values (strings, numbers, arrays, but not objects such as models), you can easily put them in a gem. And suddenly you have a distributable design system, which you could use across several applications - and have them look the same.
Some final words
This is my experience with working with Phlex so far. However, there might be some questions popping up in your head.
Should I rewrite everything in Phlex?
Phlex has some downsides. The most important is that is abstracts out HTML as Ruby and it might have some negative consequences on your workflow. If you are copying a lot of HTML from open source projects, StackOverflow or HTML file prepared by your designer, you will have to convert everything into Phlex Ruby code. This might be a huge slowdown.
Another problem is if you expect a designer or a UX specialist to actually modify HTML templates in your project. While ERB views are almost-HTML, Phlex views are not. It might be difficult to comprehend how they work for people not knowing Ruby.
How does it compare to ViewComponent?
I have used both ViewComponent and Cells when ViewComponent has not yet been born. They both offer a bit different approach, in which you put a mini-controllers inside your templates. There controllers perform some logic and then render another template, defined in a separate file. For many people, this might feel more familiar or more in line with Rails philosophy.
I personally think that the Phlex approach is closer to the original Rails approach to views. It does not introduce the whole new layer of those mini-controller-components rendering their own views (so you get model-view-template-component-componenttemplate), but rather replaces the dumb default view layer with something smarter, more Rubyish and testable.
Of course, everyone's mileage can vary. These are just tools that are, in fact, very different and it's not like one is automatically better than another.
Links
- Original version of this blog post
- Phlex website
- Ruby is a beautiful language for writing views – a blog post by Joel Drapper, author of Phlex, where he explains some of his motivation for the approach
- Phlexing with Joel Drapper – a Remote Ruby podcast episode about Phlex from September 2022
- Phlexing – an ERB to Phlex converter
- Nokolexbor – a faster drop-in replacement for Nokogiri, which I actually use to test Phlex views
Top comments (1)
An awesome companion to Seb Wilgosz's article. Thanks a lot!
Also, more Rails-ish so it can be targeted to a bigger audience.
I must say that after working with templating languages for about 20 years I'm 100% with you.
BTW, I see Phlex very similar to how Lucky, the Crystal framework from the thoughtbot guys, handles views.
Regards