I am building a block of HTML code using Rails' content_tag
helper. The challenge I face right now is joining HTML strings from an array with HTML elements generated by content_tag
.
RuboCop Rails/OutputSafety reference.
For example:
options = ["<li>Three</li>", "<li>Four</li>", "<li>Five</li>"]
# This is code to generate blocks of HTML
out = []
out << content_tag(:ul,
content_tag(:li, "One") +
content_tag(:li, "Two") +
options.join(''),
:class => ["class_1", "class_2"])
safe_join(out)
# Expect result should be like
<ul class="class_1 class_2">
<li>One</li>
<li>Two</li>
<li>Three</li>
<li>Four</li>
<li>Five</li>
</ul>
# Actual result
<ul class="class_1 class_2">
<li>One</li>
<li>Two</li>
"<li>Three</li><li>Four</li><li>Five</li>"
</ul>
However, if I use the html_safe approach like below, it will work.
%{<ul>
<li>One</li>
<li>Two</li>
#{options.join('')}
</ul>
}.html_safe
Any suggestions on what I should change?
# New apporach
options = ["Three", "Four", "Five"]
out = []
out << content_tag(:ul,
content_tag(:li, "One") +
content_tag(:li, "Two") +
options.collect do |option|
content_tag(:li, "#{option[0]}")
end.join(""),
:class => ["class_1", "class_2"])
safe_join(out)
# New approach result
<ul class="class_1 class_2">
<li>One</li>
<li>Two</li>
"<li>Three</li><li>Four</li><li>Five</li>"
</ul>
The problem is that you are concatenating your output with unsafe strings coming from options
array. This is the only place where you should use html_safe
method for the whole output to be safe:
out << content_tag(:ul,
content_tag(:li, "One") +
content_tag(:li, "Two") +
options.join('').html_safe,
:class => ["class_1", "class_2"])
Edit
First of all safe_join
method does not work like html_safe
method, it doesn't only make the joined strings html_safe. It also makes html escaping if the joined string are not html_safe in order to avoid harmful content.
https://apidock.com/rails/ActionView/Helpers/OutputSafetyHelper/safe_join
In your case safe_join
method didn't do anything at all with the strings in out
array, since they had already been html_safe.
result = content_tag(:ul,
content_tag(:li, "One") +
content_tag(:li, "Two") +
options.join(''),
:class => ["class_1", "class_2"])
result.html_safe? # => true
The cause of the problem is that you concatenated a safe string with unsafe one:
content_tag(:li, "Two") + options.join('')
content_tag(:li, "Two").html_safe? # => true
options.join('').html_safe? # => false
At that moment options.join('')
was html escaped since it wasn't safe. See the example:
# html tags in the second string are escaped, since it is not safe
"<li>One</li>".html_safe + "<li>Two</li>" # => "<li>One</li><li>Two</li>"
# nothing has been escaped, since everything is safe
"<li>One</li>".html_safe + "<li>Two</li>".html_safe # => "<li>One</li><li>Two</li>"
So, in order to get expected results 2 conditions must be met:
safe_join
method must take array of html_safe strings. If they are not html_safe, all html tags will be escaped. As you can see, you did not fulfill the second condition.
Suggestions about the new approach
.join("")
method makes the result string unsafe, even if the array contains safe strings. Use safe_join
:
content_tag(:li, "One") +
content_tag(:li, "Two") +
safe_join(
options.collect do |option|
content_tag(:li, option)
end
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With