At the Arkency Ecommerce project, we went for the server rendered views strategy. Instead of dealing with JavaScript frameworks we have it all under the Rails umbrella.
Given the project is very "business" oriented, there's less need to have a fancy JS UI.
One typical view was our Client Panel login screen:
<div class="max-w-6xl mx-auto py-6 sm:px-6 lg:px-8">
<%= form_tag("login", method: :post) do %>
<div>
<label for="client" class="block font-bold">
Client
</label>
<%= select_tag(:client_id, options_from_collection_for_select(@clients, :uid, :name), id: "client", class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md") %>
<%= button_tag('Login', class: "mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded") %>
</div>
<% end %>
</div>
It's a usual Rails view called ERB, where it's html with some special Ruby snippets wrapped with <%= %>.
Such views are fine and every Rails dev knows how to modularise it into partials, how to use helpers etc.
However, over time Rails views tend to accumulate some "logic". It's hard to avoid some if
statements. The views are more Ruby than HTML.
That's why I decided to try another approach in the Client Panel part of our application.
Let's try to write those views in Ruby and make it generate HTML.
There are obvious cons to this approach - it's no longer HTML so it's harder to get frontend people to tweak it.
Still, the pros might be worth experimenting.
class Login < Arbre::Component
def self.build(view_context)
new(Arbre::Context.new(nil, view_context)).build
end
def build(attributes = {})
super(attributes)
clients = ClientOrders::Client.all
div class: "max-w-6xl mx-auto py-6 sm:px-6 lg:px-8" do
safe_join([
text_node(form_tag("login", method: :post)),
div do
safe_join([
select_tag(:client_id, options_from_collection_for_select(clients, :uid, :name), id: "client", class: "mt-1 focus:ring-blue-500 focus:border-blue-500 block shadow-sm sm:text-sm border-gray-300 rounded-md"),
button_tag('Login', class: "mt-2 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded")
])
end
])
end
end
end
It's different, yet similar.
I'm using the [arbre](https://activeadmin.github.io/arbre/)
library here. There are others, but this one was already battle-tested in one of our projects.
There are some gotchas here. While the Ruby code looks almost like a direct translation from HTML, there are differences.
Note the safe_join
calls. It's needed whenever we combine more than one "block" of html.
Another one is text_node
which is often needed for the Rails helpers. Otherwise, the tags might not be rendered.
Now the controller looks like this:
module Client
class ClientsController < ApplicationController
layout "client_panel"
def index
render html: Login.build(view_context), layout: true
end
It's too early to say if it's worth it. I will turn some more views in this area and try to make the views more complex. Then I will see if it's really easier to deal with the view logic in pure Ruby or ERB approach is better.
One hope which I have with this evolution is that now I can actually test the code as any other Ruby class. Also, it's now possible to measure the test coverage.
What is your idea about this approach?
Top comments (6)
I recently started experimenting with Phlex (pretty much the same idea). And while I see the upsides you mentioned, I still also have these HAML vibes, that every time I want to take a snippet of HTML from the internet, I have to first manually convert it into Ruby code.
On the other hand, I see a lot of potential with that - standard components for Bootstrap of Bulma could be much easier packed into a gem and distributed. You can also "distribute" a design system as a gem across multiple projects in the company. Probably also doable with ViewComponent, but I actually never thought about it before.
I gave Phlex a try, but in the end stayed with Arbre. They seem very similar. I wasn't sure about Phlex support for Rails helpers - everything works fine?
I didn't look at this idea from the distribution perspective, good point!
I looked from the angle of "packaged" read models (a bit more than just a design) and then yes, some of them could have some reusability potential and can be packaged as gems.
Is ViewComponent similarly cool with the Ruby objects idea?
ViewComponent follows more closely Rails conventions, so there is a class for a component and a template file in erb or whatever - basically what Cells do.
As for Phlex, I don't know if it works with Rails helpers, probably not, because they only recently announced working on Rails integration. I only used it in non-Rails context, so it wasn't very important to me. What I like about Phlex is its dedication for providing top-notch performance.
You can now access all the Rails helpers through the
helpers
method. There are also a number of adapters for tag helpers that adapt them to immediately output, so you don’t need to pass the return value toraw
.Awesome!
I’m working on a command you’ll be able to run that’ll search for HTML in your Phlex views and convert it for you so if you copy something form the internet, you can just paste it in and run the command.