html_safe and helpers in rails 3 – Rails 2 vs 3
In Rails 3 (contrary to Rails 2) Strings are automatically escaped using ERB::Util.h(string)
and the reason is simple. In 95% of the cases you won’t have to print html code inside ruby strings like this:
<%="<h1>Hello World</h1>"%>
In Rails 2 for this 95% of the cases you had to call the “h” helper to escape the output which was really annoying and insecure (if you forgot one).
Although this was the case for views, constructing a helper that prints html content became tricky in Rails 3. If you want your html content to stay unescaped it has to be an ActiveSupport::SafeBuffer
instance instead of a String
instance. To achieve this there are two solutions:
html_safe and helpers in rails 3 – constructing a helper
The first one is using html_safe method:
# helper.rb def foo html = "" html += "<strong>bar</strong>" html.html_safe end
<%=foo%>
This will output html correctly. A similar approach would be to use safe_concat inside your helper like this:
# helper.rb def foo safe_concat("<strong>bar</strong>") return end
<%=foo%>
In every case you can use raw()
view helper to keep your string unescaped:
# helper.rb def foo "<strong>bar</strong>" end
<%=raw(foo)%>
html_safe and helpers in rails 3 – the tricky part
I recently came up with this case:
#helper.rb def foo html = content_tag :p, "bar" html += "<strong>another bar</strong>" html.html_safe end
It’s output was something that I wasn’t expecting. The
<strong>
tags were escaped while the <p>
tags remained unescaped. This made me suspicious of the concat method that ActiveSupport::SafeBuffer
implements, which is the following:
def concat(value) if value.html_safe? super(value) else super(ERB::Util.h(value)) end end
As you can see if the argument of concat is a String (html_safe? == false) the value will be escaped and then concated.
So if you want to concat String
and ActiveSupport::SafeBuffer
instances always begin from a String instance even if it’s an empty string:
#helper.rb def foo html = "" html += content_tag :p, "bar" html += "<strong>another bar</strong>" html.html_safe end
This will do the job for you!
Thank you! This last bit about instancing an empty string first has helped me a lot!
Hey Thanks for this posts! It saved me a lot of time