Calling All Classes


Introduction

This past weekend I was playing around with some ruby code I wrote for generating hobbit quotes, it wasn’t good at all. It was a simple little app that used a tag attribute of this one class. Since I had some time to kill I thought, why am I doing this huge if statement to decide what quote to render. Why not just do it automatically. Create a new class for each one and move the render method there.

What it looks like at first


class HobbitQuote
  attr_accessor :tag

  def initialize(args={})
    @tag = args[:tag]
  end

  def render
    if @tag == "bilbo"
      return "We hobbits are plain, quiet creatures. Adventures make one late for dinner."
    elsif @tag == "frodo"
      return "It’s a riddle. Speak "friend" and enter. What’s the Elvish word for friend?"
    elsif @tag == "sam"
      return "What we need is a few good taters."
    elsif @tag == "pippin"
      return "A mug of ale in my hand, putting my feet up on a settle after a hard day's work."
    elsif @tag == "merry"
      return "Right... Buckleberry Ferry! Follow me!"
    end
  end
end

Like I said it wasn’t very clean. Wouldn’t it be nice if this could be broken out into different classes and each one have it’s own render method? I had tests around this to make sure render was called and for each hobbit it rendered the correct quote. So I must not have been that drunk.

What happens when I don’t drink.


class HobbitQuote
  attr_accessor :tag

  def initialize(args={})
    @tag = args[:tag]
  end

  def render
    #ZOMG one line! goodbye huge if statement
    HobbitRender.new(@tag).render
  end
end

class HobbitRender
  attr_accessor :class

  def initialize(args={})
    @class = to_class(args[:tag])
  end

  def render
    if responds_to_render?
      @class.new.render
    else
      raise NotImplementedError, "This #{@class} does not respond to render!"
    end
  end

  private

  def responds_to_render?
    @class.instance_methods.include?(:render)
  end

  #This is the magic, this basically changes the tag into a class name
  #so bilbo become Bilbo, merry becomes Merry
  def to_class(tag)
    titleized_tag = tag.to_s.titleize.gsub(/\s/,'')
    titleized_tag.constantize
  end
end


class Bilbo
  def render
    "We hobbits are plain, quiet creatures. Adventures make one late for dinner."
  end
end

class Frodo
  def render
    "It’s a riddle. Speak "friend" and enter. What’s the Elvish word for friend?"
  end
end

class Sam
  def render
    "What we need is a few good taters."
  end
end

#etc...

#It's called like this:

#Creating my hobbit object which just has a name and a tag, very simple
hobbit = Hobbit.new(name: "Frodo Baggins", tag: "frodo")

#Pass the tag to the HobbitQuote object and call render
quote = HobbitQuote.new(tag: tag).render

#Output the quote
puts "Frodo:#{quote}"

#It’s a riddle. Speak "friend" and enter. What’s the Elvish word for friend?

Conclusion

So now I replaced the huge if statement that would only get larger over time and more complex when I add more hobbits with something I can reuse without putting in much effort. That’s much nicer in my opinon. I can now just create more hobbits and based on the tag, create a new class which handles the render. You might think this isn’t worth the time, or I wrote more code to do something simple. Think about what happens when the requirements of the quotes change? What if I want to make render for Sam return a random quote and have Frodo search the web for random Frodo quotes? That if statment is gonna get pretty fugly.