The Evolution of Rails View Rendering

Rails view rendering has undergone significant evolution over the past 15 years. What started as simple ERB templates has grown into a sophisticated ecosystem of view rendering solutions, each addressing different pain points and use cases.

In this article, we’ll trace this evolution through a practical example: rendering a product list. We’ll see how each approach handles the same problem and understand the trade-offs involved.

The Journey Begins: ERB Partials

In the early days of Rails, ERB (Embedded Ruby) was the standard templating engine. Views were built using partials that could be rendered with collections:

<%# app/views/products/index.html.erb %>
<%= render partial: "product", collection: @products, as: :product %>
<%# or even %>
<%= render @products %>

<%# app/views/products/_product.html.erb %>
<% cache ["v2", product, product.updated_at.to_i] do %>
  <article id="<%= dom_id(product) %>">
    <h3><%= product.name %></h3>
    <p><%= number_to_currency(product.price_cents / 100.0) %></p>
  </article>
<% end %>

Pros:

Cons:

The HAML Revolution

HAML (HTML Abstraction Markup Language) was introduced to address ERB’s verbosity:

-# app/views/products/index.html.haml
= render partial: "product", collection: @products, as: :product
-# or simply:
= render @products

-# app/views/products/_product.html.haml
- cache ["v2", product, product.updated_at.to_i] do
  %article{id: dom_id(product)}
    %h3= product.name
    %p= number_to_currency(product.price_cents / 100.0)

Pros:

Cons:

Enter ViewComponent

ViewComponent was created to address the limitations of partials by providing a more structured, testable approach:

<%# app/views/products/index.html.erb %>
<%= render ProductComponent.with_collection(@products) %>
# app/components/product_component.rb
class ProductComponent < ViewComponent::Base
  def initialize(product:)
    @product = product
  end

  def call
    content_tag :article, id: dom_id(@product) do
      safe_join([
        content_tag(:h3, @product.name),
        content_tag(:p, number_to_currency(@product.price_cents / 100.0))
      ])
    end
  end
end

Or with ERB templates:

<%# app/components/product_component.html.erb %>
<% cache ["v2", @product, @product.updated_at.to_i] do %>
  <article id="<%= dom_id(@product) %>">
    <h3><%= @product.name %></h3>
    <p><%= number_to_currency(@product.price_cents / 100.0) %></p>
  </article>
<% end %>

Pros:

Cons:

The Phlex Approach

Phlex takes the ViewComponent concept further by keeping everything in Ruby:

# app/components/views/product.rb
class Views::Product < Phlex::HTML
  include Rails.application.routes.url_helpers
  include ActionView::Helpers::NumberHelper
  include ActionView::RecordIdentifier # for dom_id

  def initialize(product:)
    @product = product
  end

  def view_template
    article(id: dom_id(@product)) do
      h3 { @product.name }
      p  { number_to_currency(@product.price_cents / 100.0) }
    end
  end
end

# app/components/views/products.rb
class Views::Products < Phlex::HTML
  def initialize(products:)
    @products = products
  end

  def view_template
    if @products.blank?
      render empty_products
    else
      @products.each do |product|
        render Views::Product.new(product:)
      end
    end
  end

  private

  def empty_products
    section(class: "empty") do
      h3 { "No products found" }
      p  { "Try adjusting your filters or adding a product." }
    end
  end
end
<%# app/views/products/index.html.erb %>
<%= render Views::Products.new(products: @products) %>

Pros:

Cons:

Performance Comparison

Let’s look at the performance characteristics of each approach:

Approach Parse Time Render Time Memory Usage Bundle Size
ERB Partials Medium Fast Low Small
HAML Medium Fast Low Small
ViewComponent Low Fast Medium Medium
Phlex None Fastest Low Small

When to Use Each Approach

Use ERB When:

Use HAML When:

Use ViewComponent When:

Use Phlex When:

Migration Strategies

From ERB to ViewComponent

  1. Start with your most complex partials
  2. Extract view logic into component methods
  3. Add tests for the new components
  4. Gradually migrate simpler partials

From ViewComponent to Phlex

  1. Start with simple components that don’t use complex ERB features
  2. Convert the Ruby logic first, then the templates
  3. Use Phlex’s migration tools if available
  4. Test thoroughly as the syntax is quite different

Best Practices for Any Approach

1. Keep Components Small and Focused

Regardless of your chosen approach, keep components focused on a single responsibility.

2. Use Consistent Naming Conventions

Establish clear naming patterns for your components and partials.

3. Test Your Views

Write tests for your view logic, especially for complex components.

4. Consider Performance

Profile your view rendering performance and optimize where necessary.

5. Document Your Patterns

Create style guides and examples for your team to follow.

The Future of Rails Views

The Rails ecosystem continues to evolve. Recent developments include:

Conclusion

The evolution of Rails view rendering reflects the framework’s commitment to developer productivity and application performance. Each approach has its place:

One point of note, Marco Roth has been doing some significant work on HERB, and it was not covered in this article. Check it out to see the performance and ergonomic improvements on top of ERB.

The key is to choose an approach that fits your team’s skills, project requirements, and long-term goals. Start simple and evolve your view rendering strategy as your application grows and your team’s needs change.

Remember that the best view rendering approach is the one that your team can use effectively and maintain over time. Don’t let the latest trends drive your decision - focus on what works best for your specific context.