The purpose of this post is to collect feedback on new Rails feature proposals, and I’ve put together a Pull Request. I welcome any comments, either here or on X or BlueSky
Translation: Ph.D.
I’d like to propose an extension to Ruby on Rails TagHelpers that will allow us to create HTML structures that were previously cumbersome.
In short, the view helper code would be written like this:
src="/path/to/image.jpg" alt="Alternate Text" />
Figure Caption
It might look like this (with my extension proposal),
def figure(src, alt:, figcaption: nil)
tag.figure do |b|
b << tag.img(src:, alt:)
b << tag.figcaption(figcaption) if figcaption.present?
end
end
Instead of this (how we currently write this using view helpers),
def figure(src, alt:, figcaption: nil)
figure_content = tag.img(src:, alt:)
figure_content += tag.figcaption(figcaption) if figcaption.present?
tag.figure do
figure_content
end
end
The proposed approach allows us to write more complex HTML using only tag helpers (without ERb), using code that closely reflects the structure of HTML. This will simplify the creation of UI components.
Note: The HTML and view helper code snippets above are from Garrett Dimon’s blog post.
Thanks to Garrett Dimon and Adam McCray
Garrett Dimon wrote a great article “Build your ERb and Partials for more maintainable front-end code in Rails” In it he discusses alternatives to using view helpers and partial views, as well as key points to consider when choosing between the two.
Adam McCrea wrote an article in response, “Say no to Partials and Helpers for a maintainable Rails frontend”proposed “to completely abandon the ERB part and helpers, and use Phlex instead”.
While I completely agree that Phlex is awesome and refreshing, I hope my proposal addresses at least one of Adam’s main complaints and allows us to actively re-evaluate traditional Rails ERb and view helpers.
In short, I don’t want to throw out the baby with the bathwater.
Let’s take a look at ViewComponents and Phlex first
Before delving into the details of my proposal, let’s take a look at how to write the above HTML using ViewComponents and Phlex.
I hope this illustrates my point, which can be summarized as “Why do we need to write a new category for 4 lines of HTML?”.
I think there is a better way.
view element
graph component.rb
class FigureComponent < ViewComponent::Base
def initialize(src, alt:, figcaption: nil)
@src = src
@alt = alt
@figcaption = figcaption
end
end
graph component.html.erb
src="<%= @src %>" alt="<%= @alt %>" />
<% if @figcaption.present? %>
<%= @figcaption %>
<% end %>
Flex
class Components::Figure < Components::Base
def initialize(src, alt:, figcaption: nil)
@src = src
@alt = alt
@figcaption = figcaption
end
def view_template
figure do
img(src: @src, alt: @alt)
figcaption { @figcaption } if @figcaption
end
end
end
this proposal
def figure(src, alt:, figcaption: nil)
tag.figure do |b|
b << tag.img(src:, alt:)
b << tag.figcaption(figcaption) if figcaption.present?
end
end
Need a simpler way
Both ViewComponent and Phlex are class-based, making them ideal for components that need to encapsulate logic.
However, with the rise of Tailwind CSS, we were encouraged to write many smaller components whose sole purpose was to dry out our code and save us from writing tons of CSS classes – which had little to no internal logic. In these cases, ViewComponents and Phlex may be overkill.
In many cases, the traditional view helpers included with Rails are sufficient for this purpose. For example, for the following HTML, you can write a very easy-to-understand helper that mirrors the HTML structure as shown below.
hypertext markup language
src="/path/to/image.jpg" alt="Alternate Text" />
View Assistant
def figure(src, alt:)
tag.figure do
tag.img(src:, alt:)
end
end
However, when
For the following HTML, where
Although I fully understand that ERb is better than the view helper at rendering highly complex HTML, I find it difficult to accept the idea that simply increasing the number of children from 1 to 2 will make the HTML more complex. I’m uncomfortable with the idea that this alone forces us to use ERb instead of view helpers.
hypertext markup language
src="/path/to/image.jpg" alt="Alternate Text" />
Figure Caption
View Assistant (difficult to read)
def figure(src, alt:, figcaption: nil)
figure_content = tag.img(src:, alt:)
figure_content += tag.figcaption(figcaption) if figcaption.present?
tag.figure do
figure_content
end
end
ERb (you often use this to replace the view helper above)
src="<%= src %>" alt="<%= alt %>" />
<% if figcaption.present? %>
<%= figcaption %>
<% end %>
Given this situation, I came up with the idea of making it easier to write HTML elements with multiple child elements in a view helper by adding a markup helper feature.
Why doesn’t tag helper work when there are multiple children?
If we write the following code in the view helper, we will lose tags and get only
.
This is because the value returned do
Blocks used as content
tag.figcaption
is the last expression in the block, and that value becomes the content. this tag.img
Lost because its value is not captured anywhere.
View Assistant
def figure(src, alt:, figcaption: nil)
tag.figure do
tag.img(src:, alt:)
tag.figcaption(figcaption) if figcaption.present?
end
end
hypertext markup language
Figure Caption
Suggested solution
I recommend using the following syntax. We extend the tag helper to optionally provide variables (in this case b
for “buffer”) to block. we promote tag.img
and tag.figcaption
to this buffer (b
), which is implemented as an array. After the tag helper generates the chunk, it executes safe_join
Produces HTML containing two child elements on the buffer and will be used as
def figure(src, alt:, figcaption: nil)
tag.figure do |b|
b << tag.img(src:, alt:)
b << tag.figcaption(figcaption) if figcaption.present?
end
end
The resulting code is tightly integrated with the structure of HTML and is easy to compare. Even someone with little knowledge of Ruby should be able to understand what’s going on.
benefit
With this proposed feature, developers who are hesitant about using ViewComponents or Phlex can better benefit from components while sticking with traditional ERb and view helpers.
It also provides a mechanism for creating widgets using functions (view helpers) that is easy to understand for non-Ruby developers (that is, designers). Note that React elements are also functions, not classes.
This feature allows us to implement slightly more complex elements in the View Assistant without having to resort to ERb templates. Compared with ERb, the advantages are the ability to accommodate multiple components in one file, explicit interface, simpler API calls (no need to write render "[path to partial]", [hash of locals]
This can get verbose) and performance.
What about unit testing?
One of the common benefits of using ViewComponents or Phlex instead of ERb and view helpers is the ability to unit test components.
I don’t know where this idea came from, but I do know that the Rails guide has something to do with Unit test ERb part And for Unit Test View Assistant.
implement
Although I’ve been using variations of this technique for years, I recommend adding it to Rails to make it more convenient and raise awareness of the technique. safe_join
technology.
I’m currently preparing the implementation and pull request. In the meantime, if you have any comments or comments, I’d love to hear them!