Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Presenter Sanity

I’m not a huge fan of Rails view helpers, which is a post for a different day, but put simply I prefer to encapsulate presentation logic in a presenter object rather than mix it in globally across all templates. There’s a few ways to do this, the simplest being the Good Ol’ Plain Ol’ Ruby Object:


  class FooPresenter
    def description_for(foo)
      Date.today.tuesday? ? "50% off! #{foo.description}" : foo.description
    end
  end

Usually, I’ll instantiate the presenter in a controller and then call it within the view:


  class FoosController
    def index
      @foos = Foo.all
      @foo_presenter = FooPresenter.new
    end
  end

  <% @foos.each do |foo| %>
    <%= foo.name %>
    <%= @foo_presenter.description_for(foo) %>
  <% end %>

Everything is simple and feels where it should be, but we’ve gotten this at the cost of losing view helpers mixed in from Rails. One of the helpers I find particularly helpful is strip_tags. If we want access to strip_tags we can do as Rails does when mixing it into the view scope and include it in our presenter:


  class FooPresenter
    include ActionView::Helpers::SanitizeHelper
    def description_for(foo)
      description = strip_tags(foo.description)
      html = Date.today.tuesday? ? "50% off! #{description}" : description
      html.html_safe
    end
  end

This feels a bit wrong as well. For one, we’ve increased the number of public methods on our presenter by at least 4, and only really needed one of them. Two, we have little control over how we are stripping the tags, and so any unit tests we’ve written for our presenter must integrate and essentially test parts of Rails. What we really want is something that can do what strip_tags is doing. Luckily, and due Rails becoming more modular, we already something to do this for us in the Rails library html-scanner. Under the hood, strip_tag is, unless configured differently, passing your string down to the sanitize method on an instance of an HTML::FullSanitizer object. We can do something similar in our presenter:


  require 'html/sanitizer'
  class FooPresenter
    def initialize(sanitizer=HTML::FullSanitizer.new)
      @sanitizer = sanitizer
    end
    def description_for(foo)
      description = @sanitizer.sanitize(foo.description)
      html = Date.today.tuesday? ? "50% off! #{description}" : description
      html.html_safe
    end
  end

Now we have an object that is easy to test purely as a unit, and also has the ability to be extended with different sanitizers.

While this works well with strip_tags, unfortunately not every helper in Rails is as nicely decoupled. The work being done in the number_to_* methods, for instance, are written completely in modules. It may be nice at some point to pull these into corresponding objects, but a short term solution could be to just have an object that includes the module:


  class NumberCruncher
    include ActionView::Helpers::NumberHelper
  end

  > NumberCruncher.new.number_to_currency(1)
  => "$1.00"

Comments
  1. Why is testing a part of rails a problem?
    Weather its rails helper or a ruby core library method(iterable#map anyone?) or something else, there will always be dependencies. The ruby itself is a piece of code.

    Test the result not implementation. Test the output. You want to strip tags? Test that. It shouldn’t matter weather you are writing it on your own or weather four of its using internal private methods or rails helpers.

    Or am I missing something here?

  2. Mike Mazur says:

    If it’s important that `FooPresenter#description_for` calls `strip_tags`, there should be a test for it. Use of mocking would be appropriate for this test.

  3. Brian Butz says:

    @Subhash Chandra

    For me, I would probably end up testing the result of sanitizing in an acceptance test that filled out a description form with some tags, and saw them shown on a page without tags. This way I can decide if I want to sanitize as the data comes in or when it goes out. How the presenter is setup now, I might not even unit test it, since most of what it does makes sense on the page itself.

    If I foresaw it requiring a bit more complexity, though, I would probably want to test drive it as a unit. For me, I want to pass in objects who have a contract that I can determine, and write the least surprising code I can. In the case of the FooPresenter, if I want to sanitize something, I want something that provides a #sanitize method to fit the bill. So I write a test that passes whatever’s going to sanitize as a stubbed object, and tests the behavior that belongs to the presenter.

    It just happens that in this case Rails provides an object with exactly the interface my code needs, and so I can pass it in (or default to it, though really I should be passing it in), and the behavior of the HTML::FullSanitizer provides makes my integration test pass.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *