DEV Community 👩‍💻👨‍💻

Andrzej Krzywda
Andrzej Krzywda

Posted on

Html templates as Ruby code

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

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

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

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 (3)

Collapse
 
katafrakt profile image
Paweł Świątkowski

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.

Collapse
 
andrzejkrzywda profile image
Andrzej Krzywda Author

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?

Collapse
 
katafrakt profile image
Paweł Świątkowski

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.

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.