On Presenters and Being OK With Being Dumb

written in

A page in our app has a lot of tooltips and help text. All of this text is static. But then came this customer request:

“If the record is a Course Fee, show ‘some text’. Otherwise, show ‘other text’.”

Fees are implemented using single table inheritance, so I already know exactly what kind of fee they are. I could just throw this right in the view.

1
2
3
4
5
<% if @fee.kind_of?(Fee::Course) %>
  some text
<% else %>
  other text
<% end %>

Ugh. Why would we want our view to know about our class structure? Do you want to have to remember to change this view when the fee type changes?

I could do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fee
  ...
  def dynamic_help_text
    "other text"
  end
  ...
end

class Fee::Course < Fee
  ...
  def dynamic_help_text
    "some text"
  end
  ...
end

And that’s…ok? I guess? I don’t see why the Fee needs to know something so purely presentational, though. Doesn’t seem like its responsibility. Presentational stuff belongs in presenters. And I love presenters!

Now, this view code will (hopefully, eventually) be entirely refactored to presenters. But it’s not there yet. We can take a first step by introducing a presenter that only cares about help text and tooltips. Let’s go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FeeHelpPresenter

  def initialize(fee)
    self.fee = fee
  end

  def dynamic_help_text
    case fee.class
    when Fee::Course
      "some text"
    else
      "other text"
    end
  end

  private
  attr_accessor :fee
end

Then, by making the help presenter available through the fee, I could do something like this in the view:

1
<%= @fee.help.dynamic_help_text %>

And, done. Well. There’s that case statement switching on fee’s class. That certainly smells bad. And it’s not great to test, either. If I want to test the presenter outside of Rails (which I most definitely do), then I have to tell the tests that Fee::Course and its ilk exist.

My inclination would be to switch on a property or behavior of the fee. But this proved tricky to find. Deciding that I simply didn’t have enough information at this time to make a good descision, I settled on a ‘good enough’ one. I let fees state if they were ‘for_courses’ or not, thinking that we might end up with other fee types that are for courses and all of those fees would share the same help text.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Fee
  ...
  def for_courses?
    false
  end
  ...
end

class Fee::Course < Fee
  ...
  def for_courses?
    true
  end
  ...
end

class FeeHelpPresenter
  ...
  def dynamic_help_text
    if fee.for_courses?
      "some text"
    else
      "other text"
    end
  end
  ...
end

And I’m admittedly not thrilled with that. It seems super dumb. But as this is the only case in the app where help text varies, I’m ok with this approach for now. And I sucessfully avoided putting logic in the view, so that’s good. When more information becomes available, a better design may present itself.