In Progress
Unit 1, Lesson 21
In Progress

Mixin to Object

It’s easy for code to become deeply dependent on mixin modules in Ruby. Especially in the context of Rails view code calling out to helper modules, disentangling dependencies can feel like untying a thick knot. In this episode you’ll learn a Ruby trick that will enable you to treat mixin modules as separate objects, and draw clear lines of encapsulation in the process.

Video transcript & code

In a typical Ruby on Rails application, we often create helper modules for methods that format domain objects for display. For instance, here's a helper module with some methods for pretty-printing playing card objects.

# encoding: UTF-8
module CardHelper
  RANK_NAMES = [nil, "Ace", *(2..10), "Jack", "Queen", "King"]
  SUIT_SYMBOLS = {
    "Hearts"   => "♠",
    "Clubs"    => "♣",
    "Spades"   => "♠",
    "Diamonds" => "♦",
  }

  def pretty_card(card)
    symbol    = SUIT_SYMBOLS[card.suit]
    rank_char = RANK_NAMES[card.rank].to_s[0]
    "#{symbol}#{rank_char}"
  end

  def long_card(card)
    "#{RANK_NAMES[card.rank]} of #{card.suit}"
  end
end

Let's imagine a web action in which we might use this helper. Maybe we are writing a blackjack game, and we need an action for when the player asks the dealer to hit them.

For this action, we'll need a deck , and a draw pile. will also need a hand for the player , which will start off with two cards.

We take the next card off draw pile , and move it to the hand .

In a real rails application, we would be putting all this code in a controller action. For the sake of this example, we'll keep things simple and just write some code to print to the console.

require "./cards"
require "./helpers"

@deck = Deck.new
@draw_pile = @deck.stack(:draw_pile)

@hand = @deck.stack(:hand)
@draw_pile.deal(2, to: @hand)

@new_card = @draw_pile.first
@new_card.move_to(@hand)

Now that we've performed the hit, and we have instance variables for all of the domain objects involved, we need to render a view to show the user what happened.

We will need the ERB library to render our view template.

To simulate the way that helpers are typically ubiquitously available in rails views, we'll include the CardHelper module at the top level.

Let's write our view template, making use of the card helpers to display both the new card drawn and the new state of the player's hand.

When we load and run this template, we get to see the card helpers in action.

require "./controller"
require "erb"
include CardHelper

hit_view = <<'EOF'
New card: <%= long_card(@new_card) %>

(Now in your hand: <%= @hand.map{|card| pretty_card(card)}.join(", ") %>)
EOF

ERB.new(hit_view).run

# >> New card: 3 of Clubs
# >>
# >> (Now in your hand: ♠4, ♠7, ♣3)

Now let's fast forward a bit. Our app has grown in size, and in order to make our views better testable and maintainable, we are pursuing a strategy used by many mature Rails shops: we're factoring our view templates into <a id="tapas__rails_pre href="https://www.rubytapas.com/out/rails-presenter-pattern">Presenter objects. As part of that process, we want to extract out our Blackjack "hit" view into its own presenter.

So we create a new HitPresenter class. We give it constructor parameters for the model objects it needs to know about. And we give it a method to render itself.

Into this method, we copy the original template code.

require "erb"
class HitPresenter
  def initialize(hand, new_card)
    @hand = hand
    @new_card = new_card
  end

def render
    ERB.new(<<~'EOF').result(binding)
    New card: <%= long_card(@new_card) %>

(Now in your hand: &lt;%=
  @hand.map{|card| pretty_card(card)}.join(", ")
%&gt;)
EOF

end end Back in the "controller" part of our code, we get rid of the template rendering. Now all we need to do is create a HitPresenter, give it the @hand and @new_card, and tell it to render itself. Since we're no longer using the CardHelper methods here, we'll remove the include for it as well.

There's just one problem: the template code we copied HitPresenter is still trying to use those CardHelper methods, and now it can't find them.

require "./controller"
require "./presenter"

puts HitPresenter.new(@hand, @new_card).render # ~> NoMethodError: undefined method `long_card' for #<HitPresenter:0x0000000...

~> NoMethodError

~> undefined method `long_card' for #<HitPresenter:0x00000002a28148>

~>

~> (erb):1:in `render'

~> C:/tools/ruby23/lib/ruby/2.3.0/erb.rb:864:in `eval'

~> C:/tools/ruby23/lib/ruby/2.3.0/erb.rb:864:in `result'

~> c:/Users/avdi_000/Dropbox/rubytapas-shared/working-episodes/481-mixin-to...

~> xmptmp-in256321RF.rb:5:in `<main>'

The most obvious solution is to just include the CardHelper module into the HitPresenter.

require "./presenter"

class HitPresenter
  include CardHelper
  # ...
end
require "./controller"
require "./presenter2"

puts HitPresenter.new(@hand, @new_card).render

>> New card: King of Clubs

>>

>> (Now in your hand: ♠3, ♠3, ♣K)

The trouble with this route is that it somewhat defeats the purpose of our moving templates into presenters. The point of having presenters is to have small objects with tightly constrained interfaces. They should be well encapsulated, easy to test, and have limited, well-defined dependencies.

One thing we really don't want to see is presenters that contain templates which in turn contain myriad, uncounted dependencies on a big amorphous blob of module includes.

Instead, we'd rather the templates contained within the presenters only reference methods provided by those presenters.

To put this in concrete terms, our HitPresenter should contain private methods for formatting cards.

The question is, how should we implement these private methods?

class HitPresenter
  # ...

private

def pretty_card(card)
    # ???
  end

def long_card(card)
    # ???
  end
end

Of course, we could just copy and paste the relevant methods from the CardHelper module. But then we would probably find that those methods depend on other methods, which we would have to copy and paste in turn. Not to mention that we'd really rather not see duplicate implementations of card formatting code diverge over time. All in all, it's a rabbit hole we'd rather not go down.

Anoher possibility is that we go ahead and include the CardHelper module, and for the time being just define the explicit HitPresenter versions to delegate to the module ancestor versions.

class HitPresenter
  include CardHelper
  # ...

private

def pretty_card(card)
    super
  end

def long_card(card)
    super
  end
end

Or we could rename our presenter versions of the methods, including in the template, and have the renamed methods delegate to the module originals.

class HitPresenter
  include CardHelper

# ...

def render
    ERB.new(<<~'EOF').result(binding)
    New card: <%= present_long_card(@new_card) %>

(Now in your hand: &lt;%=
  @hand.map{|card| present_pretty_card(card)}.join(", ")
%&gt;)
EOF

end

private

def present_pretty_card(card) pretty_card(card) end

def present_long_card(card) long_card(card) end end But both of these approaches fail to deal with the fact that we're still inheriting the entire interface of CardHelper into HitPresenter. Any templates inside HitPresenter can silently depend on any CardHelper methods. This isn't really moving us in the direction of separation of responsibilities.

There is another factor that we haven't talked about yet: our effort to extract presenters is part of a larger architectural shift that we're trying to make. We are trying to move away from the situation where some responsibilities are handled by objects, and others are handled by mixing modules. Instead, we've set an intention to have each refactoring move us towards a more consistent design where every role has an object responsible for it.

Ideally, each of our HitPresenter private formatter methods would delegate to an object responsible for formatting cards.

class HitPresenter
  # ...
  private

def pretty_card(card)
    @card_formatter.pretty_card(card)
  end

def long_card(card)
    @card_formatter.long_card(card)
  end
end

Now, we could take a detour here and start up another refactoring where we convert CardHelper into a class. But that would mean either introducing duplication, in the form of a new class paralleling the CardHelper module; or else it would mean rewriting every single view and presenter in the system that currently depends on CardHelper as a module.

And there's a more immediate problem with that idea: we have a policy of not starting new refactorings in the midst of current ones.

We don't want to make our presenter inherit legacy helper modules. But we also don't want to embark on a massive rewrite of our helpers. What do we do?

As it happens, there's a quick and very Ruby-ish solution to this conundrum.

In our presenter initializer, we can assign the @card_formatter instance variable a new blank object. Then, we can extend this object with the CardHelper module.

And that's it: our formatter methods now have a @card_formatter object that they can delegate to.

require "./presenter"
require "./helpers"

class HitPresenter
  def initialize(hand, new_card)
    @hand = hand
    @new_card = new_card
    @card_formatter = Object.new
    @card_formatter.extend(CardHelper)
  end

private

def pretty_card(card)
    @card_formatter.pretty_card(card)
  end

def long_card(card)
    @card_formatter.long_card(card)
  end
end

Let's run this code and verify that it works.

require "./controller"
require "./presenter3"

puts HitPresenter.new(@hand, @new_card).render

>> New card: King of Hearts

>>

>> (Now in your hand: ♠Q, ♦Q, ♠K)

Consider what we've accomplished here: we've enabled our HitPresenter methods to pretend that there is already a dedicated card formatter object of them to work with. Meanwhile, we've been able to leave the actual current CardHelper module unchanged and complete our refactoring. All with two lines of code.

@card_formatter = Object.new
@card_formatter.extend(CardHelper)

These lines of code also give us a convenient "seam" for future evolution. For instance, the next step we take towards converting CardHelper to an object might go like this:

We create a CardFormatter class. We include the current CardHelper module into it.

class CardFormatter
  include CardHelper
end

Then, in our presenter objects, we switch to using this new class.

require "./presenter"
require "./helpers"
require "./card_formatter"

class HitPresenter
  def initialize(hand, new_card)
    @hand = hand
    @new_card = new_card
    @card_formatter = CardFormatter.new
  end
  # ...
end

From here, we can start to gradually pull methods from the CardHelper module over to the CardFormatter class.

class CardFormatter
  include CardHelper

def pretty_card(card)
    # ...
  end
end

Right now, that's all in the future. But by dynamically extending a blank object with a module, we've taken the first step.

@card_formatter = Object.new
@card_formatter.extend(CardHelper)

Any time we have a module that we want to treat like an object, we can use this trick. We can lean on Ruby's dynamic nature to create encapsulation even where it wasn't originally planned. And that's a useful option to have. Happy hacking!

Responses