I drafted this back in 2012… publishing it now in 2016 because I’m tired of all these drafts laying around.
ERB interprets all content outside of ERB tags as safe. That is, ERB does not escape anything outside of ERB tags. Therefore, if you want to put some content into an HTML attribute, you’re forced into putting that content into a string. I find it inconvenient sometimes that I have to put everything into a String. It might end up looking very messy. So, I tried to find a way to simply use the standard ERB approach of putting the content outside of ERB tags.
My first instinct was to use ActionView::Helpers::CaptureHelper.capture to turn the content into a string, which I could then escape. Unfortunately, capture() also interprets the content as safe, and marks the string as such. Using h() has no effect.
After looking at the source of capture() and seeing that it uses a buffer to accumulate the String, I came up with a solution: provide my own buffer which is marked unsafe:
class UnsafeBuffer < ActiveSupport::SafeBuffer def html_safe? false end end
To use it, pass it to with_output_buffer along with a block that returns a string. Even if the string is safe, the buffer is marked unsafe so the string will end up being escaped. In the example below, I am using capture() to produce a safe string from content outside the ERB tags.
<div data-stuff=" <%= with_output_buffer(UnsafeBuffer.new) { capture do %> My <span class="awesome">unsafe content</span> <% end } %> ">