views:

42

answers:

2

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?

+1  A: 

Escaping the content for view is a View responsibility, this is the reason why the h helper is not available in controllers or models.

Still, I don't understand why can't you simply sanitize the content in the view.

Also note that, in Rails 3, you don't need to call the h helper. Content is sanitized automatically by default unless you flag it as html_safe!.

The main reason why is not logically true to use the h helper in the model is because the model should work view-independently. In other words, the model should not care whether the content is going to be embedded in a HTML document or JSON file (which requires a different escaping approach compared to HTML).

Simone Carletti
Good point. I'd love to have the helper in the view rather than in the model, because it's very much conceptually to do with the view. But there doesn't seem to be any way to write helper functions in ERB. What I want to know is where should the helper go? (I had this in mind when writing this question's title; I'll edit the question to make this query clearer.)
AlexC
And I know I don't need to use `h` *unless* I'm also using `html_safe`. But I am using `html_safe` for the reasons I explained - I'm assembling my own HTML string.
AlexC
+1  A: 

Place the logic that does your view assembly into a CardHelper:

app/helpers/card_helper.rb

class CardHelper
  def rules(card)
    initial_text = h card.rules_text
    # assembles HTML output based on initial_text and fields of card
    output_text.html_safe
  end
end

It's not clear from your example whether you want to format several fields via the format method. If that's the case, then you might be able to do:

class CardHelper
  def format(card, attribute)
    initial_text = h card[attribute]
    # assembles HTML output based on initial_text and fields of card
    output_text.html_safe
  end
end

You can use this helper like any other:

class CardsController
  helper CardHelper
end

and in your views:

<%= rules(@card) %>

or

<%= format(@card, :rules) %>
Jacob
So helper classes can get at `html_escape` and `h`? It's only model classes that can't? How strange.
AlexC
Actually, i'm not sure... If not you could always just include the `ERB::Util` module into your helper classes.
Jacob
Yup, just verified it. All helper methods are available in Helper classes defined in app/helpers.
Jacob
Yup, looks like it works. Thanks!
AlexC