I'm writing a webapp in Ruby on Rails 3. Rails 3 automatically escapes any potentially-bad strings, which is generally a good thing, but means if you assemble HTML yourself, you have to call html_safe
on it.
I have a Card model, which has several text fields, the contents of which are not trusted (may contain evil HTML or script). I have a function which performs a few transforms on one of these text fields, using other knowledge about the specific Card, to produce HTML output. I want to embed the HTML produced by this function in several places throughout several parts of my app.
Conceptually, this helper is to do with the View. However, I can't find any way to write functions in my View files; it seems they have to go in Helpers or the Controller/Model.
Since this function is very much specific to a Card object, the next best option would be to have a function inside my Card model card.rb:
class Card < ActiveRecord::Base
[...]
def format(unsafe_text)
initial_text = h unsafe_text # aka html_escape unsafe_text
# assembles HTML output based on initial_text and fields of self
output_text.html_safe!
end
Then I'd like to call this in assorted views by doing things like:
Rules text: <%= format(@card.rulestext) %>
However, there's a big problem here as well. In the Card model card.rb, I am able to use the html_safe!
function, but I'm not able to use h
or html_escape
. It seems that the h
and html_escape
functions are only available in ERB views, not in the helpers or controllers!
There are a few workarounds. I can make format
not sanitize its input, and go
Rules text: <%= format(h(@card.rulestext)) %>
But that's both prone to dangerous slipups (one missing h()
and we've got problems) and is very non-DRY. At the moment I'm using a partial to gain access to the h()
function:
(in a normal view)
Rules text: <%= render 'formattext', :text=> @card.rulestext %>
(app/views/shared/_formattext.html.erb)
<%= @card.format(html_escape(text)) %>
But this still feels dangerous. All I have to do is make single forgetful call to format(sometext)
in a view, rather than calling render 'formattext', :text=> sometext
, and I've got unescaped text running around.
Is there any better way to do this? Is there a way to write helper functions to live in the View rather than the Model or the Controller?