Html templates as Ruby code
Tags: rails, ruby, programming, javascript
Published: 2022-08-10 01:24:33

At the [Arkency Ecommerce project](https://github.com/RailsEventStore/ecommerce), 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:

```erb
<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.

```ruby
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:

```ruby
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?